diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0815906c23..2ffe6c4da9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,7 +8,7 @@ - [ ] Add JavaDocs and other comments explaining the behavior. - [ ] When adding or updating methods that fetch entities, add `@link` JavaDoc entries to the relevant documentation on https://docs.github.com/en/rest . - [ ] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See [CONTRIBUTING.md](CONTRIBUTING.md) for details. -- [ ] Run `mvn -D enable-ci clean install site` locally. If this command doesn't succeed, your change will not pass CI. +- [ ] Run `mvn -D enable-ci clean install site "-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED"` locally. If this command doesn't succeed, your change will not pass CI. - [ ] Push your changes to a branch other than `main`. You will create your PR from that branch. # When creating a PR: diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 74615e5f12..302259cfed 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,12 +3,32 @@ updates: - package-ecosystem: "maven" directory: "/" rebase-strategy: "disabled" + target-branch: "main" + open-pull-requests-limit: 10 schedule: interval: "monthly" time: "02:00" - package-ecosystem: "github-actions" directory: "/" rebase-strategy: "disabled" + target-branch: "main" + open-pull-requests-limit: 10 + schedule: + interval: "monthly" + time: "02:00" +- package-ecosystem: "maven" + directory: "/" + rebase-strategy: "disabled" + target-branch: "main-1.x" + open-pull-requests-limit: 10 + schedule: + interval: "monthly" + time: "02:00" +- package-ecosystem: "github-actions" + directory: "/" + rebase-strategy: "disabled" + target-branch: "main-1.x" + open-pull-requests-limit: 10 schedule: interval: "monthly" time: "02:00" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index ebb0d6b9c8..8b505fb760 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -42,17 +42,17 @@ jobs: steps: - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -63,7 +63,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -77,4 +77,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/create_release_tag_and_pr.yml b/.github/workflows/create_release_tag_and_pr.yml index 16a59223f5..cc155e3ded 100644 --- a/.github/workflows/create_release_tag_and_pr.yml +++ b/.github/workflows/create_release_tag_and_pr.yml @@ -10,22 +10,24 @@ jobs: create_release_tag: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Maven Central Repository - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' cache: 'maven' - - name: Reset staging/main + - name: Reset staging id: staging run: | - git checkout -B staging/main - git push --set-upstream -f origin staging/main + git checkout -B staging/$GITHUB_REF_NAME + git push --set-upstream -f origin staging/$GITHUB_REF_NAME + env: + GITHUB_REF_NAME: ${{ github.ref_name }} - name: Set Release Version id: release @@ -33,25 +35,25 @@ jobs: mvn -B versions:set versions:commit -DremoveSnapshot echo "version=$(mvn -B help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_OUTPUT - - uses: stefanzweifel/git-auto-commit-action@v5 + - uses: stefanzweifel/git-auto-commit-action@v7 with: commit_message: "Prepare release (${{ github.actor }}): github-api-${{ steps.release.outputs.version }}" tagging_message: 'github-api-${{ steps.release.outputs.version }}' - branch: staging/main + branch: staging/${{ github.ref_name }} - name: Increment Snapshot Version run: | mvn versions:set versions:commit -DnextSnapshot - - uses: stefanzweifel/git-auto-commit-action@v5 + - uses: stefanzweifel/git-auto-commit-action@v7 with: commit_message: "Prepare for next development iteration" - branch: staging/main + branch: staging/${{ github.ref_name }} - - name: pull-request to main + - name: Create pull-request uses: repo-sync/pull-request@v2 with: pr_title: "Prepare release (${{ github.actor }}): github-api-${{ steps.release.outputs.version }}" - source_branch: "staging/main" - destination_branch: "main" + source_branch: "staging/${{ github.ref_name }}" + destination_branch: "${{ github.ref_name }}" github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml index 9fa64e505a..0806499282 100644 --- a/.github/workflows/maven-build.yml +++ b/.github/workflows/maven-build.yml @@ -25,9 +25,9 @@ jobs: strategy: fail-fast: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 17 distribution: 'temurin' @@ -36,7 +36,7 @@ jobs: env: MAVEN_OPTS: ${{ env.JAVA_11_PLUS_MAVEN_OPTS }} run: mvn -B clean install -DskipTests --file pom.xml - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: maven-target-directory path: target/ @@ -47,9 +47,9 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 17 distribution: 'temurin' @@ -60,6 +60,25 @@ jobs: # running install site seems to more closely imitate real site deployment, # more likely to prevent failed deployment run: mvn -B clean install site -DskipTests --file pom.xml + test-bridged: + name: build-and-test Bridged (Java 17) + # Does not require build output, but orders execution to prevent launching test workflows when simple build fails + needs: build + runs-on: ubuntu-latest + strategy: + fail-fast: true + steps: + - uses: actions/checkout@v6 + - name: Set up JDK + uses: actions/setup-java@v5 + with: + java-version: 17 + distribution: 'temurin' + cache: 'maven' + - name: Maven Install (skipTests) + env: + MAVEN_OPTS: ${{ env.JAVA_11_PLUS_MAVEN_OPTS }} + run: mvn -B clean install -Pbridged -D enable-ci --file pom.xml "-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED" test: name: test (${{ matrix.os }}, Java ${{ matrix.java }}) # Does not require build output, but orders execution to prevent launching test workflows when simple build fails @@ -71,9 +90,9 @@ jobs: os: [ ubuntu, windows ] java: [ 17, 21 ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: ${{ matrix.java }} distribution: 'temurin' @@ -91,7 +110,7 @@ jobs: run: mvn -B clean install -D enable-ci --file pom.xml "-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED" - name: Save coverage data if: matrix.os == 'ubuntu' && matrix.java == '17' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: maven-test-target-directory path: target/ @@ -101,13 +120,13 @@ jobs: needs: test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/checkout@v6 + - uses: actions/download-artifact@v8 with: name: maven-test-target-directory path: target - name: Codecov Report - uses: codecov/codecov-action@v5.3.1 + uses: codecov/codecov-action@v6.0.0 with: # Codecov token from https://app.codecov.io/gh/hub4j/github-api/settings token: ${{ secrets.CODECOV_TOKEN }} @@ -119,13 +138,13 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/checkout@v6 + - uses: actions/download-artifact@v8 with: name: maven-target-directory path: target - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 11 distribution: 'temurin' diff --git a/.github/workflows/publish_release_branch.yml b/.github/workflows/publish_release_branch.yml index 6e3041a436..5f6b036426 100644 --- a/.github/workflows/publish_release_branch.yml +++ b/.github/workflows/publish_release_branch.yml @@ -12,9 +12,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Maven Central Repository - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' @@ -25,7 +25,7 @@ jobs: MAVEN_OPTS: ${{ env.JAVA_11_PLUS_MAVEN_OPTS }} run: mvn -B clean install site -D enable-ci --file pom.xml "-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED" - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: maven-release-target-directory path: target/ @@ -35,9 +35,9 @@ jobs: runs-on: ubuntu-latest needs: build steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Maven Central Repository - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' @@ -56,12 +56,20 @@ jobs: MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN_PASSWORD }} MAVEN_GPG_PASSPHRASE: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSPHRASE }} + - name: Publish package with bridge methods + run: mvn -B clean deploy -DskipTests -Prelease -Pbridged + env: + MAVEN_OPTS: ${{ env.JAVA_11_PLUS_MAVEN_OPTS }} + MAVEN_USERNAME: ${{ secrets.OSSRH_TOKEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN_PASSWORD }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSPHRASE }} + publish_gh_pages: runs-on: ubuntu-latest needs: build if: ${{ github.ref == 'refs/heads/release/v2.x' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 @@ -70,7 +78,7 @@ jobs: run: | echo "version=$(mvn -B help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_OUTPUT - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: maven-release-target-directory path: target @@ -82,7 +90,7 @@ jobs: cp -r ./target/site/* ./ - name: Publish GH Pages - uses: stefanzweifel/git-auto-commit-action@v5 + uses: stefanzweifel/git-auto-commit-action@v7 with: commit_message: "Release (${{ github.actor }}): v${{ steps.release.outputs.version }}" branch: gh-pages diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 1b55f3d0c9..f182c798df 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -17,6 +17,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Release Drafter - uses: release-drafter/release-drafter@v6 + uses: release-drafter/release-drafter@v7 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/pom.xml b/pom.xml index b28166f9d3..17cc1e001d 100644 --- a/pom.xml +++ b/pom.xml @@ -1,30 +1,60 @@ + 4.0.0 org.kohsuke - github-api - 2.0-alpha-4-SNAPSHOT + ${github-api.artifactId} + 2.0-rc.7-SNAPSHOT GitHub API for Java - https://hub4j.github.io/github-api/ GitHub API for Java + https://hub4j.github.io/github-api/ + + + + The MIT license + https://www.opensource.org/licenses/mit-license.php + repo + + + + + + kohsuke + Kohsuke Kawaguchi + kk@kohsuke.org + + + bitwiseman + Liam Newman + bitwiseman@gmail.com + + + + + + User List + github-api@googlegroups.com + https://groups.google.com/forum/#!forum/github-api + + scm:git:git@github.com/hub4j/${project.artifactId}.git scm:git:ssh://git@github.com/hub4j/${project.artifactId}.git - https://github.com/hub4j/github-api/ HEAD + https://github.com/hub4j/github-api/ - - sonatype-nexus-snapshots - Sonatype Nexus Snapshots - https://s01.oss.sonatype.org/content/repositories/snapshots/ - sonatype-nexus-staging Nexus Release Repository - https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ + ${nexus.serverUrl}/service/local/staging/deploy/maven2/ + + sonatype-nexus-snapshots + Sonatype Nexus Snapshots + ${nexus.serverUrl}/content/repositories/snapshots/ + github-pages gitsite:git@github.com/hub4j/${project.artifactId}.git @@ -32,38 +62,239 @@ - 3.3.5 - UTF-8 - 4.8.6.4 - 4.8.6 - true + github-api 3.0 - 4.12.0 - 3.10.2 0.70 0.50 false - 0.12.6 - - + + 0.13.0 + + https://ossrh-staging-api.central.sonatype.com + 4.12.0 + 3.17.0 + UTF-8 + true + 4.9.8.3 + 4.8.6 + 3.4.5 + + + + + com.fasterxml.jackson + jackson-bom + 2.21.2 + pom + import + + + org.junit + junit-bom + 5.13.4 + pom + import + + + org.slf4j + slf4j-bom + 2.0.17 + pom + import + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + junit + junit + 4.13.2 + + + org.hamcrest + hamcrest + ${hamcrest.version} + + + org.hamcrest + hamcrest-core + ${hamcrest.version} + + + org.hamcrest + hamcrest-library + ${hamcrest.version} + + + + + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + com.github.spotbugs + spotbugs-annotations + ${spotbugs.version} + + + com.infradna.tool + bridge-method-annotation + 1.31 + + + com.squareup.okhttp3 + okhttp + ${okhttp3.version} + true + + + com.squareup.okio + okio + ${okio.version} + true + + + commons-io + commons-io + 2.16.1 + + + io.jsonwebtoken + jjwt-api + ${jjwt.suite.version} + true + + + io.jsonwebtoken + jjwt-impl + ${jjwt.suite.version} + true + + + io.jsonwebtoken + jjwt-jackson + ${jjwt.suite.version} + true + + + org.apache.commons + commons-lang3 + 3.20.0 + + + com.github.npathai + hamcrest-optional + 2.0.0 + test + + + com.github.tomakehurst + wiremock-jre8-standalone + 2.35.2 + test + + + com.google.code.gson + gson + 2.13.2 + test + + + com.google.guava + guava + 33.5.0-jre + test + + + com.tngtech.archunit + archunit + 1.4.2 + test + + + junit + junit + test + + + org.awaitility + awaitility + 4.3.0 + test + + + org.hamcrest + hamcrest + test + + + + org.hamcrest + hamcrest-core + test + + + org.hamcrest + hamcrest-library + test + + + + org.junit.vintage + junit-vintage-engine + test + + + org.kohsuke + wordnet-random-name + 1.6 + test + + + org.mockito + mockito-core + 5.23.0 + test + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + repo.jenkins-ci.org + https://repo.jenkins-ci.org/public/ + + + + + repo.jenkins-ci.org + https://repo.jenkins-ci.org/public/ + + + - - - org.apache.maven.scm - maven-scm-provider-gitexe - 2.1.0 - - - org.apache.maven.scm - maven-scm-manager-plexus - 2.1.0 - - src/test/resources @@ -75,9 +306,21 @@ - org.codehaus.mojo - versions-maven-plugin - 2.18.0 + com.infradna.tool + bridge-method-injector + 1.31 + + + + process + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.8 org.apache.maven.plugins @@ -85,27 +328,41 @@ 3.5.1 - maven-surefire-plugin - 3.5.2 + org.apache.maven.plugins + maven-javadoc-plugin + 3.12.0 - - false + 11 + 11 + true + all - org.apache.maven.plugins - maven-source-plugin + maven-resources-plugin 3.3.1 - org.apache.maven.plugins - maven-gpg-plugin - 3.2.7 + maven-source-plugin + 3.4.0 + + + maven-surefire-plugin + 3.5.5 + + + false + + + + org.codehaus.mojo + versions-maven-plugin + 2.21.0 org.jacoco jacoco-maven-plugin - 0.8.12 + 0.8.14 @@ -130,10 +387,10 @@ check - verify check + verify ${project.build.directory}/jacoco-it.exec @@ -175,161 +432,48 @@ org.kohsuke.github.GHRepositorySearchBuilder org.kohsuke.github.GHUserSearchBuilder - - org.kohsuke.github.GHBranchProtection.RequiredSignatures - org.kohsuke.github.GHBranchProtectionBuilder.Restrictions - org.kohsuke.github.GHBranchProtection.Restrictions - org.kohsuke.github.GHCommentAuthorAssociation - org.kohsuke.github.GHDeployKey - org.kohsuke.github.GHEmail - org.kohsuke.github.GHInvitation - org.kohsuke.github.GHPullRequestCommitDetail.Authorship - org.kohsuke.github.GHPullRequestCommitDetail.Commit - org.kohsuke.github.GHPullRequestCommitDetail.CommitPointer - org.kohsuke.github.GHPullRequestCommitDetail.Tree - org.kohsuke.github.GHPullRequestCommitDetail - org.kohsuke.github.GHPullRequestFileDetail - org.kohsuke.github.GHReleaseUpdater - org.kohsuke.github.GHRequestedAction - org.kohsuke.github.GHVerifiedKey - - - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.11.2 - - 11 - 11 - true - all - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.7.0 - true - - sonatype-nexus-staging - https://oss.sonatype.org/ - true - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring.boot.version} - - - process-test-aot - - process-test-aot - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.21.0 - - - org.apache.maven.plugins - maven-release-plugin - 3.1.1 - - true - false - release - deploy - - - - org.sonatype.plugins - nexus-staging-maven-plugin - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.8.0 - - - org.apache.bcel - bcel - 6.10.0 - - - - - maven-compiler-plugin - 3.13.0 - - 11 - 11 - 11 - - - org.jenkins-ci - annotation-indexer - 1.17 - - - - - - maven-surefire-plugin - - @{jacoco.surefire.argLine} ${surefire.argLine} - - - - default-test - - src/test/resources/slow-or-flaky-tests.txt - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.4.2 - - - - org.kohsuke.github.api - - - - + + org.kohsuke.github.GHBranchProtection.RequiredSignatures + org.kohsuke.github.GHBranchProtectionBuilder.Restrictions + org.kohsuke.github.GHBranchProtection.Restrictions + org.kohsuke.github.GHCommentAuthorAssociation + org.kohsuke.github.GHDeployKey + org.kohsuke.github.GHEmail + org.kohsuke.github.GHInvitation + org.kohsuke.github.GHPullRequestCommitDetail.Authorship + org.kohsuke.github.GHPullRequestCommitDetail.Commit + org.kohsuke.github.GHPullRequestCommitDetail.CommitPointer + org.kohsuke.github.GHPullRequestCommitDetail.Tree + org.kohsuke.github.GHPullRequestCommitDetail + org.kohsuke.github.GHPullRequestFileDetail + org.kohsuke.github.GHReleaseUpdater + org.kohsuke.github.GHRequestedAction + org.kohsuke.github.GHVerifiedKey + + + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.7.0 + true + + sonatype-nexus-staging + ${nexus.serverUrl}/ + true + + + + + com.diffplug.spotless spotless-maven-plugin - 2.44.2 - - - spotless-check - - - - check - - - + 2.46.1 @@ -338,50 +482,60 @@ + 4.35 ${basedir}/src/build/eclipse/formatter.xml + true + true + false ${basedir}/src/build/eclipse/eclipse.importorder - - - + + + + + + + + spotless-check + + + + check + + + - com.github.spotbugs - spotbugs-maven-plugin - ${spotbugs-maven-plugin.version} + com.github.ekryd.sortpom + sortpom-maven-plugin + 4.0.0 - true - ${spotbugs-maven-plugin.failOnError} + false + scope,groupId,artifactId + groupId,artifactId + true + ${sortpom.verifyFail} - run-spotbugs - verify - check + verify + validate - - - - com.github.spotbugs - spotbugs - ${spotbugs.version} - - com.github.siom79.japicmp japicmp-maven-plugin - 0.23.0 + 0.23.1 true @@ -396,194 +550,172 @@ - verify cmp + verify - - - - - - - com.fasterxml.jackson - jackson-bom - 2.18.2 - import - pom - - - - - - - org.apache.commons - commons-lang3 - 3.17.0 - - - com.tngtech.archunit - archunit - 1.3.0 - test - - - org.hamcrest - hamcrest - ${hamcrest.version} - test - - - - org.springframework.boot - spring-boot-starter-test - ${spring.boot.version} - test - - - - org.hamcrest - hamcrest-core - ${hamcrest.version} - test - - - org.hamcrest - hamcrest-library - ${hamcrest.version} - test - - - com.github.npathai - hamcrest-optional - 2.0.0 - test - - - junit - junit - 4.13.2 - test - - - org.awaitility - awaitility - 4.2.2 - test - - - - org.junit.vintage - junit-vintage-engine - 5.10.2 - test - - - com.fasterxml.jackson.core - jackson-databind - - - commons-io - commons-io - 2.16.1 - - - com.infradna.tool - bridge-method-annotation - 1.30 - true - - - com.google.guava - guava - 33.4.0-jre - test - - - io.jsonwebtoken - jjwt-api - ${jjwt.suite.version} - true - - - io.jsonwebtoken - jjwt-impl - ${jjwt.suite.version} - true - - - io.jsonwebtoken - jjwt-jackson - ${jjwt.suite.version} - true - - - com.squareup.okio - okio - ${okio.version} - true - - - com.squareup.okhttp3 - okhttp - ${okhttp3.version} - true - - - org.kohsuke - wordnet-random-name - 1.6 - test - - - org.mockito - mockito-core - 5.15.2 - test - - - com.github.spotbugs - spotbugs-annotations - ${spotbugs.version} - provided - - - com.github.tomakehurst - wiremock-jre8-standalone - 2.35.2 - test - - - com.google.code.gson - gson - 2.11.0 - test - - - org.slf4j - slf4j-simple - 2.0.16 - test - - - - - repo.jenkins-ci.org - https://repo.jenkins-ci.org/public/ - - - - - repo.jenkins-ci.org - https://repo.jenkins-ci.org/public/ - - + + com.github.spotbugs + spotbugs-maven-plugin + ${spotbugs-maven-plugin.version} + + true + ${spotbugs-maven-plugin.failOnError} + + + + + com.github.spotbugs + spotbugs + ${spotbugs.version} + + + + + run-spotbugs + + check + + verify + + + + + maven-compiler-plugin + 3.15.0 + + 11 + 11 + 11 + + + org.jenkins-ci + annotation-indexer + 1.18 + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.5.0 + + + + org.kohsuke.github.api + + + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 3.9.0 + + + org.apache.bcel + bcel + 6.12.0 + + + + + org.apache.maven.plugins + maven-release-plugin + 3.3.1 + + true + false + release + deploy + + + + org.apache.maven.plugins + maven-site-plugin + 3.21.0 + + + maven-surefire-plugin + + @{jacoco.surefire.argLine} ${surefire.argLine} + + ${project.artifactId} + + + + + default-test + + src/test/resources/slow-or-flaky-tests.txt + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + process-test-aot + + process-test-aot + + + + + + + + org.apache.maven.scm + maven-scm-provider-gitexe + 2.2.1 + + + org.apache.maven.scm + maven-scm-manager-plexus + 2.1.0 + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + org.apache.maven.plugins + maven-project-info-reports-plugin + + + org.jacoco + jacoco-maven-plugin + + + + + report-integration + + + + + + @@ -600,10 +732,10 @@ okhttp-test - integration-test test + integration-test ${project.basedir}/target/${project.artifactId}-${project.version}.jar src/test/resources/slow-or-flaky-tests.txt @@ -611,11 +743,11 @@ - httpclient-test - integration-test + httpclient-test-tracing test + integration-test ${project.basedir}/target/${project.artifactId}-${project.version}.jar false @@ -629,10 +761,10 @@ slow-or-flaky-test - integration-test test + integration-test ${project.basedir}/target/${project.artifactId}-${project.version}.jar 2 @@ -641,10 +773,10 @@ slow-or-flaky-test-tracing - integration-test test + integration-test ${project.basedir}/target/${project.artifactId}-${project.version}.jar 2 @@ -658,10 +790,10 @@ jwt0.11.x-test - integration-test test + integration-test ${project.basedir}/target/${project.artifactId}-${project.version}.jar false @@ -694,19 +826,56 @@ + + bridged + + + github-api-bridged + + + + + com.infradna.tool + bridge-method-injector + + + maven-resources-plugin + + + copy-bridged-resources + + copy-resources + + + validate + + ${basedir}/target/classes/META-INF/native-image/org.kohsuke/${github-api.artifactId} + + + src/main/resources/META-INF/native-image/org.kohsuke/github-api + + + + + + + + + ci-non-windows - - enable-ci - !windows + + enable-ci + true + stop @@ -718,35 +887,31 @@ - - org.jacoco - jacoco-maven-plugin - com.diffplug.spotless spotless-maven-plugin spotless-check - - process-sources check + + process-sources org.apache.maven.plugins maven-enforcer-plugin - 3.5.0 + 3.6.2 enforce-jacoco-exist - verify enforce + verify @@ -760,6 +925,10 @@ + + org.jacoco + jacoco-maven-plugin + @@ -767,20 +936,16 @@ release - - org.jacoco - jacoco-maven-plugin - org.apache.maven.plugins maven-gpg-plugin sign-artifacts - verify sign + verify --pinentry-mode @@ -792,84 +957,35 @@ org.apache.maven.plugins - maven-source-plugin + maven-javadoc-plugin - attach-sources + attach-javadocs - jar-no-fork + jar org.apache.maven.plugins - maven-javadoc-plugin + maven-source-plugin - attach-javadocs + attach-sources - jar + jar-no-fork + + org.jacoco + jacoco-maven-plugin + - - - - org.jacoco - jacoco-maven-plugin - - - - - report-integration - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - - org.apache.maven.plugins - maven-project-info-reports-plugin - - - - - - - The MIT license - https://www.opensource.org/licenses/mit-license.php - repo - - - - - - User List - github-api@googlegroups.com - https://groups.google.com/forum/#!forum/github-api - - - - - - Kohsuke Kawaguchi - kohsuke - kk@kohsuke.org - - - Liam Newman - bitwiseman - bitwiseman@gmail.com - - diff --git a/src/main/java/org/kohsuke/github/AbstractBuilder.java b/src/main/java/org/kohsuke/github/AbstractBuilder.java index c91043cd19..af78f8b47a 100644 --- a/src/main/java/org/kohsuke/github/AbstractBuilder.java +++ b/src/main/java/org/kohsuke/github/AbstractBuilder.java @@ -43,13 +43,13 @@ */ abstract class AbstractBuilder extends GitHubInteractiveObject implements GitHubRequestBuilderDone { - @Nonnull - private final Class returnType; + @CheckForNull + private final R baseInstance; private final boolean commitChangesImmediately; - @CheckForNull - private final R baseInstance; + @Nonnull + private final Class returnType; /** The requester. */ @Nonnull @@ -111,7 +111,7 @@ public R done() throws IOException { } /** - * Applies a value to a name for this builder. + * Chooses whether to return a continuing builder or an updated data record * * If {@code S} is the same as {@code R}, this method will commit changes after the first value change and return a * {@code R} from {@link #done()}. @@ -119,23 +119,27 @@ public R done() throws IOException { * If {@code S} is not the same as {@code R}, this method will return an {@code S} and letting the caller batch * together multiple changes and call {@link #done()} when they are ready. * - * @param name - * the name of the field - * @param value - * the value of the field * @return either a continuing builder or an updated data record * @throws IOException * if an I/O error occurs */ @Nonnull @BetaApi - protected S with(@Nonnull String name, Object value) throws IOException { - requester.with(name, value); - return continueOrDone(); + protected S continueOrDone() throws IOException { + // This little bit of roughness in this base class means all inheriting builders get to create Updater and + // Setter classes from almost identical code. Creator can often be implemented with significant code reuse as + // well. + if (commitChangesImmediately) { + // These casts look strange and risky, but they they're actually guaranteed safe due to the return path + // being based on the previous comparison of class instances passed to the constructor. + return (S) done(); + } else { + return (S) this; + } } /** - * Chooses whether to return a continuing builder or an updated data record + * Applies a value to a name for this builder. * * If {@code S} is the same as {@code R}, this method will commit changes after the first value change and return a * {@code R} from {@link #done()}. @@ -143,22 +147,18 @@ protected S with(@Nonnull String name, Object value) throws IOException { * If {@code S} is not the same as {@code R}, this method will return an {@code S} and letting the caller batch * together multiple changes and call {@link #done()} when they are ready. * + * @param name + * the name of the field + * @param value + * the value of the field * @return either a continuing builder or an updated data record * @throws IOException * if an I/O error occurs */ @Nonnull @BetaApi - protected S continueOrDone() throws IOException { - // This little bit of roughness in this base class means all inheriting builders get to create Updater and - // Setter classes from almost identical code. Creator can often be implemented with significant code reuse as - // well. - if (commitChangesImmediately) { - // These casts look strange and risky, but they they're actually guaranteed safe due to the return path - // being based on the previous comparison of class instances passed to the constructor. - return (S) done(); - } else { - return (S) this; - } + protected S with(@Nonnull String name, Object value) throws IOException { + requester.with(name, value); + return continueOrDone(); } } diff --git a/src/main/java/org/kohsuke/github/EnterpriseManagedSupport.java b/src/main/java/org/kohsuke/github/EnterpriseManagedSupport.java index 5b4c62be6b..9d3030558d 100644 --- a/src/main/java/org/kohsuke/github/EnterpriseManagedSupport.java +++ b/src/main/java/org/kohsuke/github/EnterpriseManagedSupport.java @@ -14,11 +14,22 @@ */ class EnterpriseManagedSupport { + private static final Logger LOGGER = Logger.getLogger(EnterpriseManagedSupport.class.getName()); static final String COULD_NOT_RETRIEVE_ORGANIZATION_EXTERNAL_GROUPS = "Could not retrieve organization external groups"; static final String NOT_PART_OF_EXTERNALLY_MANAGED_ENTERPRISE_ERROR = "This organization is not part of externally managed enterprise."; + static final String TEAM_CANNOT_BE_EXTERNALLY_MANAGED_ERROR = "This team cannot be externally managed since it has explicit members."; - private static final Logger LOGGER = Logger.getLogger(EnterpriseManagedSupport.class.getName()); + private static String logUnexpectedFailure(final JsonProcessingException exception, final String payload) { + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw); + exception.printStackTrace(pw); + return String.format("Could not parse GitHub error response: '%s'. Full stacktrace follows:%n%s", payload, sw); + } + + static EnterpriseManagedSupport forOrganization(final GHOrganization org) { + return new EnterpriseManagedSupport(org); + } private final GHOrganization organization; @@ -26,6 +37,15 @@ private EnterpriseManagedSupport(GHOrganization organization) { this.organization = organization; } + Optional filterException(final GHException e) { + if (e.getCause() instanceof HttpException) { + final HttpException he = (HttpException) e.getCause(); + return filterException(he, COULD_NOT_RETRIEVE_ORGANIZATION_EXTERNAL_GROUPS) + .map(translated -> new GHException(COULD_NOT_RETRIEVE_ORGANIZATION_EXTERNAL_GROUPS, translated)); + } + return Optional.empty(); + } + Optional filterException(final HttpException he, final String scenario) { if (he.getResponseCode() == 400) { final String responseMessage = he.getMessage(); @@ -46,24 +66,4 @@ Optional filterException(final HttpException he, final String sce return Optional.empty(); } - Optional filterException(final GHException e) { - if (e.getCause() instanceof HttpException) { - final HttpException he = (HttpException) e.getCause(); - return filterException(he, COULD_NOT_RETRIEVE_ORGANIZATION_EXTERNAL_GROUPS) - .map(translated -> new GHException(COULD_NOT_RETRIEVE_ORGANIZATION_EXTERNAL_GROUPS, translated)); - } - return Optional.empty(); - } - - static EnterpriseManagedSupport forOrganization(final GHOrganization org) { - return new EnterpriseManagedSupport(org); - } - - private static String logUnexpectedFailure(final JsonProcessingException exception, final String payload) { - final StringWriter sw = new StringWriter(); - final PrintWriter pw = new PrintWriter(sw); - exception.printStackTrace(pw); - return String.format("Could not parse GitHub error response: '%s'. Full stacktrace follows:%n%s", payload, sw); - } - } diff --git a/src/main/java/org/kohsuke/github/GHApp.java b/src/main/java/org/kohsuke/github/GHApp.java index 9a6ba57072..628ac0cf01 100644 --- a/src/main/java/org/kohsuke/github/GHApp.java +++ b/src/main/java/org/kohsuke/github/GHApp.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.net.URL; +import java.time.Instant; import java.util.Collections; import java.util.Date; import java.util.List; @@ -20,48 +21,20 @@ */ public class GHApp extends GHObject { - /** - * Create default GHApp instance - */ - public GHApp() { - } - - private GHUser owner; - private String name; - private String slug; private String description; - private String externalUrl; - private Map permissions; + private List events; - private long installationsCount; + private String externalUrl; private String htmlUrl; - - /** - * Gets owner. - * - * @return the owner - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHUser getOwner() { - return owner; - } - - /** - * Gets name. - * - * @return the name - */ - public String getName() { - return name; - } - + private long installationsCount; + private String name; + private GHUser owner; + private Map permissions; + private String slug; /** - * Gets the slug name of the GitHub app. - * - * @return the slug name of the GitHub app + * Create default GHApp instance */ - public String getSlug() { - return slug; + public GHApp() { } /** @@ -73,15 +46,6 @@ public String getDescription() { return description; } - /** - * Gets external url. - * - * @return the external url - */ - public String getExternalUrl() { - return externalUrl; - } - /** * Gets events. * @@ -94,12 +58,12 @@ public List getEvents() { } /** - * Gets installations count. + * Gets external url. * - * @return the installations count + * @return the external url */ - public long getInstallationsCount() { - return installationsCount; + public String getExternalUrl() { + return externalUrl; } /** @@ -111,61 +75,6 @@ public URL getHtmlUrl() { return GitHubClient.parseURL(htmlUrl); } - /** - * Gets permissions. - * - * @return the permissions - */ - public Map getPermissions() { - return Collections.unmodifiableMap(permissions); - } - - /** - * Obtains all the installation requests associated with this app. - *

- * You must use a JWT to access this endpoint. - * - * @return a list of App installation requests - * @see List - * installation requests - */ - public PagedIterable listInstallationRequests() { - return root().createRequest() - .withUrlPath("/app/installation-requests") - .toIterable(GHAppInstallationRequest[].class, null); - } - - /** - * Obtains all the installations associated with this app. - *

- * You must use a JWT to access this endpoint. - * - * @return a list of App installations - * @see List installations - */ - public PagedIterable listInstallations() { - return listInstallations(null); - } - - /** - * Obtains all the installations associated with this app since a given date. - *

- * You must use a JWT to access this endpoint. - * - * @param since - * - Allows users to get installations that have been updated since a given date. - * @return a list of App installations since a given time. - * @see List installations - */ - public PagedIterable listInstallations(final Date since) { - Requester requester = root().createRequest().withUrlPath("/app/installations"); - if (since != null) { - requester.with("since", GitHubClient.printDate(since)); - } - return requester.toIterable(GHAppInstallation[].class, null); - } - /** * Obtain an installation associated with this app. *

@@ -242,4 +151,112 @@ public GHAppInstallation getInstallationByUser(String name) throws IOException { .fetch(GHAppInstallation.class); } + /** + * Gets installations count. + * + * @return the installations count + */ + public long getInstallationsCount() { + return installationsCount; + } + + /** + * Gets name. + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * Gets owner. + * + * @return the owner + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHUser getOwner() { + return owner; + } + + /** + * Gets permissions. + * + * @return the permissions + */ + public Map getPermissions() { + return Collections.unmodifiableMap(permissions); + } + + /** + * Gets the slug name of the GitHub app. + * + * @return the slug name of the GitHub app + */ + public String getSlug() { + return slug; + } + + /** + * Obtains all the installation requests associated with this app. + *

+ * You must use a JWT to access this endpoint. + * + * @return a list of App installation requests + * @see List + * installation requests + */ + public PagedIterable listInstallationRequests() { + return root().createRequest() + .withUrlPath("/app/installation-requests") + .toIterable(GHAppInstallationRequest[].class, null); + } + + /** + * Obtains all the installations associated with this app. + *

+ * You must use a JWT to access this endpoint. + * + * @return a list of App installations + * @see List installations + */ + public PagedIterable listInstallations() { + return listInstallations(GitHubClient.toInstantOrNull(null)); + } + + /** + * Obtains all the installations associated with this app since a given date. + *

+ * You must use a JWT to access this endpoint. + * + * @param since + * - Allows users to get installations that have been updated since a given date. + * @return a list of App installations since a given time. + * @see List installations + * @deprecated use {@link #listInstallations(Instant)} + */ + @Deprecated + public PagedIterable listInstallations(final Date since) { + return listInstallations(since.toInstant()); + } + + /** + * Obtains all the installations associated with this app since a given date. + *

+ * You must use a JWT to access this endpoint. + * + * @param since + * - Allows users to get installations that have been updated since a given date. + * @return a list of App installations since a given time. + * @see List installations + */ + public PagedIterable listInstallations(final Instant since) { + Requester requester = root().createRequest().withUrlPath("/app/installations"); + if (since != null) { + requester.with("since", GitHubClient.printInstant(since)); + } + return requester.toIterable(GHAppInstallation[].class, null); + } + } diff --git a/src/main/java/org/kohsuke/github/GHAppCreateTokenBuilder.java b/src/main/java/org/kohsuke/github/GHAppCreateTokenBuilder.java index edab276e90..54c5228257 100644 --- a/src/main/java/org/kohsuke/github/GHAppCreateTokenBuilder.java +++ b/src/main/java/org/kohsuke/github/GHAppCreateTokenBuilder.java @@ -14,9 +14,9 @@ */ public class GHAppCreateTokenBuilder extends GitHubInteractiveObject { + private final String apiUrlTail; /** The builder. */ protected final Requester builder; - private final String apiUrlTail; /** * Instantiates a new GH app create token builder. @@ -34,17 +34,33 @@ public class GHAppCreateTokenBuilder extends GitHubInteractiveObject { } /** - * By default the installation token has access to all repositories that the installation can access. To restrict - * the access to specific repositories, you can provide the repository_ids when creating the token. When you omit - * repository_ids, the response does not contain neither the repositories nor the permissions key. + * Creates an app token with all the parameters. + *

+ * You must use a JWT to access this endpoint. * - * @param repositoryIds - * Array containing the repositories Ids + * @return a GHAppInstallationToken + * @throws IOException + * on error + */ + public GHAppInstallationToken create() throws IOException { + return builder.method("POST").withUrlPath(apiUrlTail).fetch(GHAppInstallationToken.class); + } + + /** + * Set the permissions granted to the access token. The permissions object includes the permission names and their + * access type. + * + * @param permissions + * Map containing the permission names and types. * @return a GHAppCreateTokenBuilder */ @BetaApi - public GHAppCreateTokenBuilder repositoryIds(List repositoryIds) { - this.builder.with("repository_ids", repositoryIds); + public GHAppCreateTokenBuilder permissions(Map permissions) { + Map retMap = new HashMap<>(); + for (Map.Entry entry : permissions.entrySet()) { + retMap.put(entry.getKey(), GitHubRequest.transformEnum(entry.getValue())); + } + builder.with("permissions", retMap); return this; } @@ -63,34 +79,18 @@ public GHAppCreateTokenBuilder repositories(List repositories) { } /** - * Set the permissions granted to the access token. The permissions object includes the permission names and their - * access type. + * By default the installation token has access to all repositories that the installation can access. To restrict + * the access to specific repositories, you can provide the repository_ids when creating the token. When you omit + * repository_ids, the response does not contain neither the repositories nor the permissions key. * - * @param permissions - * Map containing the permission names and types. + * @param repositoryIds + * Array containing the repositories Ids * @return a GHAppCreateTokenBuilder */ @BetaApi - public GHAppCreateTokenBuilder permissions(Map permissions) { - Map retMap = new HashMap<>(); - for (Map.Entry entry : permissions.entrySet()) { - retMap.put(entry.getKey(), GitHubRequest.transformEnum(entry.getValue())); - } - builder.with("permissions", retMap); + public GHAppCreateTokenBuilder repositoryIds(List repositoryIds) { + this.builder.with("repository_ids", repositoryIds); return this; } - /** - * Creates an app token with all the parameters. - *

- * You must use a JWT to access this endpoint. - * - * @return a GHAppInstallationToken - * @throws IOException - * on error - */ - public GHAppInstallationToken create() throws IOException { - return builder.method("POST").withUrlPath(apiUrlTail).fetch(GHAppInstallationToken.class); - } - } diff --git a/src/main/java/org/kohsuke/github/GHAppFromManifest.java b/src/main/java/org/kohsuke/github/GHAppFromManifest.java index 3d6c4cdf20..2fa5e3998c 100644 --- a/src/main/java/org/kohsuke/github/GHAppFromManifest.java +++ b/src/main/java/org/kohsuke/github/GHAppFromManifest.java @@ -8,17 +8,17 @@ */ public class GHAppFromManifest extends GHApp { + private String clientId; + + private String clientSecret; + private String pem; + private String webhookSecret; /** * Create default GHAppFromManifest instance */ public GHAppFromManifest() { } - private String clientId; - private String clientSecret; - private String webhookSecret; - private String pem; - /** * Gets the client id * @@ -38,20 +38,20 @@ public String getClientSecret() { } /** - * Gets the webhook secret + * Gets the pem * - * @return the webhook secret + * @return the pem */ - public String getWebhookSecret() { - return webhookSecret; + public String getPem() { + return pem; } /** - * Gets the pem + * Gets the webhook secret * - * @return the pem + * @return the webhook secret */ - public String getPem() { - return pem; + public String getWebhookSecret() { + return webhookSecret; } } diff --git a/src/main/java/org/kohsuke/github/GHAppInstallation.java b/src/main/java/org/kohsuke/github/GHAppInstallation.java index c2611e468c..e92c744e99 100644 --- a/src/main/java/org/kohsuke/github/GHAppInstallation.java +++ b/src/main/java/org/kohsuke/github/GHAppInstallation.java @@ -1,11 +1,13 @@ package org.kohsuke.github; import com.fasterxml.jackson.annotation.JsonProperty; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.kohsuke.github.internal.EnumUtils; import java.io.IOException; import java.net.URL; +import java.time.Instant; import java.util.Collections; import java.util.Date; import java.util.List; @@ -25,98 +27,103 @@ */ public class GHAppInstallation extends GHObject { - /** - * Create default GHAppInstallation instance - */ - public GHAppInstallation() { - } + private static class GHAppInstallationRepositoryResult extends SearchResult { + private GHRepository[] repositories; - private GHUser account; + @Override + GHRepository[] getItems(GitHub root) { + return repositories; + } + } @JsonProperty("access_tokens_url") private String accessTokenUrl; - @JsonProperty("repositories_url") - private String repositoriesUrl; + + private GHUser account; @JsonProperty("app_id") private long appId; - @JsonProperty("target_id") - private long targetId; - @JsonProperty("target_type") - private GHTargetType targetType; - private Map permissions; private List events; - @JsonProperty("single_file_name") - private String singleFileName; + private String htmlUrl; + private Map permissions; + @JsonProperty("repositories_url") + private String repositoriesUrl; @JsonProperty("repository_selection") private GHRepositorySelection repositorySelection; - private String htmlUrl; + @JsonProperty("single_file_name") + private String singleFileName; private String suspendedAt; private GHUser suspendedBy; + @JsonProperty("target_id") + private long targetId; + @JsonProperty("target_type") + private GHTargetType targetType; /** - * Gets the html url. - * - * @return the html url + * Create default GHAppInstallation instance */ - public URL getHtmlUrl() { - return GitHubClient.parseURL(htmlUrl); + public GHAppInstallation() { } /** - * Gets account. + * Starts a builder that creates a new App Installation Token. * - * @return the account + *

+ * You use the returned builder to set various properties, then call {@link GHAppCreateTokenBuilder#create()} to + * finally create an access token. + * + * @return a GHAppCreateTokenBuilder instance */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHUser getAccount() { - return account; + public GHAppCreateTokenBuilder createToken() { + return new GHAppCreateTokenBuilder(root(), String.format("/app/installations/%d/access_tokens", getId())); } /** - * Gets access token url. + * Starts a builder that creates a new App Installation Token. * - * @return the access token url + *

+ * You use the returned builder to set various properties, then call {@link GHAppCreateTokenBuilder#create()} to + * finally create an access token. + * + * @param permissions + * map of permissions for the created token + * @return a GHAppCreateTokenBuilder instance + * @deprecated Use {@link GHAppInstallation#createToken()} instead. */ - public String getAccessTokenUrl() { - return accessTokenUrl; + @Deprecated + public GHAppCreateTokenBuilder createToken(Map permissions) { + return createToken().permissions(permissions); } /** - * Gets repositories url. + * Delete a Github App installation + *

+ * You must use a JWT to access this endpoint. * - * @return the repositories url + * @throws IOException + * on error + * @see Delete an installation */ - public String getRepositoriesUrl() { - return repositoriesUrl; + public void deleteInstallation() throws IOException { + root().createRequest().method("DELETE").withUrlPath(String.format("/app/installations/%d", getId())).send(); } /** - * List repositories that this app installation can access. + * Gets access token url. * - * @return the paged iterable - * @deprecated This method cannot work on a {@link GHAppInstallation} retrieved from - * {@link GHApp#listInstallations()} (for example), except when resorting to unsupported hacks involving - * setRoot(GitHub) to switch from an application client to an installation client. This method will be - * removed. You should instead use an installation client (with an installation token, not a JWT), - * retrieve a {@link GHAuthenticatedAppInstallation} from {@link GitHub#getInstallation()}, then call - * {@link GHAuthenticatedAppInstallation#listRepositories()}. + * @return the access token url */ - @Deprecated - public PagedSearchIterable listRepositories() { - GitHubRequest request; - - request = root().createRequest().withUrlPath("/installation/repositories").build(); - - return new PagedSearchIterable<>(root(), request, GHAppInstallationRepositoryResult.class); + public String getAccessTokenUrl() { + return accessTokenUrl; } - private static class GHAppInstallationRepositoryResult extends SearchResult { - private GHRepository[] repositories; - - @Override - GHRepository[] getItems(GitHub root) { - return repositories; - } + /** + * Gets account. + * + * @return the account + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHUser getAccount() { + return account; } /** @@ -129,50 +136,62 @@ public long getAppId() { } /** - * Gets target id. + * Gets events. * - * @return the target id + * @return the events */ - public long getTargetId() { - return targetId; + public List getEvents() { + return events.stream() + .map(e -> EnumUtils.getEnumOrDefault(GHEvent.class, e, GHEvent.UNKNOWN)) + .collect(Collectors.toList()); } /** - * Gets target type. + * Gets the html url. * - * @return the target type + * @return the html url */ - public GHTargetType getTargetType() { - return targetType; + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); } /** - * Gets permissions. + * Shows whether the user or organization account actively subscribes to a plan listed by the authenticated GitHub + * App. When someone submits a plan change that won't be processed until the end of their billing cycle, you will + * also see the upcoming pending change. * - * @return the permissions + *

+ * GitHub Apps must use a JWT to access this endpoint. + *

+ * OAuth Apps must use basic authentication with their client ID and client secret to access this endpoint. + * + * @return a GHMarketplaceAccountPlan instance + * @throws IOException + * it may throw an {@link IOException} + * @see Get + * a subscription plan for an account */ - public Map getPermissions() { - return Collections.unmodifiableMap(permissions); + public GHMarketplaceAccountPlan getMarketplaceAccount() throws IOException { + return new GHMarketplacePlanForAccountBuilder(root(), account.getId()).createRequest(); } /** - * Gets events. + * Gets permissions. * - * @return the events + * @return the permissions */ - public List getEvents() { - return events.stream() - .map(e -> EnumUtils.getEnumOrDefault(GHEvent.class, e, GHEvent.UNKNOWN)) - .collect(Collectors.toList()); + public Map getPermissions() { + return Collections.unmodifiableMap(permissions); } /** - * Gets single file name. + * Gets repositories url. * - * @return the single file name + * @return the repositories url */ - public String getSingleFileName() { - return singleFileName; + public String getRepositoriesUrl() { + return repositoriesUrl; } /** @@ -184,13 +203,23 @@ public GHRepositorySelection getRepositorySelection() { return repositorySelection; } + /** + * Gets single file name. + * + * @return the single file name + */ + public String getSingleFileName() { + return singleFileName; + } + /** * Gets suspended at. * * @return the suspended at */ - public Date getSuspendedAt() { - return GitHubClient.parseDate(suspendedAt); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getSuspendedAt() { + return GitHubClient.parseInstant(suspendedAt); } /** @@ -204,66 +233,40 @@ public GHUser getSuspendedBy() { } /** - * Delete a Github App installation - *

- * You must use a JWT to access this endpoint. + * Gets target id. * - * @throws IOException - * on error - * @see Delete an installation + * @return the target id */ - public void deleteInstallation() throws IOException { - root().createRequest().method("DELETE").withUrlPath(String.format("/app/installations/%d", getId())).send(); + public long getTargetId() { + return targetId; } /** - * Starts a builder that creates a new App Installation Token. - * - *

- * You use the returned builder to set various properties, then call {@link GHAppCreateTokenBuilder#create()} to - * finally create an access token. + * Gets target type. * - * @param permissions - * map of permissions for the created token - * @return a GHAppCreateTokenBuilder instance - * @deprecated Use {@link GHAppInstallation#createToken()} instead. + * @return the target type */ - @Deprecated - public GHAppCreateTokenBuilder createToken(Map permissions) { - return createToken().permissions(permissions); + public GHTargetType getTargetType() { + return targetType; } /** - * Starts a builder that creates a new App Installation Token. - * - *

- * You use the returned builder to set various properties, then call {@link GHAppCreateTokenBuilder#create()} to - * finally create an access token. + * List repositories that this app installation can access. * - * @return a GHAppCreateTokenBuilder instance + * @return the paged iterable + * @deprecated This method cannot work on a {@link GHAppInstallation} retrieved from + * {@link GHApp#listInstallations()} (for example), except when resorting to unsupported hacks involving + * setRoot(GitHub) to switch from an application client to an installation client. This method will be + * removed. You should instead use an installation client (with an installation token, not a JWT), + * retrieve a {@link GHAuthenticatedAppInstallation} from {@link GitHub#getInstallation()}, then call + * {@link GHAuthenticatedAppInstallation#listRepositories()}. */ - public GHAppCreateTokenBuilder createToken() { - return new GHAppCreateTokenBuilder(root(), String.format("/app/installations/%d/access_tokens", getId())); - } + @Deprecated + public PagedSearchIterable listRepositories() { + GitHubRequest request; - /** - * Shows whether the user or organization account actively subscribes to a plan listed by the authenticated GitHub - * App. When someone submits a plan change that won't be processed until the end of their billing cycle, you will - * also see the upcoming pending change. - * - *

- * GitHub Apps must use a JWT to access this endpoint. - *

- * OAuth Apps must use basic authentication with their client ID and client secret to access this endpoint. - * - * @return a GHMarketplaceAccountPlan instance - * @throws IOException - * it may throw an {@link IOException} - * @see Get - * a subscription plan for an account - */ - public GHMarketplaceAccountPlan getMarketplaceAccount() throws IOException { - return new GHMarketplacePlanForAccountBuilder(root(), account.getId()).createRequest(); + request = root().createRequest().withUrlPath("/installation/repositories").build(); + + return new PagedSearchIterable<>(root(), request, GHAppInstallationRepositoryResult.class); } } diff --git a/src/main/java/org/kohsuke/github/GHAppInstallationRequest.java b/src/main/java/org/kohsuke/github/GHAppInstallationRequest.java index a2e7c279fe..44ace753a2 100644 --- a/src/main/java/org/kohsuke/github/GHAppInstallationRequest.java +++ b/src/main/java/org/kohsuke/github/GHAppInstallationRequest.java @@ -9,16 +9,16 @@ * @see GHApp#listInstallationRequests() GHApp#listInstallationRequests() */ public class GHAppInstallationRequest extends GHObject { + private GHOrganization account; + + private GHUser requester; + /** * Create default GHAppInstallationRequest instance */ public GHAppInstallationRequest() { } - private GHOrganization account; - - private GHUser requester; - /** * Gets the organization where the app was requested to be installed. * diff --git a/src/main/java/org/kohsuke/github/GHAppInstallationToken.java b/src/main/java/org/kohsuke/github/GHAppInstallationToken.java index 817156a4bc..3d268cf38a 100644 --- a/src/main/java/org/kohsuke/github/GHAppInstallationToken.java +++ b/src/main/java/org/kohsuke/github/GHAppInstallationToken.java @@ -1,5 +1,8 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; + +import java.time.Instant; import java.util.*; // TODO: Auto-generated Javadoc @@ -11,36 +14,37 @@ */ public class GHAppInstallationToken extends GitHubInteractiveObject { + private Map permissions; + + private List repositories; + + private GHRepositorySelection repositorySelection; + private String token; + /** The expires at. */ + protected String expiresAt; /** * Create default GHAppInstallationToken instance */ public GHAppInstallationToken() { } - private String token; - - /** The expires at. */ - protected String expires_at; - private Map permissions; - private List repositories; - private GHRepositorySelection repositorySelection; - /** - * Gets permissions. + * Gets expires at. * - * @return the permissions + * @return date when this token expires */ - public Map getPermissions() { - return Collections.unmodifiableMap(permissions); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getExpiresAt() { + return GitHubClient.parseInstant(expiresAt); } /** - * Gets token. + * Gets permissions. * - * @return the token + * @return the permissions */ - public String getToken() { - return token; + public Map getPermissions() { + return Collections.unmodifiableMap(permissions); } /** @@ -62,11 +66,11 @@ public GHRepositorySelection getRepositorySelection() { } /** - * Gets expires at. + * Gets token. * - * @return date when this token expires + * @return the token */ - public Date getExpiresAt() { - return GitHubClient.parseDate(expires_at); + public String getToken() { + return token; } } diff --git a/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java b/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java index 8a150de1fb..fc89d371ee 100644 --- a/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java +++ b/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java @@ -12,8 +12,8 @@ class GHAppInstallationsIterable extends PagedIterable { /** The Constant APP_INSTALLATIONS_URL. */ public static final String APP_INSTALLATIONS_URL = "/user/installations"; - private final transient GitHub root; private GHAppInstallationsPage result; + private final transient GitHub root; /** * Instantiates a new GH app installations iterable. diff --git a/src/main/java/org/kohsuke/github/GHAppInstallationsPage.java b/src/main/java/org/kohsuke/github/GHAppInstallationsPage.java index 9a29832941..cd8f9a1f7e 100644 --- a/src/main/java/org/kohsuke/github/GHAppInstallationsPage.java +++ b/src/main/java/org/kohsuke/github/GHAppInstallationsPage.java @@ -5,8 +5,8 @@ * Represents the one page of GHAppInstallations. */ class GHAppInstallationsPage { - private int total_count; private GHAppInstallation[] installations; + private int totalCount; /** * Gets the total count. @@ -14,7 +14,7 @@ class GHAppInstallationsPage { * @return the total count */ public int getTotalCount() { - return total_count; + return totalCount; } /** diff --git a/src/main/java/org/kohsuke/github/GHArtifact.java b/src/main/java/org/kohsuke/github/GHArtifact.java index cc37a5bf4d..21c16836f8 100644 --- a/src/main/java/org/kohsuke/github/GHArtifact.java +++ b/src/main/java/org/kohsuke/github/GHArtifact.java @@ -1,12 +1,14 @@ package org.kohsuke.github; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.commons.lang3.StringUtils; import org.kohsuke.github.function.InputStreamFunction; import java.io.IOException; import java.net.URL; +import java.time.Instant; import java.util.Date; import java.util.Objects; @@ -20,38 +22,47 @@ */ public class GHArtifact extends GHObject { - /** - * Create default GHArtifact instance - */ - public GHArtifact() { - } + private String archiveDownloadUrl; + private boolean expired; + + private String expiresAt; + private String name; // Not provided by the API. @JsonIgnore private GHRepository owner; - - private String name; private long sizeInBytes; - private String archiveDownloadUrl; - private boolean expired; - private String expiresAt; + /** + * Create default GHArtifact instance + */ + public GHArtifact() { + } /** - * Gets the name. + * Deletes the artifact. * - * @return the name + * @throws IOException + * the io exception */ - public String getName() { - return name; + public void delete() throws IOException { + root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); } /** - * Gets the size of the artifact in bytes. + * Downloads the artifact. * - * @return the size + * @param + * the type of result + * @param streamFunction + * The {@link InputStreamFunction} that will process the stream + * @return the result of reading the stream. + * @throws IOException + * The IO exception. */ - public long getSizeInBytes() { - return sizeInBytes; + public T download(InputStreamFunction streamFunction) throws IOException { + requireNonNull(streamFunction, "Stream function must not be null"); + + return root().createRequest().method("GET").withUrlPath(getApiRoute(), "zip").fetchStream(streamFunction); } /** @@ -64,21 +75,22 @@ public URL getArchiveDownloadUrl() { } /** - * If this artifact has expired. + * Gets the date at which this artifact will expire. * - * @return if the artifact has expired + * @return the date of expiration */ - public boolean isExpired() { - return expired; + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getExpiresAt() { + return GitHubClient.parseInstant(expiresAt); } /** - * Gets the date at which this artifact will expire. + * Gets the name. * - * @return the date of expiration + * @return the name */ - public Date getExpiresAt() { - return GitHubClient.parseDate(expiresAt); + public String getName() { + return name; } /** @@ -92,30 +104,21 @@ public GHRepository getRepository() { } /** - * Deletes the artifact. + * Gets the size of the artifact in bytes. * - * @throws IOException - * the io exception + * @return the size */ - public void delete() throws IOException { - root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); + public long getSizeInBytes() { + return sizeInBytes; } /** - * Downloads the artifact. + * If this artifact has expired. * - * @param - * the type of result - * @param streamFunction - * The {@link InputStreamFunction} that will process the stream - * @return the result of reading the stream. - * @throws IOException - * The IO exception. + * @return if the artifact has expired */ - public T download(InputStreamFunction streamFunction) throws IOException { - requireNonNull(streamFunction, "Stream function must not be null"); - - return root().createRequest().method("GET").withUrlPath(getApiRoute(), "zip").fetchStream(streamFunction); + public boolean isExpired() { + return expired; } private String getApiRoute() { diff --git a/src/main/java/org/kohsuke/github/GHArtifactsPage.java b/src/main/java/org/kohsuke/github/GHArtifactsPage.java index 4ef5878699..8b3675bb11 100644 --- a/src/main/java/org/kohsuke/github/GHArtifactsPage.java +++ b/src/main/java/org/kohsuke/github/GHArtifactsPage.java @@ -9,8 +9,8 @@ @SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, justification = "JSON API") class GHArtifactsPage { - private int total_count; private GHArtifact[] artifacts; + private int totalCount; /** * Gets the total count. @@ -18,7 +18,7 @@ class GHArtifactsPage { * @return the total count */ public int getTotalCount() { - return total_count; + return totalCount; } /** diff --git a/src/main/java/org/kohsuke/github/GHAsset.java b/src/main/java/org/kohsuke/github/GHAsset.java index 3f9c8f420c..8ad0455483 100644 --- a/src/main/java/org/kohsuke/github/GHAsset.java +++ b/src/main/java/org/kohsuke/github/GHAsset.java @@ -13,41 +13,63 @@ public class GHAsset extends GHObject { /** - * Create default GHAsset instance + * Wrap gh asset [ ]. + * + * @param assets + * the assets + * @param release + * the release + * @return the gh asset [ ] */ - public GHAsset() { + public static GHAsset[] wrap(GHAsset[] assets, GHRelease release) { + for (GHAsset aTo : assets) { + aTo.wrap(release); + } + return assets; } - /** The owner. */ - GHRepository owner; - private String name; + private String browserDownloadUrl; + private String contentType; + private long downloadCount; private String label; - private String state; - private String content_type; + private String name; private long size; - private long download_count; - private String browser_download_url; + private String state; + /** The owner. */ + GHRepository owner; /** - * Gets content type. - * - * @return the content type + * Create default GHAsset instance */ - public String getContentType() { - return content_type; + public GHAsset() { } /** - * Sets content type. + * Delete. * - * @param contentType - * the content type * @throws IOException * the io exception */ - public void setContentType(String contentType) throws IOException { - edit("content_type", contentType); - this.content_type = contentType; + public void delete() throws IOException { + root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); + } + + /** + * Gets browser download url. + * + * @return the browser download url + */ + public String getBrowserDownloadUrl() { + return browserDownloadUrl; + } + + /** + * Gets content type. + * + * @return the content type + */ + public String getContentType() { + return contentType; } /** @@ -56,7 +78,7 @@ public void setContentType(String contentType) throws IOException { * @return the download count */ public long getDownloadCount() { - return download_count; + return downloadCount; } /** @@ -68,19 +90,6 @@ public String getLabel() { return label; } - /** - * Sets label. - * - * @param label - * the label - * @throws IOException - * the io exception - */ - public void setLabel(String label) throws IOException { - edit("label", label); - this.label = label; - } - /** * Gets name. * @@ -119,26 +128,33 @@ public String getState() { } /** - * Gets browser download url. + * Sets content type. * - * @return the browser download url + * @param contentType + * the content type + * @throws IOException + * the io exception */ - public String getBrowserDownloadUrl() { - return browser_download_url; - } - - private void edit(String key, Object value) throws IOException { - root().createRequest().with(key, value).method("PATCH").withUrlPath(getApiRoute()).send(); + public void setContentType(String contentType) throws IOException { + edit("content_type", contentType); + this.contentType = contentType; } /** - * Delete. + * Sets label. * + * @param label + * the label * @throws IOException * the io exception */ - public void delete() throws IOException { - root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); + public void setLabel(String label) throws IOException { + edit("label", label); + this.label = label; + } + + private void edit(String key, Object value) throws IOException { + root().createRequest().with(key, value).method("PATCH").withUrlPath(getApiRoute()).send(); } private String getApiRoute() { @@ -156,20 +172,4 @@ GHAsset wrap(GHRelease release) { this.owner = release.getOwner(); return this; } - - /** - * Wrap gh asset [ ]. - * - * @param assets - * the assets - * @param release - * the release - * @return the gh asset [ ] - */ - public static GHAsset[] wrap(GHAsset[] assets, GHRelease release) { - for (GHAsset aTo : assets) { - aTo.wrap(release); - } - return assets; - } } diff --git a/src/main/java/org/kohsuke/github/GHAuthenticatedAppInstallation.java b/src/main/java/org/kohsuke/github/GHAuthenticatedAppInstallation.java index 7c3fc8257b..73d55ba4c1 100644 --- a/src/main/java/org/kohsuke/github/GHAuthenticatedAppInstallation.java +++ b/src/main/java/org/kohsuke/github/GHAuthenticatedAppInstallation.java @@ -10,6 +10,15 @@ */ public class GHAuthenticatedAppInstallation extends GitHubInteractiveObject { + private static class GHAuthenticatedAppInstallationRepositoryResult extends SearchResult { + private GHRepository[] repositories; + + @Override + GHRepository[] getItems(GitHub root) { + return repositories; + } + } + /** * Instantiates a new GH authenticated app installation. * @@ -33,13 +42,4 @@ public PagedSearchIterable listRepositories() { return new PagedSearchIterable<>(root(), request, GHAuthenticatedAppInstallationRepositoryResult.class); } - private static class GHAuthenticatedAppInstallationRepositoryResult extends SearchResult { - private GHRepository[] repositories; - - @Override - GHRepository[] getItems(GitHub root) { - return repositories; - } - } - } diff --git a/src/main/java/org/kohsuke/github/GHAuthorization.java b/src/main/java/org/kohsuke/github/GHAuthorization.java index 1dd22edbeb..9768de3063 100644 --- a/src/main/java/org/kohsuke/github/GHAuthorization.java +++ b/src/main/java/org/kohsuke/github/GHAuthorization.java @@ -17,129 +17,119 @@ */ public class GHAuthorization extends GHObject { - /** - * Create default GHAuthorization instance - */ - public GHAuthorization() { + @SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD" }, + justification = "JSON API") + private static class App { + private String name; + // private String client_id; not yet used + private String url; } - /** The Constant USER. */ - public static final String USER = "user"; - - /** The Constant USER_EMAIL. */ - public static final String USER_EMAIL = "user:email"; - - /** The Constant USER_FOLLOW. */ - public static final String USER_FOLLOW = "user:follow"; - - /** The Constant PUBLIC_REPO. */ - public static final String PUBLIC_REPO = "public_repo"; + /** The Constant ADMIN_KEY. */ + public static final String ADMIN_KEY = "admin:public_key"; - /** The Constant REPO. */ - public static final String REPO = "repo"; + /** The Constant ADMIN_ORG. */ + public static final String ADMIN_ORG = "admin:org"; - /** The Constant REPO_STATUS. */ - public static final String REPO_STATUS = "repo:status"; + /** The Constant AMIN_HOOK. */ + public static final String AMIN_HOOK = "admin:repo_hook"; /** The Constant DELETE_REPO. */ public static final String DELETE_REPO = "delete_repo"; + /** The Constant GIST. */ + public static final String GIST = "gist"; + /** The Constant NOTIFICATIONS. */ public static final String NOTIFICATIONS = "notifications"; - /** The Constant GIST. */ - public static final String GIST = "gist"; + /** The Constant PUBLIC_REPO. */ + public static final String PUBLIC_REPO = "public_repo"; /** The Constant READ_HOOK. */ public static final String READ_HOOK = "read:repo_hook"; - /** The Constant WRITE_HOOK. */ - public static final String WRITE_HOOK = "write:repo_hook"; - - /** The Constant AMIN_HOOK. */ - public static final String AMIN_HOOK = "admin:repo_hook"; + /** The Constant READ_KEY. */ + public static final String READ_KEY = "read:public_key"; /** The Constant READ_ORG. */ public static final String READ_ORG = "read:org"; - /** The Constant WRITE_ORG. */ - public static final String WRITE_ORG = "write:org"; + /** The Constant REPO. */ + public static final String REPO = "repo"; - /** The Constant ADMIN_ORG. */ - public static final String ADMIN_ORG = "admin:org"; + /** The Constant REPO_STATUS. */ + public static final String REPO_STATUS = "repo:status"; - /** The Constant READ_KEY. */ - public static final String READ_KEY = "read:public_key"; + /** The Constant USER. */ + public static final String USER = "user"; + + /** The Constant USER_EMAIL. */ + public static final String USER_EMAIL = "user:email"; + + /** The Constant USER_FOLLOW. */ + public static final String USER_FOLLOW = "user:follow"; + + /** The Constant WRITE_HOOK. */ + public static final String WRITE_HOOK = "write:repo_hook"; /** The Constant WRITE_KEY. */ public static final String WRITE_KEY = "write:public_key"; - /** The Constant ADMIN_KEY. */ - public static final String ADMIN_KEY = "admin:public_key"; + /** The Constant WRITE_ORG. */ + public static final String WRITE_ORG = "write:org"; - private List scopes; - private String token; - private String token_last_eight; - private String hashed_token; private App app; - private String note; - private String note_url; private String fingerprint; // TODO add some user class for https://developer.github.com/v3/oauth_authorizations/#check-an-authorization ? // private GHUser user; + private String hashedToken; + private String note; + private String noteUrl; + private List scopes; + private String token; + private String tokenLastEight; /** - * Gets scopes. - * - * @return the scopes - */ - public List getScopes() { - return Collections.unmodifiableList(scopes); - } - - /** - * Gets token. - * - * @return the token + * Create default GHAuthorization instance */ - public String getToken() { - return token; + public GHAuthorization() { } /** - * Gets token last eight. + * Gets app name. * - * @return the token last eight + * @return the app name */ - public String getTokenLastEight() { - return token_last_eight; + public String getAppName() { + return app.name; } /** - * Gets hashed token. + * Gets app url. * - * @return the hashed token + * @return the app url */ - public String getHashedToken() { - return hashed_token; + public URL getAppUrl() { + return GitHubClient.parseURL(app.url); } /** - * Gets app url. + * Gets fingerprint. * - * @return the app url + * @return the fingerprint */ - public URL getAppUrl() { - return GitHubClient.parseURL(app.url); + public String getFingerprint() { + return fingerprint; } /** - * Gets app name. + * Gets hashed token. * - * @return the app name + * @return the hashed token */ - public String getAppName() { - return app.name; + public String getHashedToken() { + return hashedToken; } /** @@ -157,23 +147,33 @@ public String getNote() { * @return the note url */ public URL getNoteUrl() { - return GitHubClient.parseURL(note_url); + return GitHubClient.parseURL(noteUrl); } /** - * Gets fingerprint. + * Gets scopes. * - * @return the fingerprint + * @return the scopes */ - public String getFingerprint() { - return fingerprint; + public List getScopes() { + return Collections.unmodifiableList(scopes); } - @SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD" }, - justification = "JSON API") - private static class App { - private String url; - private String name; - // private String client_id; not yet used + /** + * Gets token. + * + * @return the token + */ + public String getToken() { + return token; + } + + /** + * Gets token last eight. + * + * @return the token last eight + */ + public String getTokenLastEight() { + return tokenLastEight; } } diff --git a/src/main/java/org/kohsuke/github/GHAutolink.java b/src/main/java/org/kohsuke/github/GHAutolink.java index 841f1d0f65..9fe9c6a791 100644 --- a/src/main/java/org/kohsuke/github/GHAutolink.java +++ b/src/main/java/org/kohsuke/github/GHAutolink.java @@ -15,10 +15,10 @@ public class GHAutolink { private int id; - private String key_prefix; - private String url_template; - private boolean is_alphanumeric; + private boolean isAlphanumeric; + private String keyPrefix; private GHRepository owner; + private String urlTemplate; /** * Instantiates a new Gh autolink. @@ -26,6 +26,20 @@ public class GHAutolink { public GHAutolink() { } + /** + * Deletes this autolink + * + * @throws IOException + * if the deletion fails + */ + public void delete() throws IOException { + owner.root() + .createRequest() + .method("DELETE") + .withUrlPath(String.format("/repos/%s/%s/autolinks/%d", owner.getOwnerName(), owner.getName(), getId())) + .send(); + } + /** * Gets the autolink ID * @@ -41,7 +55,17 @@ public int getId() { * @return the key prefix string */ public String getKeyPrefix() { - return key_prefix; + return keyPrefix; + } + + /** + * Gets the repository that owns this autolink + * + * @return the repository instance + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHRepository getOwner() { + return owner; } /** @@ -50,7 +74,7 @@ public String getKeyPrefix() { * @return the URL template string */ public String getUrlTemplate() { - return url_template; + return urlTemplate; } /** @@ -59,31 +83,7 @@ public String getUrlTemplate() { * @return true if alphanumeric, false otherwise */ public boolean isAlphanumeric() { - return is_alphanumeric; - } - - /** - * Gets the repository that owns this autolink - * - * @return the repository instance - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHRepository getOwner() { - return owner; - } - - /** - * Deletes this autolink - * - * @throws IOException - * if the deletion fails - */ - public void delete() throws IOException { - owner.root() - .createRequest() - .method("DELETE") - .withUrlPath(String.format("/repos/%s/%s/autolinks/%d", owner.getOwnerName(), owner.getName(), getId())) - .send(); + return isAlphanumeric; } /** diff --git a/src/main/java/org/kohsuke/github/GHAutolinkBuilder.java b/src/main/java/org/kohsuke/github/GHAutolinkBuilder.java index 3082d9487d..c5726ced6e 100644 --- a/src/main/java/org/kohsuke/github/GHAutolinkBuilder.java +++ b/src/main/java/org/kohsuke/github/GHAutolinkBuilder.java @@ -11,11 +11,11 @@ */ public class GHAutolinkBuilder { + private Boolean isAlphanumeric; + private String keyPrefix; private final GHRepository repo; private final Requester req; - private String keyPrefix; private String urlTemplate; - private Boolean isAlphanumeric; /** * Instantiates a new Gh autolink builder. @@ -28,6 +28,37 @@ public class GHAutolinkBuilder { req = repo.root().createRequest(); } + /** + * Create gh autolink. + * + * @return the gh autolink + * @throws IOException + * the io exception + */ + public GHAutolink create() throws IOException { + GHAutolink autolink = req.method("POST") + .with("key_prefix", keyPrefix) + .with("url_template", urlTemplate) + .with("is_alphanumeric", isAlphanumeric) + .withHeader("Accept", "application/vnd.github+json") + .withUrlPath(getApiTail()) + .fetch(GHAutolink.class); + + return autolink.lateBind(repo); + } + + /** + * With is alphanumeric gh autolink builder. + * + * @param isAlphanumeric + * the is alphanumeric + * @return the gh autolink builder + */ + public GHAutolinkBuilder withIsAlphanumeric(boolean isAlphanumeric) { + this.isAlphanumeric = isAlphanumeric; + return this; + } + /** * With key prefix gh autolink builder. * @@ -52,39 +83,8 @@ public GHAutolinkBuilder withUrlTemplate(String urlTemplate) { return this; } - /** - * With is alphanumeric gh autolink builder. - * - * @param isAlphanumeric - * the is alphanumeric - * @return the gh autolink builder - */ - public GHAutolinkBuilder withIsAlphanumeric(boolean isAlphanumeric) { - this.isAlphanumeric = isAlphanumeric; - return this; - } - private String getApiTail() { return String.format("/repos/%s/%s/autolinks", repo.getOwnerName(), repo.getName()); } - /** - * Create gh autolink. - * - * @return the gh autolink - * @throws IOException - * the io exception - */ - public GHAutolink create() throws IOException { - GHAutolink autolink = req.method("POST") - .with("key_prefix", keyPrefix) - .with("url_template", urlTemplate) - .with("is_alphanumeric", isAlphanumeric) - .withHeader("Accept", "application/vnd.github+json") - .withUrlPath(getApiTail()) - .fetch(GHAutolink.class); - - return autolink.lateBind(repo); - } - } diff --git a/src/main/java/org/kohsuke/github/GHBlob.java b/src/main/java/org/kohsuke/github/GHBlob.java index 2fc168ec69..31c83b6ff4 100644 --- a/src/main/java/org/kohsuke/github/GHBlob.java +++ b/src/main/java/org/kohsuke/github/GHBlob.java @@ -17,22 +17,31 @@ */ public class GHBlob { + private String content, encoding, url, sha; + + private long size; /** * Create default GHBlob instance */ public GHBlob() { } - private String content, encoding, url, sha; - private long size; + /** + * Gets content. + * + * @return Encoded content. You probably want {@link #read()} + */ + public String getContent() { + return content; + } /** - * Gets url. + * Gets encoding. * - * @return API URL of this blob. + * @return the encoding */ - public URL getUrl() { - return GitHubClient.parseURL(url); + public String getEncoding() { + return encoding; } /** @@ -54,21 +63,12 @@ public long getSize() { } /** - * Gets encoding. - * - * @return the encoding - */ - public String getEncoding() { - return encoding; - } - - /** - * Gets content. + * Gets url. * - * @return Encoded content. You probably want {@link #read()} + * @return API URL of this blob. */ - public String getContent() { - return content; + public URL getUrl() { + return GitHubClient.parseURL(url); } /** diff --git a/src/main/java/org/kohsuke/github/GHBlobBuilder.java b/src/main/java/org/kohsuke/github/GHBlobBuilder.java index 187867689b..237768e503 100644 --- a/src/main/java/org/kohsuke/github/GHBlobBuilder.java +++ b/src/main/java/org/kohsuke/github/GHBlobBuilder.java @@ -22,19 +22,6 @@ public class GHBlobBuilder { req = repo.root().createRequest(); } - /** - * Configures a blob with the specified text {@code content}. - * - * @param content - * string text of the blob - * @return a GHBlobBuilder - */ - public GHBlobBuilder textContent(String content) { - req.with("content", content); - req.with("encoding", "utf-8"); - return this; - } - /** * Configures a blob with the specified binary {@code content}. * @@ -49,10 +36,6 @@ public GHBlobBuilder binaryContent(byte[] content) { return this; } - private String getApiTail() { - return String.format("/repos/%s/%s/git/blobs", repo.getOwnerName(), repo.getName()); - } - /** * Creates a blob based on the parameters specified thus far. * @@ -63,4 +46,21 @@ private String getApiTail() { public GHBlob create() throws IOException { return req.method("POST").withUrlPath(getApiTail()).fetch(GHBlob.class); } + + /** + * Configures a blob with the specified text {@code content}. + * + * @param content + * string text of the blob + * @return a GHBlobBuilder + */ + public GHBlobBuilder textContent(String content) { + req.with("content", content); + req.with("encoding", "utf-8"); + return this; + } + + private String getApiTail() { + return String.format("/repos/%s/%s/git/blobs", repo.getOwnerName(), repo.getName()); + } } diff --git a/src/main/java/org/kohsuke/github/GHBranch.java b/src/main/java/org/kohsuke/github/GHBranch.java index 99335c1225..c18bd23aa7 100644 --- a/src/main/java/org/kohsuke/github/GHBranch.java +++ b/src/main/java/org/kohsuke/github/GHBranch.java @@ -21,13 +21,32 @@ "URF_UNREAD_FIELD" }, justification = "JSON API") public class GHBranch extends GitHubInteractiveObject { - private GHRepository owner; + /** + * The type Commit. + */ + public static class Commit { + + /** The sha. */ + String sha; + + /** The url. */ + @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") + String url; + + /** + * Create default Commit instance + */ + public Commit() { + } + } - private String name; private Commit commit; + private String name; + private GHRepository owner; @JsonProperty("protected") private boolean protection; - private String protection_url; + + private String protectionUrl; /** * Instantiates a new GH branch. @@ -42,32 +61,23 @@ public class GHBranch extends GitHubInteractiveObject { } /** - * The type Commit. + * Disables branch protection and allows anyone with push access to push changes. + * + * @throws IOException + * if disabling protection fails */ - public static class Commit { - - /** - * Create default Commit instance - */ - public Commit() { - } - - /** The sha. */ - String sha; - - /** The url. */ - @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") - String url; + public void disableProtection() throws IOException { + root().createRequest().method("DELETE").setRawUrlPath(protectionUrl).send(); } /** - * Gets owner. + * Enables branch protection to control what commit statuses are required to push. * - * @return the repository that this branch is in. + * @return GHBranchProtectionBuilder for enabling protection + * @see GHCommitStatus#getContext() GHCommitStatus#getContext() */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHRepository getOwner() { - return owner; + public GHBranchProtectionBuilder enableProtection() { + return new GHBranchProtectionBuilder(this); } /** @@ -80,21 +90,13 @@ public String getName() { } /** - * Is protected boolean. - * - * @return true if the push to this branch is restricted via branch protection. - */ - public boolean isProtected() { - return protection; - } - - /** - * Gets protection url. + * Gets owner. * - * @return API URL that deals with the protection of this branch. + * @return the repository that this branch is in. */ - public URL getProtectionUrl() { - return GitHubClient.parseURL(protection_url); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHRepository getOwner() { + return owner; } /** @@ -105,36 +107,34 @@ public URL getProtectionUrl() { * the io exception */ public GHBranchProtection getProtection() throws IOException { - return root().createRequest().setRawUrlPath(protection_url).fetch(GHBranchProtection.class); + return root().createRequest().setRawUrlPath(protectionUrl).fetch(GHBranchProtection.class); } /** - * Gets sha 1. + * Gets protection url. * - * @return The SHA1 of the commit that this branch currently points to. + * @return API URL that deals with the protection of this branch. */ - public String getSHA1() { - return commit.sha; + public URL getProtectionUrl() { + return GitHubClient.parseURL(protectionUrl); } /** - * Disables branch protection and allows anyone with push access to push changes. + * Gets sha 1. * - * @throws IOException - * if disabling protection fails + * @return The SHA1 of the commit that this branch currently points to. */ - public void disableProtection() throws IOException { - root().createRequest().method("DELETE").setRawUrlPath(protection_url).send(); + public String getSHA1() { + return commit.sha; } /** - * Enables branch protection to control what commit statuses are required to push. + * Is protected boolean. * - * @return GHBranchProtectionBuilder for enabling protection - * @see GHCommitStatus#getContext() GHCommitStatus#getContext() + * @return true if the push to this branch is restricted via branch protection. */ - public GHBranchProtectionBuilder enableProtection() { - return new GHBranchProtectionBuilder(this); + public boolean isProtected() { + return protection; } /** @@ -190,15 +190,6 @@ public GHCommit merge(String head, String commitMessage) throws IOException { return result; } - /** - * Gets the api route. - * - * @return the api route - */ - String getApiRoute() { - return owner.getApiTailUrl("/branches/" + name); - } - /** * To string. * @@ -210,6 +201,15 @@ public String toString() { return "Branch:" + name + " in " + url; } + /** + * Gets the api route. + * + * @return the api route + */ + String getApiRoute() { + return owner.getApiTailUrl("/branches/" + name); + } + /** * Wrap. * diff --git a/src/main/java/org/kohsuke/github/GHBranchProtection.java b/src/main/java/org/kohsuke/github/GHBranchProtection.java index 8fbdc0d232..f5d661459c 100644 --- a/src/main/java/org/kohsuke/github/GHBranchProtection.java +++ b/src/main/java/org/kohsuke/github/GHBranchProtection.java @@ -20,207 +20,20 @@ justification = "JSON API") public class GHBranchProtection extends GitHubInteractiveObject { - /** - * Create default GHBranchProtection instance - */ - public GHBranchProtection() { - } - - private static final String REQUIRE_SIGNATURES_URI = "/required_signatures"; - - @JsonProperty - private AllowDeletions allowDeletions; - - @JsonProperty - private AllowForcePushes allowForcePushes; - - @JsonProperty - private AllowForkSyncing allowForkSyncing; - - @JsonProperty - private BlockCreations blockCreations; - - @JsonProperty - private EnforceAdmins enforceAdmins; - - @JsonProperty - private LockBranch lockBranch; - - @JsonProperty - private RequiredConversationResolution requiredConversationResolution; - - @JsonProperty - private RequiredLinearHistory requiredLinearHistory; - - @JsonProperty("required_pull_request_reviews") - private RequiredReviews requiredReviews; - - @JsonProperty - private RequiredStatusChecks requiredStatusChecks; - - @JsonProperty - private Restrictions restrictions; - - @JsonProperty - private String url; - - /** - * Enabled signed commits. - * - * @throws IOException - * the io exception - */ - public void enabledSignedCommits() throws IOException { - requester().method("POST").withUrlPath(url + REQUIRE_SIGNATURES_URI).fetch(RequiredSignatures.class); - } - - /** - * Disable signed commits. - * - * @throws IOException - * the io exception - */ - public void disableSignedCommits() throws IOException { - requester().method("DELETE").withUrlPath(url + REQUIRE_SIGNATURES_URI).send(); - } - - /** - * Gets allow deletions. - * - * @return the enforce admins - */ - public AllowDeletions getAllowDeletions() { - return allowDeletions; - } - - /** - * Gets allow force pushes. - * - * @return the enforce admins - */ - public AllowForcePushes getAllowForcePushes() { - return allowForcePushes; - } - - /** - * Gets allow fork syncing. - * - * @return the enforce admins - */ - public AllowForkSyncing getAllowForkSyncing() { - return allowForkSyncing; - } - - /** - * Gets block creations. - * - * @return the enforce admins - */ - public BlockCreations getBlockCreations() { - return blockCreations; - } - - /** - * Gets enforce admins. - * - * @return the enforce admins - */ - public EnforceAdmins getEnforceAdmins() { - return enforceAdmins; - } - - /** - * Gets lock branch. - * - * @return the enforce admins - */ - public LockBranch getLockBranch() { - return lockBranch; - } - - /** - * Gets required conversation resolution. - * - * @return the enforce admins - */ - public RequiredConversationResolution getRequiredConversationResolution() { - return requiredConversationResolution; - } - - /** - * Gets required linear history. - * - * @return the enforce admins - */ - public RequiredLinearHistory getRequiredLinearHistory() { - return requiredLinearHistory; - } - - /** - * Gets required reviews. - * - * @return the required reviews - */ - public RequiredReviews getRequiredReviews() { - return requiredReviews; - } - - /** - * Gets required signatures. - * - * @return the required signatures - * @throws IOException - * the io exception - */ - public boolean getRequiredSignatures() throws IOException { - return requester().withUrlPath(url + REQUIRE_SIGNATURES_URI).fetch(RequiredSignatures.class).enabled; - } - - /** - * Gets required status checks. - * - * @return the required status checks - */ - public RequiredStatusChecks getRequiredStatusChecks() { - return requiredStatusChecks; - } - - /** - * Gets restrictions. - * - * @return the restrictions - */ - public Restrictions getRestrictions() { - return restrictions; - } - - /** - * Gets url. - * - * @return the url - */ - public String getUrl() { - return url; - } - - private Requester requester() { - return root().createRequest(); - } - /** * The type AllowDeletions. */ public static class AllowDeletions { + @JsonProperty + private boolean enabled; + /** * Create default AllowDeletions instance */ public AllowDeletions() { } - @JsonProperty - private boolean enabled; - /** * Is enabled boolean. * @@ -231,69 +44,20 @@ public boolean isEnabled() { } } - /** - * The type Check. - */ - public static class Check { - private String context; - - @JsonInclude(JsonInclude.Include.NON_NULL) - private Integer appId; - - /** - * no-arg constructor for the serializer - */ - public Check() { - } - - /** - * Regular constructor for use in user business logic - * - * @param context - * the context string of the check - * @param appId - * the application ID the check is supposed to come from. Pass "-1" to explicitly allow any app to - * set the status. Pass "null" to automatically select the GitHub App that has recently provided this - * check. - */ - public Check(String context, Integer appId) { - this.context = context; - this.appId = appId; - } - - /** - * The context string of the check - * - * @return the string - */ - public String getContext() { - return context; - } - - /** - * The application ID the check is supposed to come from. The value "-1" indicates "any source". - * - * @return the integer - */ - public Integer getAppId() { - return appId; - } - } - /** * The type AllowForcePushes. */ public static class AllowForcePushes { + @JsonProperty + private boolean enabled; + /** * Create default AllowForcePushes instance */ public AllowForcePushes() { } - @JsonProperty - private boolean enabled; - /** * Is enabled boolean. * @@ -309,15 +73,15 @@ public boolean isEnabled() { */ public static class AllowForkSyncing { + @JsonProperty + private boolean enabled; + /** * Create default AllowForkSyncing instance */ public AllowForkSyncing() { } - @JsonProperty - private boolean enabled; - /** * Is enabled boolean. * @@ -333,15 +97,15 @@ public boolean isEnabled() { */ public static class BlockCreations { + @JsonProperty + private boolean enabled; + /** * Create default BlockCreations instance */ public BlockCreations() { } - @JsonProperty - private boolean enabled; - /** * Is enabled boolean. * @@ -353,15 +117,58 @@ public boolean isEnabled() { } /** - * The type EnforceAdmins. + * The type Check. */ - public static class EnforceAdmins { + public static class Check { + @JsonInclude(JsonInclude.Include.NON_NULL) + private Integer appId; + + private String context; /** - * Create default EnforceAdmins instance + * no-arg constructor for the serializer */ - public EnforceAdmins() { + public Check() { + } + + /** + * Regular constructor for use in user business logic + * + * @param context + * the context string of the check + * @param appId + * the application ID the check is supposed to come from. Pass "-1" to explicitly allow any app to + * set the status. Pass "null" to automatically select the GitHub App that has recently provided this + * check. + */ + public Check(String context, Integer appId) { + this.context = context; + this.appId = appId; + } + + /** + * The application ID the check is supposed to come from. The value "-1" indicates "any source". + * + * @return the integer + */ + public Integer getAppId() { + return appId; + } + + /** + * The context string of the check + * + * @return the string + */ + public String getContext() { + return context; } + } + + /** + * The type EnforceAdmins. + */ + public static class EnforceAdmins { @JsonProperty private boolean enabled; @@ -369,6 +176,12 @@ public EnforceAdmins() { @JsonProperty private String url; + /** + * Create default EnforceAdmins instance + */ + public EnforceAdmins() { + } + /** * Gets url. * @@ -393,15 +206,15 @@ public boolean isEnabled() { */ public static class LockBranch { + @JsonProperty + private boolean enabled; + /** * Create default LockBranch instance */ public LockBranch() { } - @JsonProperty - private boolean enabled; - /** * Is enabled boolean. * @@ -417,15 +230,15 @@ public boolean isEnabled() { */ public static class RequiredConversationResolution { + @JsonProperty + private boolean enabled; + /** * Create default RequiredConversationResolution instance */ public RequiredConversationResolution() { } - @JsonProperty - private boolean enabled; - /** * Is enabled boolean. * @@ -441,15 +254,15 @@ public boolean isEnabled() { */ public static class RequiredLinearHistory { + @JsonProperty + private boolean enabled; + /** * Create default RequiredLinearHistory instance */ public RequiredLinearHistory() { } - @JsonProperty - private boolean enabled; - /** * Is enabled boolean. * @@ -465,18 +278,12 @@ public boolean isEnabled() { */ public static class RequiredReviews { - /** - * Create default RequiredReviews instance - */ - public RequiredReviews() { - } + @JsonProperty + private boolean dismissStaleReviews; @JsonProperty("dismissal_restrictions") private Restrictions dismissalRestriction; - @JsonProperty - private boolean dismissStaleReviews; - @JsonProperty private boolean requireCodeOwnerReviews; @@ -489,6 +296,12 @@ public RequiredReviews() { @JsonProperty private String url; + /** + * Create default RequiredReviews instance + */ + public RequiredReviews() { + } + /** * Gets dismissal restrictions. * @@ -498,6 +311,15 @@ public Restrictions getDismissalRestrictions() { return dismissalRestriction; } + /** + * Gets required reviewers. + * + * @return the required reviewers + */ + public int getRequiredReviewers() { + return requiredReviewers; + } + /** * Gets url. * @@ -526,47 +348,12 @@ public boolean isRequireCodeOwnerReviews() { } /** - * Is require last push approval boolean. - * - * @return the boolean - */ - public boolean isRequireLastPushApproval() { - return requireLastPushApproval; - } - - /** - * Gets required reviewers. - * - * @return the required reviewers - */ - public int getRequiredReviewers() { - return requiredReviewers; - } - } - - private static class RequiredSignatures { - @JsonProperty - private boolean enabled; - - @JsonProperty - private String url; - - /** - * Gets url. - * - * @return the url - */ - public String getUrl() { - return url; - } - - /** - * Is enabled boolean. + * Is require last push approval boolean. * * @return the boolean */ - public boolean isEnabled() { - return enabled; + public boolean isRequireLastPushApproval() { + return requireLastPushApproval; } } @@ -575,17 +362,11 @@ public boolean isEnabled() { */ public static class RequiredStatusChecks { - /** - * Create default RequiredStatusChecks instance - */ - public RequiredStatusChecks() { - } - @JsonProperty - private Collection contexts; + private Collection checks; @JsonProperty - private Collection checks; + private Collection contexts; @JsonProperty private boolean strict; @@ -594,12 +375,9 @@ public RequiredStatusChecks() { private String url; /** - * Gets contexts. - * - * @return the contexts + * Create default RequiredStatusChecks instance */ - public Collection getContexts() { - return Collections.unmodifiableCollection(contexts); + public RequiredStatusChecks() { } /** @@ -611,6 +389,15 @@ public Collection getChecks() { return Collections.unmodifiableCollection(checks); } + /** + * Gets contexts. + * + * @return the contexts + */ + public Collection getContexts() { + return Collections.unmodifiableCollection(contexts); + } + /** * Gets url. * @@ -635,12 +422,6 @@ public boolean isRequiresBranchUpToDate() { */ public static class Restrictions { - /** - * Create default Restrictions instance - */ - public Restrictions() { - } - @JsonProperty private Collection teams; @@ -654,6 +435,12 @@ public Restrictions() { private String usersUrl; + /** + * Create default Restrictions instance + */ + public Restrictions() { + } + /** * Gets teams. * @@ -699,4 +486,217 @@ public String getUsersUrl() { return usersUrl; } } + + private static class RequiredSignatures { + @JsonProperty + private boolean enabled; + + @JsonProperty + private String url; + + /** + * Gets url. + * + * @return the url + */ + public String getUrl() { + return url; + } + + /** + * Is enabled boolean. + * + * @return the boolean + */ + public boolean isEnabled() { + return enabled; + } + } + + private static final String REQUIRE_SIGNATURES_URI = "/required_signatures"; + + @JsonProperty + private AllowDeletions allowDeletions; + + @JsonProperty + private AllowForcePushes allowForcePushes; + + @JsonProperty + private AllowForkSyncing allowForkSyncing; + + @JsonProperty + private BlockCreations blockCreations; + + @JsonProperty + private EnforceAdmins enforceAdmins; + + @JsonProperty + private LockBranch lockBranch; + + @JsonProperty + private RequiredConversationResolution requiredConversationResolution; + + @JsonProperty + private RequiredLinearHistory requiredLinearHistory; + + @JsonProperty("required_pull_request_reviews") + private RequiredReviews requiredReviews; + + @JsonProperty + private RequiredStatusChecks requiredStatusChecks; + + @JsonProperty + private Restrictions restrictions; + + @JsonProperty + private String url; + + /** + * Create default GHBranchProtection instance + */ + public GHBranchProtection() { + } + + /** + * Disable signed commits. + * + * @throws IOException + * the io exception + */ + public void disableSignedCommits() throws IOException { + requester().method("DELETE").withUrlPath(url + REQUIRE_SIGNATURES_URI).send(); + } + + /** + * Enabled signed commits. + * + * @throws IOException + * the io exception + */ + public void enabledSignedCommits() throws IOException { + requester().method("POST").withUrlPath(url + REQUIRE_SIGNATURES_URI).fetch(RequiredSignatures.class); + } + + /** + * Gets allow deletions. + * + * @return the enforce admins + */ + public AllowDeletions getAllowDeletions() { + return allowDeletions; + } + + /** + * Gets allow force pushes. + * + * @return the enforce admins + */ + public AllowForcePushes getAllowForcePushes() { + return allowForcePushes; + } + + /** + * Gets allow fork syncing. + * + * @return the enforce admins + */ + public AllowForkSyncing getAllowForkSyncing() { + return allowForkSyncing; + } + + /** + * Gets block creations. + * + * @return the enforce admins + */ + public BlockCreations getBlockCreations() { + return blockCreations; + } + + /** + * Gets enforce admins. + * + * @return the enforce admins + */ + public EnforceAdmins getEnforceAdmins() { + return enforceAdmins; + } + + /** + * Gets lock branch. + * + * @return the enforce admins + */ + public LockBranch getLockBranch() { + return lockBranch; + } + + /** + * Gets required conversation resolution. + * + * @return the enforce admins + */ + public RequiredConversationResolution getRequiredConversationResolution() { + return requiredConversationResolution; + } + + /** + * Gets required linear history. + * + * @return the enforce admins + */ + public RequiredLinearHistory getRequiredLinearHistory() { + return requiredLinearHistory; + } + + /** + * Gets required reviews. + * + * @return the required reviews + */ + public RequiredReviews getRequiredReviews() { + return requiredReviews; + } + + /** + * Gets required signatures. + * + * @return the required signatures + * @throws IOException + * the io exception + */ + public boolean getRequiredSignatures() throws IOException { + return requester().withUrlPath(url + REQUIRE_SIGNATURES_URI).fetch(RequiredSignatures.class).enabled; + } + + /** + * Gets required status checks. + * + * @return the required status checks + */ + public RequiredStatusChecks getRequiredStatusChecks() { + return requiredStatusChecks; + } + + /** + * Gets restrictions. + * + * @return the restrictions + */ + public Restrictions getRestrictions() { + return restrictions; + } + + /** + * Gets url. + * + * @return the url + */ + public String getUrl() { + return url; + } + + private Requester requester() { + return root().createRequest(); + } } diff --git a/src/main/java/org/kohsuke/github/GHBranchProtectionBuilder.java b/src/main/java/org/kohsuke/github/GHBranchProtectionBuilder.java index c56e7f2197..5b1521d9f1 100644 --- a/src/main/java/org/kohsuke/github/GHBranchProtectionBuilder.java +++ b/src/main/java/org/kohsuke/github/GHBranchProtectionBuilder.java @@ -23,11 +23,21 @@ "URF_UNREAD_FIELD" }, justification = "JSON API") public class GHBranchProtectionBuilder { - private final GHBranch branch; + private static class Restrictions { + private Set teams = new HashSet(); + private Set users = new HashSet(); + } + private static class StatusChecks { + final List checks = new ArrayList<>(); + boolean strict; + } + private final GHBranch branch; private Map fields = new HashMap(); private Map prReviews; + private Restrictions restrictions; + private StatusChecks statusChecks; /** @@ -48,8 +58,8 @@ public class GHBranchProtectionBuilder { * the checks * @return the gh branch protection builder */ - public GHBranchProtectionBuilder addRequiredStatusChecks(Collection checks) { - getStatusChecks().checks.addAll(checks); + public GHBranchProtectionBuilder addRequiredChecks(GHBranchProtection.Check... checks) { + addRequiredStatusChecks(Arrays.asList(checks)); return this; } @@ -60,8 +70,8 @@ public GHBranchProtectionBuilder addRequiredStatusChecks(Collection checks) { + getStatusChecks().checks.addAll(checks); return this; } @@ -235,18 +245,6 @@ public GHBranchProtectionBuilder lockBranch(boolean v) { return this; } - /** - * Required reviewers gh branch protection builder. - * - * @param v - * the v - * @return the gh branch protection builder - */ - public GHBranchProtectionBuilder requiredReviewers(int v) { - getPrReviews().put("required_approving_review_count", v); - return this; - } - /** * Require branch is up to date gh branch protection builder. * @@ -310,6 +308,16 @@ public GHBranchProtectionBuilder requireLastPushApproval(boolean v) { return this; } + /** + * Require reviews gh branch protection builder. + * + * @return the gh branch protection builder + */ + public GHBranchProtectionBuilder requireReviews() { + getPrReviews(); + return this; + } + /** * Require all conversations on code to be resolved before a pull request can be merged into a branch that matches * this rule. @@ -357,12 +365,24 @@ public GHBranchProtectionBuilder requiredLinearHistory(boolean v) { } /** - * Require reviews gh branch protection builder. + * Required reviewers gh branch protection builder. * + * @param v + * the v * @return the gh branch protection builder */ - public GHBranchProtectionBuilder requireReviews() { - getPrReviews(); + public GHBranchProtectionBuilder requiredReviewers(int v) { + getPrReviews().put("required_approving_review_count", v); + return this; + } + + /** + * Restrict push access gh branch protection builder. + * + * @return the gh branch protection builder + */ + public GHBranchProtectionBuilder restrictPushAccess() { + getRestrictions(); return this; } @@ -381,16 +401,6 @@ public GHBranchProtectionBuilder restrictReviewDismissals() { return this; } - /** - * Restrict push access gh branch protection builder. - * - * @return the gh branch protection builder - */ - public GHBranchProtectionBuilder restrictPushAccess() { - getRestrictions(); - return this; - } - /** * Team push access gh branch protection builder. * @@ -538,14 +548,4 @@ private StatusChecks getStatusChecks() { private Requester requester() { return branch.root().createRequest(); } - - private static class Restrictions { - private Set teams = new HashSet(); - private Set users = new HashSet(); - } - - private static class StatusChecks { - final List checks = new ArrayList<>(); - boolean strict; - } } diff --git a/src/main/java/org/kohsuke/github/GHBranchSync.java b/src/main/java/org/kohsuke/github/GHBranchSync.java index c6823abd51..47b1a34158 100644 --- a/src/main/java/org/kohsuke/github/GHBranchSync.java +++ b/src/main/java/org/kohsuke/github/GHBranchSync.java @@ -8,15 +8,14 @@ public class GHBranchSync extends GitHubInteractiveObject { /** - * Create default GHBranchSync instance + * The base branch. */ - public GHBranchSync() { - } + private String baseBranch; /** - * The Repository that this branch is in. + * The merge type. */ - private GHRepository owner; + private String mergeType; /** * The message. @@ -24,32 +23,23 @@ public GHBranchSync() { private String message; /** - * The merge type. - */ - private String mergeType; - - /** - * The base branch. + * The Repository that this branch is in. */ - private String baseBranch; + private GHRepository owner; /** - * Gets owner. - * - * @return the repository that this branch is in. + * Create default GHBranchSync instance */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHRepository getOwner() { - return owner; + public GHBranchSync() { } /** - * Gets message. + * Gets base branch. * - * @return the message + * @return the base branch */ - public String getMessage() { - return message; + public String getBaseBranch() { + return baseBranch; } /** @@ -62,12 +52,22 @@ public String getMergeType() { } /** - * Gets base branch. + * Gets message. * - * @return the base branch + * @return the message */ - public String getBaseBranch() { - return baseBranch; + public String getMessage() { + return message; + } + + /** + * Gets owner. + * + * @return the repository that this branch is in. + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHRepository getOwner() { + return owner; } /** diff --git a/src/main/java/org/kohsuke/github/GHCheckRun.java b/src/main/java/org/kohsuke/github/GHCheckRun.java index cb12173ae4..2c776af8d7 100644 --- a/src/main/java/org/kohsuke/github/GHCheckRun.java +++ b/src/main/java/org/kohsuke/github/GHCheckRun.java @@ -1,12 +1,14 @@ package org.kohsuke.github; import com.fasterxml.jackson.annotation.JsonProperty; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.kohsuke.github.internal.EnumUtils; import java.io.IOException; import java.net.URL; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.Date; @@ -24,88 +26,42 @@ public class GHCheckRun extends GHObject { /** - * Create default GHCheckRun instance - */ - public GHCheckRun() { - } - - /** The owner. */ - @JsonProperty("repository") - GHRepository owner; - - private String status; - private String conclusion; - private String name; - private String headSha; - private String nodeId; - private String externalId; - private String startedAt; - private String completedAt; - private String htmlUrl; - private String detailsUrl; - private Output output; - private GHApp app; - private GHPullRequest[] pullRequests = new GHPullRequest[0]; - private GHCheckSuite checkSuite; - - /** - * Wrap. - * - * @param owner - * the owner - * @return the GH check run - */ - GHCheckRun wrap(GHRepository owner) { - this.owner = owner; - wrap(owner.root()); - return this; - } - - /** - * Wrap. - * - * @param root - * the root - * @return the GH check run + * The Enum AnnotationLevel. */ - GHCheckRun wrap(GitHub root) { - if (owner != null) { - for (GHPullRequest singlePull : pullRequests) { - singlePull.wrap(owner); - } - } - if (checkSuite != null) { - if (owner != null) { - checkSuite.wrap(owner); - } else { - checkSuite.wrap(root); - } - } + public static enum AnnotationLevel { - return this; + /** The failure. */ + FAILURE, + /** The notice. */ + NOTICE, + /** The warning. */ + WARNING } /** - * Gets status of the check run. + * Final conclusion of the check. * - * @return Status of the check run - * @see Status - */ - public Status getStatus() { - return Status.from(status); - } - - /** - * The Enum Status. + * From Check Run + * Parameters - conclusion. */ - public static enum Status { + public static enum Conclusion { - /** The queued. */ - QUEUED, - /** The in progress. */ - IN_PROGRESS, - /** The completed. */ - COMPLETED, + /** The action required. */ + ACTION_REQUIRED, + /** The cancelled. */ + CANCELLED, + /** The failure. */ + FAILURE, + /** The neutral. */ + NEUTRAL, + /** The skipped. */ + SKIPPED, + /** The stale. */ + STALE, + /** The success. */ + SUCCESS, + /** The timed out. */ + TIMED_OUT, /** The unknown. */ UNKNOWN; @@ -114,10 +70,10 @@ public static enum Status { * * @param value * the value - * @return the status + * @return the conclusion */ - public static Status from(String value) { - return EnumUtils.getNullableEnumOrDefault(Status.class, value, Status.UNKNOWN); + public static Conclusion from(String value) { + return EnumUtils.getNullableEnumOrDefault(Conclusion.class, value, Conclusion.UNKNOWN); } /** @@ -132,39 +88,80 @@ public String toString() { } /** - * Gets conclusion of a completed check run. + * Represents an output in a check run to include summary and other results. * - * @return Status of the check run - * @see Conclusion + * @see documentation */ - public Conclusion getConclusion() { - return Conclusion.from(conclusion); - } + public static class Output { + + private int annotationsCount; + private String annotationsUrl; + private String summary; + private String text; + private String title; + /** + * Create default Output instance + */ + public Output() { + } + + /** + * Gets the annotation count of a check run. + * + * @return annotation count of a check run + */ + public int getAnnotationsCount() { + return annotationsCount; + } + + /** + * Gets the URL of annotations. + * + * @return URL of annotations + */ + public URL getAnnotationsUrl() { + return GitHubClient.parseURL(annotationsUrl); + } + + /** + * Gets the summary of the check run, note that it supports Markdown. + * + * @return summary of check run + */ + public String getSummary() { + return summary; + } + + /** + * Gets the details of the check run, note that it supports Markdown. + * + * @return Details of the check run + */ + public String getText() { + return text; + } + + /** + * Gets the title of check run. + * + * @return title of check run + */ + public String getTitle() { + return title; + } + } /** - * Final conclusion of the check. - * - * From Check Run - * Parameters - conclusion. + * The Enum Status. */ - public static enum Conclusion { + public static enum Status { - /** The action required. */ - ACTION_REQUIRED, - /** The cancelled. */ - CANCELLED, - /** The failure. */ - FAILURE, - /** The neutral. */ - NEUTRAL, - /** The success. */ - SUCCESS, - /** The skipped. */ - SKIPPED, - /** The stale. */ - STALE, - /** The timed out. */ - TIMED_OUT, + /** The completed. */ + COMPLETED, + /** The in progress. */ + IN_PROGRESS, + /** The queued. */ + QUEUED, /** The unknown. */ UNKNOWN; @@ -173,10 +170,10 @@ public static enum Conclusion { * * @param value * the value - * @return the conclusion + * @return the status */ - public static Conclusion from(String value) { - return EnumUtils.getNullableEnumOrDefault(Conclusion.class, value, Conclusion.UNKNOWN); + public static Status from(String value) { + return EnumUtils.getNullableEnumOrDefault(Status.class, value, Status.UNKNOWN); } /** @@ -189,70 +186,71 @@ public String toString() { return name().toLowerCase(Locale.ROOT); } } + private GHApp app; + private GHCheckSuite checkSuite; + private String completedAt; + private String conclusion; + private String detailsUrl; + private String externalId; + private String headSha; + private String htmlUrl; + private String name; + private String nodeId; + private Output output; + private GHPullRequest[] pullRequests = new GHPullRequest[0]; - /** - * Gets the custom name of this check run. - * - * @return Name of the check run - */ - public String getName() { - return name; - } + private String startedAt; + + private String status; + + /** The owner. */ + @JsonProperty("repository") + GHRepository owner; /** - * Gets the HEAD SHA. - * - * @return sha for the HEAD commit + * Create default GHCheckRun instance */ - public String getHeadSha() { - return headSha; + public GHCheckRun() { } /** - * Gets the pull requests participated in this check run. - * - * Note this field is only populated for events. When getting a {@link GHCheckRun} outside of an event, this is - * always empty. + * Gets the GitHub app this check run belongs to, included in response. * - * @return the list of {@link GHPullRequest}s for this check run. Only populated for events. - * @throws IOException - * the io exception + * @return GitHub App */ - public List getPullRequests() throws IOException { - for (GHPullRequest singlePull : pullRequests) { - // Only refresh if we haven't do so before - singlePull.refresh(singlePull.getTitle()); - } - return Collections.unmodifiableList(Arrays.asList(pullRequests)); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHApp getApp() { + return app; } /** - * Gets the HTML URL: https://github.com/[owner]/[repo-name]/runs/[check-run-id], usually an GitHub Action page of - * the check run. + * Gets the check suite this check run belongs to. * - * @return HTML URL + * @return Check suite */ - public URL getHtmlUrl() { - return GitHubClient.parseURL(htmlUrl); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHCheckSuite getCheckSuite() { + return checkSuite; } /** - * Gets the global node id to access most objects in GitHub. + * Gets the completed time of the check run in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ. * - * @return Global node id - * @see documentation + * @return Timestamp of the completed time */ - public String getNodeId() { - return nodeId; + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getCompletedAt() { + return GitHubClient.parseInstant(completedAt); } /** - * Gets a reference for the check run on the integrator's system. + * Gets conclusion of a completed check run. * - * @return Reference id + * @return Status of the check run + * @see Conclusion */ - public String getExternalId() { - return externalId; + public Conclusion getConclusion() { + return Conclusion.from(conclusion); } /** @@ -265,41 +263,50 @@ public URL getDetailsUrl() { } /** - * Gets the start time of the check run in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ. + * Gets a reference for the check run on the integrator's system. * - * @return Timestamp of the start time + * @return Reference id */ - public Date getStartedAt() { - return GitHubClient.parseDate(startedAt); + public String getExternalId() { + return externalId; } /** - * Gets the completed time of the check run in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ. + * Gets the HEAD SHA. * - * @return Timestamp of the completed time + * @return sha for the HEAD commit */ - public Date getCompletedAt() { - return GitHubClient.parseDate(completedAt); + public String getHeadSha() { + return headSha; } /** - * Gets the GitHub app this check run belongs to, included in response. + * Gets the HTML URL: https://github.com/[owner]/[repo-name]/runs/[check-run-id], usually an GitHub Action page of + * the check run. * - * @return GitHub App + * @return HTML URL */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHApp getApp() { - return app; + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); } /** - * Gets the check suite this check run belongs to. + * Gets the custom name of this check run. * - * @return Check suite + * @return Name of the check run */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHCheckSuite getCheckSuite() { - return checkSuite; + public String getName() { + return name; + } + + /** + * Gets the global node id to access most objects in GitHub. + * + * @return Global node id + * @see documentation + */ + public String getNodeId() { + return nodeId; } /** @@ -313,81 +320,41 @@ public Output getOutput() { } /** - * Represents an output in a check run to include summary and other results. + * Gets the pull requests participated in this check run. * - * @see documentation + * Note this field is only populated for events. When getting a {@link GHCheckRun} outside of an event, this is + * always empty. + * + * @return the list of {@link GHPullRequest}s for this check run. Only populated for events. + * @throws IOException + * the io exception */ - public static class Output { - - /** - * Create default Output instance - */ - public Output() { - } - - private String title; - private String summary; - private String text; - private int annotationsCount; - private String annotationsUrl; - - /** - * Gets the title of check run. - * - * @return title of check run - */ - public String getTitle() { - return title; - } - - /** - * Gets the summary of the check run, note that it supports Markdown. - * - * @return summary of check run - */ - public String getSummary() { - return summary; - } - - /** - * Gets the details of the check run, note that it supports Markdown. - * - * @return Details of the check run - */ - public String getText() { - return text; - } - - /** - * Gets the annotation count of a check run. - * - * @return annotation count of a check run - */ - public int getAnnotationsCount() { - return annotationsCount; - } - - /** - * Gets the URL of annotations. - * - * @return URL of annotations - */ - public URL getAnnotationsUrl() { - return GitHubClient.parseURL(annotationsUrl); + public List getPullRequests() throws IOException { + for (GHPullRequest singlePull : pullRequests) { + // Only refresh if we haven't do so before + singlePull.refresh(singlePull.getTitle()); } + return Collections.unmodifiableList(Arrays.asList(pullRequests)); } /** - * The Enum AnnotationLevel. + * Gets the start time of the check run in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ. + * + * @return Timestamp of the start time */ - public static enum AnnotationLevel { + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getStartedAt() { + return GitHubClient.parseInstant(startedAt); + } - /** The notice. */ - NOTICE, - /** The warning. */ - WARNING, - /** The failure. */ - FAILURE + /** + * Gets status of the check run. + * + * @return Status of the check run + * @see Status + */ + public Status getStatus() { + return Status.from(status); } /** @@ -399,4 +366,41 @@ public static enum AnnotationLevel { return new GHCheckRunBuilder(owner, getId()); } + /** + * Wrap. + * + * @param owner + * the owner + * @return the GH check run + */ + GHCheckRun wrap(GHRepository owner) { + this.owner = owner; + wrap(owner.root()); + return this; + } + + /** + * Wrap. + * + * @param root + * the root + * @return the GH check run + */ + GHCheckRun wrap(GitHub root) { + if (owner != null) { + for (GHPullRequest singlePull : pullRequests) { + singlePull.wrap(owner); + } + } + if (checkSuite != null) { + if (owner != null) { + checkSuite.wrap(owner); + } else { + checkSuite.wrap(root); + } + } + + return this; + } + } diff --git a/src/main/java/org/kohsuke/github/GHCheckRunBuilder.java b/src/main/java/org/kohsuke/github/GHCheckRunBuilder.java index eefe6d0235..0dcff092ba 100644 --- a/src/main/java/org/kohsuke/github/GHCheckRunBuilder.java +++ b/src/main/java/org/kohsuke/github/GHCheckRunBuilder.java @@ -30,6 +30,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; +import java.time.Instant; import java.util.Collections; import java.util.Date; import java.util.LinkedList; @@ -49,13 +50,268 @@ @SuppressFBWarnings(value = "URF_UNREAD_FIELD", justification = "Jackson serializes these even without a getter") public final class GHCheckRunBuilder { + /** + * The Class Action. + * + * @see documentation + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class Action { + + private final String description; + private final String identifier; + private final String label; + + /** + * Instantiates a new action. + * + * @param label + * the label + * @param description + * the description + * @param identifier + * the identifier + */ + public Action(@NonNull String label, @NonNull String description, @NonNull String identifier) { + this.label = label; + this.description = description; + this.identifier = identifier; + } + + } + + /** + * The Class Annotation. + * + * @see documentation + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class Annotation { + + private final String annotationLevel; + private Integer endColumn; + private final int endLine; + private final String message; + private final String path; + private String rawDetails; + private Integer startColumn; + private final int startLine; + private String title; + + /** + * Instantiates a new annotation. + * + * @param path + * the path + * @param line + * the line + * @param annotationLevel + * the annotation level + * @param message + * the message + */ + public Annotation(@NonNull String path, + int line, + @NonNull GHCheckRun.AnnotationLevel annotationLevel, + @NonNull String message) { + this(path, line, line, annotationLevel, message); + } + + /** + * Instantiates a new annotation. + * + * @param path + * the path + * @param startLine + * the start line + * @param endLine + * the end line + * @param annotationLevel + * the annotation level + * @param message + * the message + */ + public Annotation(@NonNull String path, + int startLine, + int endLine, + @NonNull GHCheckRun.AnnotationLevel annotationLevel, + @NonNull String message) { + this.path = path; + this.startLine = startLine; + this.endLine = endLine; + this.annotationLevel = annotationLevel.toString().toLowerCase(Locale.ROOT); + this.message = message; + } + + /** + * With end column. + * + * @param endColumn + * the end column + * @return the annotation + */ + public @NonNull Annotation withEndColumn(@CheckForNull Integer endColumn) { + this.endColumn = endColumn; + return this; + } + + /** + * With raw details. + * + * @param rawDetails + * the raw details + * @return the annotation + */ + public @NonNull Annotation withRawDetails(@CheckForNull String rawDetails) { + this.rawDetails = rawDetails; + return this; + } + + /** + * With start column. + * + * @param startColumn + * the start column + * @return the annotation + */ + public @NonNull Annotation withStartColumn(@CheckForNull Integer startColumn) { + this.startColumn = startColumn; + return this; + } + + /** + * With title. + * + * @param title + * the title + * @return the annotation + */ + public @NonNull Annotation withTitle(@CheckForNull String title) { + this.title = title; + return this; + } + + } + /** + * The Class Image. + * + * @see documentation + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class Image { + + private final String alt; + private String caption; + private final String imageUrl; + + /** + * Instantiates a new image. + * + * @param alt + * the alt + * @param imageURL + * the image URL + */ + public Image(@NonNull String alt, @NonNull String imageURL) { + this.alt = alt; + this.imageUrl = imageURL; + } + + /** + * With caption. + * + * @param caption + * the caption + * @return the image + */ + public @NonNull Image withCaption(@CheckForNull String caption) { + this.caption = caption; + return this; + } + + } + /** + * The Class Output. + * + * @see documentation + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class Output { + + private List annotations; + private List images; + private final String summary; + private String text; + private final String title; + + /** + * Instantiates a new output. + * + * @param title + * the title + * @param summary + * the summary + */ + public Output(@NonNull String title, @NonNull String summary) { + this.title = title; + this.summary = summary; + } + + /** + * Adds the. + * + * @param annotation + * the annotation + * @return the output + */ + public @NonNull Output add(@NonNull Annotation annotation) { + if (annotations == null) { + annotations = new LinkedList<>(); + } + annotations.add(annotation); + return this; + } + + /** + * Adds the. + * + * @param image + * the image + * @return the output + */ + public @NonNull Output add(@NonNull Image image) { + if (images == null) { + images = new LinkedList<>(); + } + images.add(image); + return this; + } + + /** + * With text. + * + * @param text + * the text + * @return the output + */ + public @NonNull Output withText(@CheckForNull String text) { + this.text = text; + return this; + } + + } + + private static final int MAX_ANNOTATIONS = 50; + + private List actions; + + private Output output; + /** The repo. */ protected final GHRepository repo; /** The requester. */ protected final Requester requester; - private Output output; - private List actions; private GHCheckRunBuilder(GHRepository repo, Requester requester) { this.repo = repo; @@ -96,100 +352,17 @@ private GHCheckRunBuilder(GHRepository repo, Requester requester) { } /** - * With name. + * Adds the. * - * @param name - * the name - * @param oldName - * the old name + * @param action + * the action * @return the GH check run builder */ - public @NonNull GHCheckRunBuilder withName(@CheckForNull String name, String oldName) { - if (oldName == null) { - throw new GHException("Can not update uncreated check run"); - } - requester.with("name", name); - return this; - } - - /** - * With details URL. - * - * @param detailsURL - * the details URL - * @return the GH check run builder - */ - public @NonNull GHCheckRunBuilder withDetailsURL(@CheckForNull String detailsURL) { - requester.with("details_url", detailsURL); - return this; - } - - /** - * With external ID. - * - * @param externalID - * the external ID - * @return the GH check run builder - */ - public @NonNull GHCheckRunBuilder withExternalID(@CheckForNull String externalID) { - requester.with("external_id", externalID); - return this; - } - - /** - * With status. - * - * @param status - * the status - * @return the GH check run builder - */ - public @NonNull GHCheckRunBuilder withStatus(@CheckForNull GHCheckRun.Status status) { - if (status != null) { - // Do *not* use the overload taking Enum, as that s/_/-/g which would be wrong here. - requester.with("status", status.toString().toLowerCase(Locale.ROOT)); - } - return this; - } - - /** - * With conclusion. - * - * @param conclusion - * the conclusion - * @return the GH check run builder - */ - public @NonNull GHCheckRunBuilder withConclusion(@CheckForNull GHCheckRun.Conclusion conclusion) { - if (conclusion != null) { - requester.with("conclusion", conclusion.toString().toLowerCase(Locale.ROOT)); - } - return this; - } - - /** - * With started at. - * - * @param startedAt - * the started at - * @return the GH check run builder - */ - public @NonNull GHCheckRunBuilder withStartedAt(@CheckForNull Date startedAt) { - if (startedAt != null) { - requester.with("started_at", GitHubClient.printDate(startedAt)); - } - return this; - } - - /** - * With completed at. - * - * @param completedAt - * the completed at - * @return the GH check run builder - */ - public @NonNull GHCheckRunBuilder withCompletedAt(@CheckForNull Date completedAt) { - if (completedAt != null) { - requester.with("completed_at", GitHubClient.printDate(completedAt)); + public @NonNull GHCheckRunBuilder add(@NonNull Action action) { + if (actions == null) { + actions = new LinkedList<>(); } + actions.add(action); return this; } @@ -208,22 +381,6 @@ private GHCheckRunBuilder(GHRepository repo, Requester requester) { return this; } - /** - * Adds the. - * - * @param action - * the action - * @return the GH check run builder - */ - public @NonNull GHCheckRunBuilder add(@NonNull Action action) { - if (actions == null) { - actions = new LinkedList<>(); - } - actions.add(action); - return this; - } - - private static final int MAX_ANNOTATIONS = 50; /** * Actually creates the check run. (If more than fifty annotations were requested, this is done in batches.) * @@ -257,256 +414,126 @@ private GHCheckRunBuilder(GHRepository repo, Requester requester) { } /** - * The Class Output. + * With completed at. * - * @see documentation + * @param completedAt + * the completed at + * @return the GH check run builder + * @deprecated Use {@link #withCompletedAt(Instant)} */ - @JsonInclude(JsonInclude.Include.NON_NULL) - public static final class Output { - - private final String title; - private final String summary; - private String text; - private List annotations; - private List images; - - /** - * Instantiates a new output. - * - * @param title - * the title - * @param summary - * the summary - */ - public Output(@NonNull String title, @NonNull String summary) { - this.title = title; - this.summary = summary; - } - - /** - * With text. - * - * @param text - * the text - * @return the output - */ - public @NonNull Output withText(@CheckForNull String text) { - this.text = text; - return this; - } - - /** - * Adds the. - * - * @param annotation - * the annotation - * @return the output - */ - public @NonNull Output add(@NonNull Annotation annotation) { - if (annotations == null) { - annotations = new LinkedList<>(); - } - annotations.add(annotation); - return this; - } - - /** - * Adds the. - * - * @param image - * the image - * @return the output - */ - public @NonNull Output add(@NonNull Image image) { - if (images == null) { - images = new LinkedList<>(); - } - images.add(image); - return this; - } - + @Deprecated + public @NonNull GHCheckRunBuilder withCompletedAt(@CheckForNull Date completedAt) { + return withCompletedAt(GitHubClient.toInstantOrNull(completedAt)); } /** - * The Class Annotation. + * With completed at. * - * @see documentation + * @param completedAt + * the completed at + * @return the GH check run builder */ - @JsonInclude(JsonInclude.Include.NON_NULL) - public static final class Annotation { - - private final String path; - private final int start_line; - private final int end_line; - private final String annotation_level; - private final String message; - private Integer start_column; - private Integer end_column; - private String title; - private String raw_details; - - /** - * Instantiates a new annotation. - * - * @param path - * the path - * @param line - * the line - * @param annotationLevel - * the annotation level - * @param message - * the message - */ - public Annotation(@NonNull String path, - int line, - @NonNull GHCheckRun.AnnotationLevel annotationLevel, - @NonNull String message) { - this(path, line, line, annotationLevel, message); - } - - /** - * Instantiates a new annotation. - * - * @param path - * the path - * @param startLine - * the start line - * @param endLine - * the end line - * @param annotationLevel - * the annotation level - * @param message - * the message - */ - public Annotation(@NonNull String path, - int startLine, - int endLine, - @NonNull GHCheckRun.AnnotationLevel annotationLevel, - @NonNull String message) { - this.path = path; - start_line = startLine; - end_line = endLine; - annotation_level = annotationLevel.toString().toLowerCase(Locale.ROOT); - this.message = message; - } - - /** - * With start column. - * - * @param startColumn - * the start column - * @return the annotation - */ - public @NonNull Annotation withStartColumn(@CheckForNull Integer startColumn) { - start_column = startColumn; - return this; + public @NonNull GHCheckRunBuilder withCompletedAt(@CheckForNull Instant completedAt) { + if (completedAt != null) { + requester.with("completed_at", GitHubClient.printInstant(completedAt)); } + return this; + } - /** - * With end column. - * - * @param endColumn - * the end column - * @return the annotation - */ - public @NonNull Annotation withEndColumn(@CheckForNull Integer endColumn) { - end_column = endColumn; - return this; + /** + * With conclusion. + * + * @param conclusion + * the conclusion + * @return the GH check run builder + */ + public @NonNull GHCheckRunBuilder withConclusion(@CheckForNull GHCheckRun.Conclusion conclusion) { + if (conclusion != null) { + requester.with("conclusion", conclusion.toString().toLowerCase(Locale.ROOT)); } + return this; + } - /** - * With title. - * - * @param title - * the title - * @return the annotation - */ - public @NonNull Annotation withTitle(@CheckForNull String title) { - this.title = title; - return this; - } + /** + * With details URL. + * + * @param detailsURL + * the details URL + * @return the GH check run builder + */ + public @NonNull GHCheckRunBuilder withDetailsURL(@CheckForNull String detailsURL) { + requester.with("details_url", detailsURL); + return this; + } + /** + * With external ID. + * + * @param externalID + * the external ID + * @return the GH check run builder + */ + public @NonNull GHCheckRunBuilder withExternalID(@CheckForNull String externalID) { + requester.with("external_id", externalID); + return this; + } - /** - * With raw details. - * - * @param rawDetails - * the raw details - * @return the annotation - */ - public @NonNull Annotation withRawDetails(@CheckForNull String rawDetails) { - raw_details = rawDetails; - return this; + /** + * With name. + * + * @param name + * the name + * @param oldName + * the old name + * @return the GH check run builder + */ + public @NonNull GHCheckRunBuilder withName(@CheckForNull String name, String oldName) { + if (oldName == null) { + throw new GHException("Can not update uncreated check run"); } - + requester.with("name", name); + return this; } /** - * The Class Image. + * With started at. * - * @see documentation + * @param startedAt + * the started at + * @return the GH check run builder + * @deprecated Use {@link #withStartedAt(Instant)} */ - @JsonInclude(JsonInclude.Include.NON_NULL) - public static final class Image { - - private final String alt; - private final String image_url; - private String caption; - - /** - * Instantiates a new image. - * - * @param alt - * the alt - * @param imageURL - * the image URL - */ - public Image(@NonNull String alt, @NonNull String imageURL) { - this.alt = alt; - image_url = imageURL; - } + @Deprecated + public @NonNull GHCheckRunBuilder withStartedAt(@CheckForNull Date startedAt) { + return withStartedAt(GitHubClient.toInstantOrNull(startedAt)); + } - /** - * With caption. - * - * @param caption - * the caption - * @return the image - */ - public @NonNull Image withCaption(@CheckForNull String caption) { - this.caption = caption; - return this; + /** + * With started at. + * + * @param startedAt + * the started at + * @return the GH check run builder + */ + public @NonNull GHCheckRunBuilder withStartedAt(@CheckForNull Instant startedAt) { + if (startedAt != null) { + requester.with("started_at", GitHubClient.printInstant(startedAt)); } - + return this; } /** - * The Class Action. + * With status. * - * @see documentation + * @param status + * the status + * @return the GH check run builder */ - @JsonInclude(JsonInclude.Include.NON_NULL) - public static final class Action { - - private final String label; - private final String description; - private final String identifier; - - /** - * Instantiates a new action. - * - * @param label - * the label - * @param description - * the description - * @param identifier - * the identifier - */ - public Action(@NonNull String label, @NonNull String description, @NonNull String identifier) { - this.label = label; - this.description = description; - this.identifier = identifier; + public @NonNull GHCheckRunBuilder withStatus(@CheckForNull GHCheckRun.Status status) { + if (status != null) { + // Do *not* use the overload taking Enum, as that s/_/-/g which would be wrong here. + requester.with("status", status.toString().toLowerCase(Locale.ROOT)); } - + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHCheckRunsPage.java b/src/main/java/org/kohsuke/github/GHCheckRunsPage.java index 2caf0a711f..d0b5d012f2 100644 --- a/src/main/java/org/kohsuke/github/GHCheckRunsPage.java +++ b/src/main/java/org/kohsuke/github/GHCheckRunsPage.java @@ -9,8 +9,8 @@ @SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, justification = "JSON API") class GHCheckRunsPage { - private int total_count; - private GHCheckRun[] check_runs; + private GHCheckRun[] checkRuns; + private int totalCount; /** * Gets the total count. @@ -18,7 +18,7 @@ class GHCheckRunsPage { * @return the total count */ public int getTotalCount() { - return total_count; + return totalCount; } /** @@ -29,9 +29,9 @@ public int getTotalCount() { * @return the check runs */ GHCheckRun[] getCheckRuns(GHRepository owner) { - for (GHCheckRun check_run : check_runs) { - check_run.wrap(owner); + for (GHCheckRun checkRun : checkRuns) { + checkRun.wrap(owner); } - return check_runs; + return checkRuns; } } diff --git a/src/main/java/org/kohsuke/github/GHCheckSuite.java b/src/main/java/org/kohsuke/github/GHCheckSuite.java index 8c9dea61f7..0ada44e82b 100644 --- a/src/main/java/org/kohsuke/github/GHCheckSuite.java +++ b/src/main/java/org/kohsuke/github/GHCheckSuite.java @@ -1,10 +1,12 @@ package org.kohsuke.github; import com.fasterxml.jackson.annotation.JsonProperty; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.net.URL; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.Date; @@ -21,103 +23,137 @@ public class GHCheckSuite extends GHObject { /** - * Create default GHCheckSuite instance + * The Class HeadCommit. */ - public GHCheckSuite() { + public static class HeadCommit extends GitHubBridgeAdapterObject { + + private GitUser author; + + private GitUser committer; + private String id; + private String message; + private String timestamp; + private String treeId; + /** + * Create default HeadCommit instance + */ + public HeadCommit() { + } + + /** + * Gets author. + * + * @return the author + */ + public GitUser getAuthor() { + return author; + } + + /** + * Gets committer. + * + * @return the committer + */ + public GitUser getCommitter() { + return committer; + } + + /** + * Gets id of the commit, used by {@link GHCheckSuite} when a {@link GHEvent#CHECK_SUITE} comes. + * + * @return id of the commit + */ + public String getId() { + return id; + } + + /** + * Gets message. + * + * @return commit message. + */ + public String getMessage() { + return message; + } + + /** + * Gets timestamp of the commit. + * + * @return timestamp of the commit + */ + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getTimestamp() { + return GitHubClient.parseInstant(timestamp); + } + + /** + * Gets id of the tree. + * + * @return id of the tree + */ + public String getTreeId() { + return treeId; + } } - /** The owner. */ - @JsonProperty("repository") - GHRepository owner; + private String after; - private String nodeId; - private String headBranch; - private String headSha; - private String status; - private String conclusion; + private GHApp app; private String before; - private String after; - private int latestCheckRunsCount; private String checkRunsUrl; + private String conclusion; + private String headBranch; private HeadCommit headCommit; - private GHApp app; + private String headSha; + private int latestCheckRunsCount; + private String nodeId; private GHPullRequest[] pullRequests; + private String status; + /** The owner. */ + @JsonProperty("repository") + GHRepository owner; /** - * Wrap. - * - * @param owner - * the owner - * @return the GH check suite - */ - GHCheckSuite wrap(GHRepository owner) { - this.owner = owner; - this.wrap(owner.root()); - return this; - } - - /** - * Wrap. - * - * @param root - * the root - * @return the GH check suite - */ - GHCheckSuite wrap(GitHub root) { - if (owner != null) { - if (pullRequests != null && pullRequests.length != 0) { - for (GHPullRequest singlePull : pullRequests) { - singlePull.wrap(owner); - } - } - } - return this; - } - - /** - * Wrap. - * - * @return the GH pull request[] + * Create default GHCheckSuite instance */ - GHPullRequest[] wrap() { - return pullRequests; + public GHCheckSuite() { } /** - * Gets the global node id to access most objects in GitHub. + * The SHA of the most recent commit on ref after the push. * - * @return global node id - * @see documentation + * @return sha of a commit */ - public String getNodeId() { - return nodeId; + public String getAfter() { + return after; } /** - * The head branch name the changes are on. + * Gets the GitHub app this check suite belongs to, included in response. * - * @return head branch name + * @return GitHub App */ - public String getHeadBranch() { - return headBranch; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHApp getApp() { + return app; } /** - * Gets the HEAD SHA. + * The SHA of the most recent commit on ref before the push. * - * @return sha for the HEAD commit + * @return sha of a commit */ - public String getHeadSha() { - return headSha; + public String getBefore() { + return before; } /** - * Gets status of the check suite. It can be one of request, in_progress, or completed. + * The url used to list all the check runs belonged to this suite. * - * @return status of the check suite + * @return url containing all check runs */ - public String getStatus() { - return status; + public URL getCheckRunsUrl() { + return GitHubClient.parseURL(checkRunsUrl); } /** @@ -132,58 +168,49 @@ public String getConclusion() { } /** - * The SHA of the most recent commit on ref before the push. - * - * @return sha of a commit - */ - public String getBefore() { - return before; - } - - /** - * The SHA of the most recent commit on ref after the push. + * The head branch name the changes are on. * - * @return sha of a commit + * @return head branch name */ - public String getAfter() { - return after; + public String getHeadBranch() { + return headBranch; } /** - * The quantity of check runs that had run as part of the latest push. + * The commit of current head. * - * @return sha of the most recent commit + * @return head commit */ - public int getLatestCheckRunsCount() { - return latestCheckRunsCount; + public HeadCommit getHeadCommit() { + return headCommit; } /** - * The url used to list all the check runs belonged to this suite. + * Gets the HEAD SHA. * - * @return url containing all check runs + * @return sha for the HEAD commit */ - public URL getCheckRunsUrl() { - return GitHubClient.parseURL(checkRunsUrl); + public String getHeadSha() { + return headSha; } /** - * The commit of current head. + * The quantity of check runs that had run as part of the latest push. * - * @return head commit + * @return sha of the most recent commit */ - public HeadCommit getHeadCommit() { - return headCommit; + public int getLatestCheckRunsCount() { + return latestCheckRunsCount; } /** - * Gets the GitHub app this check suite belongs to, included in response. + * Gets the global node id to access most objects in GitHub. * - * @return GitHub App + * @return global node id + * @see documentation */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHApp getApp() { - return app; + public String getNodeId() { + return nodeId; } /** @@ -208,75 +235,51 @@ public List getPullRequests() throws IOException { } /** - * The Class HeadCommit. + * Gets status of the check suite. It can be one of request, in_progress, or completed. + * + * @return status of the check suite */ - public static class HeadCommit { - - /** - * Create default HeadCommit instance - */ - public HeadCommit() { - } - - private String id; - private String treeId; - private String message; - private String timestamp; - private GitUser author; - private GitUser committer; - - /** - * Gets id of the commit, used by {@link GHCheckSuite} when a {@link GHEvent#CHECK_SUITE} comes. - * - * @return id of the commit - */ - public String getId() { - return id; - } - - /** - * Gets id of the tree. - * - * @return id of the tree - */ - public String getTreeId() { - return treeId; - } - - /** - * Gets message. - * - * @return commit message. - */ - public String getMessage() { - return message; - } + public String getStatus() { + return status; + } - /** - * Gets timestamp of the commit. - * - * @return timestamp of the commit - */ - public Date getTimestamp() { - return GitHubClient.parseDate(timestamp); - } + /** + * Wrap. + * + * @return the GH pull request[] + */ + GHPullRequest[] wrap() { + return pullRequests; + } - /** - * Gets author. - * - * @return the author - */ - public GitUser getAuthor() { - return author; - } + /** + * Wrap. + * + * @param owner + * the owner + * @return the GH check suite + */ + GHCheckSuite wrap(GHRepository owner) { + this.owner = owner; + this.wrap(owner.root()); + return this; + } - /** - * Gets committer. - * - * @return the committer - */ - public GitUser getCommitter() { - return committer; + /** + * Wrap. + * + * @param root + * the root + * @return the GH check suite + */ + GHCheckSuite wrap(GitHub root) { + if (owner != null) { + if (pullRequests != null && pullRequests.length != 0) { + for (GHPullRequest singlePull : pullRequests) { + singlePull.wrap(owner); + } + } } + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHCodeownersError.java b/src/main/java/org/kohsuke/github/GHCodeownersError.java index 49654263b1..b090a1673b 100644 --- a/src/main/java/org/kohsuke/github/GHCodeownersError.java +++ b/src/main/java/org/kohsuke/github/GHCodeownersError.java @@ -9,23 +9,14 @@ */ public class GHCodeownersError { - /** - * Create default GHCodeownersError instance - */ - public GHCodeownersError() { - } + private String kind, source, suggestion, message, path; private int line, column; - private String kind, source, suggestion, message, path; - /** - * Gets line. - * - * @return the line + * Create default GHCodeownersError instance */ - public int getLine() { - return line; + public GHCodeownersError() { } /** @@ -47,21 +38,12 @@ public String getKind() { } /** - * Gets source. - * - * @return the source - */ - public String getSource() { - return source; - } - - /** - * Gets suggestion. + * Gets line. * - * @return the suggestion + * @return the line */ - public String getSuggestion() { - return suggestion; + public int getLine() { + return line; } /** @@ -81,4 +63,22 @@ public String getMessage() { public String getPath() { return path; } + + /** + * Gets source. + * + * @return the source + */ + public String getSource() { + return source; + } + + /** + * Gets suggestion. + * + * @return the suggestion + */ + public String getSuggestion() { + return suggestion; + } } diff --git a/src/main/java/org/kohsuke/github/GHCommentAuthorAssociation.java b/src/main/java/org/kohsuke/github/GHCommentAuthorAssociation.java index b00905ab8f..011016f504 100644 --- a/src/main/java/org/kohsuke/github/GHCommentAuthorAssociation.java +++ b/src/main/java/org/kohsuke/github/GHCommentAuthorAssociation.java @@ -38,5 +38,9 @@ public enum GHCommentAuthorAssociation { /** * Author is the owner of the repository. */ - OWNER + OWNER, + /** + * Author association is not recognized. + */ + UNKNOWN } diff --git a/src/main/java/org/kohsuke/github/GHCommit.java b/src/main/java/org/kohsuke/github/GHCommit.java index 83ea3be00b..1edf0503d3 100644 --- a/src/main/java/org/kohsuke/github/GHCommit.java +++ b/src/main/java/org/kohsuke/github/GHCommit.java @@ -1,9 +1,11 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.net.URL; +import java.time.Instant; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collections; @@ -21,109 +23,61 @@ @SuppressFBWarnings(value = { "NP_UNWRITTEN_FIELD", "UWF_UNWRITTEN_FIELD" }, justification = "JSON API") public class GHCommit { - private GHRepository owner; - - private ShortInfo commit; - /** - * Short summary of this commit. + * A file that was modified. */ - @SuppressFBWarnings( - value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD", - "UWF_UNWRITTEN_FIELD" }, - justification = "JSON API") - public static class ShortInfo extends GitCommit { + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "It's being initialized by JSON deserialization") + public static class File { - private int comment_count = -1; + /** The deletions. */ + int changes, additions, deletions; - /** - * Gets comment count. - * - * @return the comment count - * @throws GHException - * the GH exception - */ - public int getCommentCount() throws GHException { - if (comment_count < 0) { - throw new GHException("Not available on this endpoint."); - } - return comment_count; - } + /** The previous filename. */ + String filename, previousFilename; - /** - * Creates instance of {@link GHCommit.ShortInfo}. - */ - public ShortInfo() { - // Empty constructor required for Jackson binding - }; + /** The patch. */ + String rawUrl, blobUrl, sha, patch; + + /** The status. */ + String status; /** - * Instantiates a new short info. - * - * @param commit - * the commit + * Create default File instance */ - ShortInfo(GitCommit commit) { - // Inherited copy constructor, used for bridge method from {@link GitCommit}, - // which is used in {@link GHContentUpdateResponse}) to {@link GHCommit}. - super(commit); + public File() { } /** - * Gets the parent SHA 1 s. + * Gets blob url. * - * @return the parent SHA 1 s + * @return URL like + * 'https://github.com/jenkinsci/jenkins/blob/1182e2ebb1734d0653142bd422ad33c21437f7cf/core/pom.xml' + * that resolves to the HTML page that describes this file. */ - @Override - public List getParentSHA1s() { - List shortInfoParents = super.getParentSHA1s(); - if (shortInfoParents == null) { - throw new GHException("Not available on this endpoint. Try calling getParentSHA1s from outer class."); - } - return shortInfoParents; + public URL getBlobUrl() { + return GitHubClient.parseURL(blobUrl); } - } - - /** - * The type Stats. - */ - public static class Stats { - /** - * Create default Stats instance + * Gets file name. + * + * @return Full path in the repository. */ - public Stats() { + @SuppressFBWarnings(value = "NM_CONFUSING", + justification = "It's a part of the library's API and cannot be renamed") + public String getFileName() { + return filename; } - /** The deletions. */ - int total, additions, deletions; - } - - /** - * A file that was modified. - */ - @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "It's being initialized by JSON deserialization") - public static class File { - /** - * Create default File instance + * Gets lines added. + * + * @return Number of lines added. */ - public File() { + public int getLinesAdded() { + return additions; } - /** The status. */ - String status; - - /** The deletions. */ - int changes, additions, deletions; - - /** The patch. */ - String raw_url, blob_url, sha, patch; - - /** The previous filename. */ - String filename, previous_filename; - /** * Gets lines changed. * @@ -133,15 +87,6 @@ public int getLinesChanged() { return changes; } - /** - * Gets lines added. - * - * @return Number of lines added. - */ - public int getLinesAdded() { - return additions; - } - /** * Gets lines deleted. * @@ -152,23 +97,12 @@ public int getLinesDeleted() { } /** - * Gets status. - * - * @return "modified", "added", or "removed" - */ - public String getStatus() { - return status; - } - - /** - * Gets file name. + * Gets patch. * - * @return Full path in the repository. + * @return The actual change. */ - @SuppressFBWarnings(value = "NM_CONFUSING", - justification = "It's a part of the library's API and cannot be renamed") - public String getFileName() { - return filename; + public String getPatch() { + return patch; } /** @@ -177,16 +111,7 @@ public String getFileName() { * @return Previous path, in case file has moved. */ public String getPreviousFilename() { - return previous_filename; - } - - /** - * Gets patch. - * - * @return The actual change. - */ - public String getPatch() { - return patch; + return previousFilename; } /** @@ -197,27 +122,25 @@ public String getPatch() { * resolves to the actual content of the file. */ public URL getRawUrl() { - return GitHubClient.parseURL(raw_url); + return GitHubClient.parseURL(rawUrl); } /** - * Gets blob url. + * Gets sha. * - * @return URL like - * 'https://github.com/jenkinsci/jenkins/blob/1182e2ebb1734d0653142bd422ad33c21437f7cf/core/pom.xml' - * that resolves to the HTML page that describes this file. + * @return [0 -9a-f]{40} SHA1 checksum. */ - public URL getBlobUrl() { - return GitHubClient.parseURL(blob_url); + public String getSha() { + return sha; } /** - * Gets sha. + * Gets status. * - * @return [0 -9a-f]{40} SHA1 checksum. + * @return "modified", "added", or "removed" */ - public String getSha() { - return sha; + public String getStatus() { + return status; } } @@ -226,18 +149,93 @@ public String getSha() { */ public static class Parent { + /** The sha. */ + String sha; + + /** The url. */ + @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") + String url; + /** * Create default Parent instance */ public Parent() { } + } - /** The url. */ - @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") - String url; + /** + * Short summary of this commit. + */ + @SuppressFBWarnings( + value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD", + "UWF_UNWRITTEN_FIELD" }, + justification = "JSON API") + public static class ShortInfo extends GitCommit { + + private int commentCount = -1; + + /** + * Creates instance of {@link GHCommit.ShortInfo}. + */ + public ShortInfo() { + // Empty constructor required for Jackson binding + } + + /** + * Instantiates a new short info. + * + * @param commit + * the commit + */ + ShortInfo(GitCommit commit) { + // Inherited copy constructor, used for bridge method from {@link GitCommit}, + // which is used in {@link GHContentUpdateResponse}) to {@link GHCommit}. + super(commit); + }; + + /** + * Gets comment count. + * + * @return the comment count + * @throws GHException + * the GH exception + */ + public int getCommentCount() throws GHException { + if (commentCount < 0) { + throw new GHException("Not available on this endpoint."); + } + return commentCount; + } + + /** + * Gets the parent SHA 1 s. + * + * @return the parent SHA 1 s + */ + @Override + public List getParentSHA1s() { + List shortInfoParents = super.getParentSHA1s(); + if (shortInfoParents == null) { + throw new GHException("Not available on this endpoint. Try calling getParentSHA1s from outer class."); + } + return shortInfoParents; + } - /** The sha. */ - String sha; + } + + /** + * The type Stats. + */ + public static class Stats { + + /** The deletions. */ + int total, additions, deletions; + + /** + * Create default Stats instance + */ + public Stats() { + } } /** @@ -245,33 +243,37 @@ public Parent() { */ static class User { - /** The gravatar id. */ - // TODO: what if someone who doesn't have an account on GitHub makes a commit? - @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") - String url, avatar_url, gravatar_id; - /** The id. */ @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") int id; /** The login. */ String login; + + /** The gravatar id. */ + // TODO: what if someone who doesn't have an account on GitHub makes a commit? + @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") + String url, avatarUrl, gravatarId; } - /** The sha. */ - String url, html_url, sha, message; + private ShortInfo commit; + + private GHRepository owner; + + /** The committer. */ + User author, committer; /** The files. */ List files; - /** The stats. */ - Stats stats; - /** The parents. */ List parents; - /** The committer. */ - User author, committer; + /** The stats. */ + Stats stats; + + /** The sha. */ + String url, htmlUrl, sha, message; /** * Creates an instance of {@link GHCommit}. @@ -294,7 +296,7 @@ public GHCommit() { commit = shortInfo; owner = commit.getOwner(); - html_url = commit.getHtmlUrl(); + htmlUrl = commit.getHtmlUrl(); sha = commit.getSha(); url = commit.getUrl(); parents = commit.getParents(); @@ -302,73 +304,119 @@ public GHCommit() { } /** - * Gets commit short info. + * Create comment gh commit comment. + * + * @param body + * the body + * @return the gh commit comment + * @throws IOException + * the io exception + */ + public GHCommitComment createComment(String body) throws IOException { + return createComment(body, null, null, null); + } + + /** + * Creates a commit comment. + *

+ * I'm not sure how path/line/position parameters interact with each other. + * + * @param body + * body of the comment + * @param path + * path of file being commented on + * @param line + * target line for comment + * @param position + * position on line + * @return created GHCommitComment + * @throws IOException + * if comment is not created + */ + public GHCommitComment createComment(String body, String path, Integer line, Integer position) throws IOException { + GHCommitComment r = owner.root() + .createRequest() + .method("POST") + .with("body", body) + .with("path", path) + .with("line", line) + .with("position", position) + .withUrlPath( + String.format("/repos/%s/%s/commits/%s/comments", owner.getOwnerName(), owner.getName(), sha)) + .fetch(GHCommitComment.class); + return r.wrap(owner); + } + + /** + * Gets author. * - * @return the commit short info + * @return the author * @throws IOException * the io exception */ - public ShortInfo getCommitShortInfo() throws IOException { - if (commit == null) - populate(); - return commit; + public GHUser getAuthor() throws IOException { + populate(); + return resolveUser(author); } /** - * Gets owner. + * Gets the date the change was authored on. * - * @return the repository that contains the commit. + * @return the date the change was authored on. + * @throws IOException + * if the information was not already fetched and an attempt at fetching the information failed. */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHRepository getOwner() { - return owner; + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getAuthoredDate() throws IOException { + return getCommitShortInfo().getAuthoredDate(); } /** - * Gets lines changed. + * Gets check-runs for given sha. * - * @return the number of lines added + removed. + * @return check runs for given sha. * @throws IOException - * if the field was not populated and refresh fails + * on error */ - public int getLinesChanged() throws IOException { - populate(); - return stats.total; + public PagedIterable getCheckRuns() throws IOException { + return owner.getCheckRuns(sha); } /** - * Gets lines added. + * Gets the date the change was committed on. * - * @return Number of lines added. + * @return the date the change was committed on. * @throws IOException - * if the field was not populated and refresh fails + * if the information was not already fetched and an attempt at fetching the information failed. */ - public int getLinesAdded() throws IOException { - populate(); - return stats.additions; + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getCommitDate() throws IOException { + return getCommitShortInfo().getCommitDate(); } /** - * Gets lines deleted. + * Gets commit short info. * - * @return Number of lines removed. + * @return the commit short info * @throws IOException - * if the field was not populated and refresh fails + * the io exception */ - public int getLinesDeleted() throws IOException { - populate(); - return stats.deletions; + public ShortInfo getCommitShortInfo() throws IOException { + if (commit == null) + populate(); + return commit; } /** - * Use this method to walk the tree. + * Gets committer. * - * @return a GHTree to walk + * @return the committer * @throws IOException - * on error + * the io exception */ - public GHTree getTree() throws IOException { - return owner.getTree(getCommitShortInfo().getTreeSHA1()); + public GHUser getCommitter() throws IOException { + populate(); + return resolveUser(committer); } /** @@ -378,42 +426,64 @@ public GHTree getTree() throws IOException { * "https://github.com/kohsuke/sandbox-ant/commit/8ae38db0ea5837313ab5f39d43a6f73de3bd9000" */ public URL getHtmlUrl() { - return GitHubClient.parseURL(html_url); + return GitHubClient.parseURL(htmlUrl); } /** - * Gets sha 1. + * Gets last status. * - * @return [0 -9a-f]{40} SHA1 checksum. + * @return the last status of this commit, which is what gets shown in the UI. + * @throws IOException + * on error */ - public String getSHA1() { - return sha; + public GHCommitStatus getLastStatus() throws IOException { + return owner.getLastCommitStatus(sha); } /** - * Gets url. + * Gets lines added. * - * @return API URL of this object. + * @return Number of lines added. + * @throws IOException + * if the field was not populated and refresh fails */ - public URL getUrl() { - return GitHubClient.parseURL(url); + public int getLinesAdded() throws IOException { + populate(); + return stats.additions; } /** - * List of files changed/added/removed in this commit. Uses a paginated list if the files returned by GitHub exceed - * 300 in quantity. + * Gets lines changed. * - * @return the List of files - * @see Get a - * commit + * @return the number of lines added + removed. * @throws IOException - * on error + * if the field was not populated and refresh fails */ - public PagedIterable listFiles() throws IOException { + public int getLinesChanged() throws IOException { + populate(); + return stats.total; + } + /** + * Gets lines deleted. + * + * @return Number of lines removed. + * @throws IOException + * if the field was not populated and refresh fails + */ + public int getLinesDeleted() throws IOException { populate(); + return stats.deletions; + } - return new GHCommitFileIterable(owner, sha, files); + /** + * Gets owner. + * + * @return the repository that contains the commit. + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHRepository getOwner() { + return owner; } /** @@ -453,67 +523,32 @@ public List getParents() throws IOException { } /** - * Gets author. - * - * @return the author - * @throws IOException - * the io exception - */ - public GHUser getAuthor() throws IOException { - populate(); - return resolveUser(author); - } - - /** - * Gets the date the change was authored on. - * - * @return the date the change was authored on. - * @throws IOException - * if the information was not already fetched and an attempt at fetching the information failed. - */ - public Date getAuthoredDate() throws IOException { - return getCommitShortInfo().getAuthoredDate(); - } - - /** - * Gets committer. + * Gets sha 1. * - * @return the committer - * @throws IOException - * the io exception + * @return [0 -9a-f]{40} SHA1 checksum. */ - public GHUser getCommitter() throws IOException { - populate(); - return resolveUser(committer); + public String getSHA1() { + return sha; } /** - * Gets the date the change was committed on. + * Use this method to walk the tree. * - * @return the date the change was committed on. + * @return a GHTree to walk * @throws IOException - * if the information was not already fetched and an attempt at fetching the information failed. + * on error */ - public Date getCommitDate() throws IOException { - return getCommitShortInfo().getCommitDate(); - } - - private GHUser resolveUser(User author) throws IOException { - if (author == null || author.login == null) - return null; - return owner.root().getUser(author.login); + public GHTree getTree() throws IOException { + return owner.getTree(getCommitShortInfo().getTreeSHA1()); } /** - * Retrieves a list of pull requests which contain this commit. + * Gets url. * - * @return {@link PagedIterable} with the pull requests which contain this commit + * @return API URL of this object. */ - public PagedIterable listPullRequests() { - return owner.root() - .createRequest() - .withUrlPath(String.format("/repos/%s/%s/commits/%s/pulls", owner.getOwnerName(), owner.getName(), sha)) - .toIterable(GHPullRequest[].class, item -> item.wrapUp(owner)); + public URL getUrl() { + return GitHubClient.parseURL(url); } /** @@ -541,47 +576,32 @@ public PagedIterable listComments() { } /** - * Creates a commit comment. - *

- * I'm not sure how path/line/position parameters interact with each other. + * List of files changed/added/removed in this commit. Uses a paginated list if the files returned by GitHub exceed + * 300 in quantity. * - * @param body - * body of the comment - * @param path - * path of file being commented on - * @param line - * target line for comment - * @param position - * position on line - * @return created GHCommitComment + * @return the List of files + * @see Get a + * commit * @throws IOException - * if comment is not created + * on error */ - public GHCommitComment createComment(String body, String path, Integer line, Integer position) throws IOException { - GHCommitComment r = owner.root() - .createRequest() - .method("POST") - .with("body", body) - .with("path", path) - .with("line", line) - .with("position", position) - .withUrlPath( - String.format("/repos/%s/%s/commits/%s/comments", owner.getOwnerName(), owner.getName(), sha)) - .fetch(GHCommitComment.class); - return r.wrap(owner); + public PagedIterable listFiles() throws IOException { + + populate(); + + return new GHCommitFileIterable(owner, sha, files); } /** - * Create comment gh commit comment. + * Retrieves a list of pull requests which contain this commit. * - * @param body - * the body - * @return the gh commit comment - * @throws IOException - * the io exception + * @return {@link PagedIterable} with the pull requests which contain this commit */ - public GHCommitComment createComment(String body) throws IOException { - return createComment(body, null, null, null); + public PagedIterable listPullRequests() { + return owner.root() + .createRequest() + .withUrlPath(String.format("/repos/%s/%s/commits/%s/pulls", owner.getOwnerName(), owner.getName(), sha)) + .toIterable(GHPullRequest[].class, item -> item.wrapUp(owner)); } /** @@ -595,26 +615,10 @@ public PagedIterable listStatuses() throws IOException { return owner.listCommitStatuses(sha); } - /** - * Gets last status. - * - * @return the last status of this commit, which is what gets shown in the UI. - * @throws IOException - * on error - */ - public GHCommitStatus getLastStatus() throws IOException { - return owner.getLastCommitStatus(sha); - } - - /** - * Gets check-runs for given sha. - * - * @return check runs for given sha. - * @throws IOException - * on error - */ - public PagedIterable getCheckRuns() throws IOException { - return owner.getCheckRuns(sha); + private GHUser resolveUser(User author) throws IOException { + if (author == null || author.login == null) + return null; + return owner.root().getUser(author.login); } /** diff --git a/src/main/java/org/kohsuke/github/GHCommitBuilder.java b/src/main/java/org/kohsuke/github/GHCommitBuilder.java index 11c382312b..65f4c6d679 100644 --- a/src/main/java/org/kohsuke/github/GHCommitBuilder.java +++ b/src/main/java/org/kohsuke/github/GHCommitBuilder.java @@ -1,37 +1,32 @@ package org.kohsuke.github; import java.io.IOException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; +import java.time.Instant; import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.TimeZone; // TODO: Auto-generated Javadoc /** * Builder pattern for creating a new commit. Based on https://developer.github.com/v3/git/commits/#create-a-commit */ public class GHCommitBuilder { - private final GHRepository repo; - private final Requester req; - - private final List parents = new ArrayList(); - private static final class UserInfo { - private final String name; - private final String email; private final String date; + private final String email; + private final String name; - private UserInfo(String name, String email, Date date) { + private UserInfo(String name, String email, Instant date) { this.name = name; this.email = email; - TimeZone tz = TimeZone.getTimeZone("UTC"); - DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - df.setTimeZone(tz); - this.date = df.format((date != null) ? date : new Date()); + this.date = GitHubClient.printInstant(date); } } + private final List parents = new ArrayList(); + + private final GHRepository repo; + + private final Requester req; /** * Instantiates a new GH commit builder. @@ -45,39 +40,20 @@ private UserInfo(String name, String email, Date date) { } /** - * Message gh commit builder. - * - * @param message - * the commit message - * @return the gh commit builder - */ - public GHCommitBuilder message(String message) { - req.with("message", message); - return this; - } - - /** - * Tree gh commit builder. - * - * @param tree - * the SHA of the tree object this commit points to - * @return the gh commit builder - */ - public GHCommitBuilder tree(String tree) { - req.with("tree", tree); - return this; - } - - /** - * Parent gh commit builder. + * Configures the author of this commit. * - * @param parent - * the SHA of a parent commit. + * @param name + * the name + * @param email + * the email + * @param date + * the date * @return the gh commit builder + * @deprecated use {@link #author(String, String, Instant)} instead */ - public GHCommitBuilder parent(String parent) { - parents.add(parent); - return this; + @Deprecated + public GHCommitBuilder author(String name, String email, Date date) { + return author(name, email, GitHubClient.toInstantOrNull(date)); } /** @@ -91,22 +67,26 @@ public GHCommitBuilder parent(String parent) { * the date * @return the gh commit builder */ - public GHCommitBuilder author(String name, String email, Date date) { + public GHCommitBuilder author(String name, String email, Instant date) { req.with("author", new UserInfo(name, email, date)); return this; } /** - * Configures the PGP signature of this commit. - * - * @param signature - * the signature calculated from the commit + * Configures the committer of this commit. * + * @param name + * the name + * @param email + * the email + * @param date + * the date * @return the gh commit builder + * @deprecated use {@link #committer(String, String, Instant)} instead */ - public GHCommitBuilder withSignature(String signature) { - req.with("signature", signature); - return this; + @Deprecated + public GHCommitBuilder committer(String name, String email, Date date) { + return committer(name, email, GitHubClient.toInstantOrNull(date)); } /** @@ -120,15 +100,11 @@ public GHCommitBuilder withSignature(String signature) { * the date * @return the gh commit builder */ - public GHCommitBuilder committer(String name, String email, Date date) { + public GHCommitBuilder committer(String name, String email, Instant date) { req.with("committer", new UserInfo(name, email, date)); return this; } - private String getApiTail() { - return String.format("/repos/%s/%s/git/commits", repo.getOwnerName(), repo.getName()); - } - /** * Creates a blob based on the parameters specified thus far. * @@ -140,4 +116,57 @@ public GHCommit create() throws IOException { req.with("parents", parents); return req.method("POST").withUrlPath(getApiTail()).fetch(GHCommit.class).wrapUp(repo); } + + /** + * Message gh commit builder. + * + * @param message + * the commit message + * @return the gh commit builder + */ + public GHCommitBuilder message(String message) { + req.with("message", message); + return this; + } + + /** + * Parent gh commit builder. + * + * @param parent + * the SHA of a parent commit. + * @return the gh commit builder + */ + public GHCommitBuilder parent(String parent) { + parents.add(parent); + return this; + } + + /** + * Tree gh commit builder. + * + * @param tree + * the SHA of the tree object this commit points to + * @return the gh commit builder + */ + public GHCommitBuilder tree(String tree) { + req.with("tree", tree); + return this; + } + + /** + * Configures the PGP signature of this commit. + * + * @param signature + * the signature calculated from the commit + * + * @return the gh commit builder + */ + public GHCommitBuilder withSignature(String signature) { + req.with("signature", signature); + return this; + } + + private String getApiTail() { + return String.format("/repos/%s/%s/git/commits", repo.getOwnerName(), repo.getName()); + } } diff --git a/src/main/java/org/kohsuke/github/GHCommitComment.java b/src/main/java/org/kohsuke/github/GHCommitComment.java index 40427b41ea..b73a49666d 100644 --- a/src/main/java/org/kohsuke/github/GHCommitComment.java +++ b/src/main/java/org/kohsuke/github/GHCommitComment.java @@ -19,16 +19,10 @@ justification = "JSON API") public class GHCommitComment extends GHObject implements Reactable { - /** - * Create default GHCommitComment instance - */ - public GHCommitComment() { - } - private GHRepository owner; /** The commit id. */ - String body, html_url, commit_id; + String body, htmlUrl, commitId; /** The line. */ Integer line; @@ -40,33 +34,53 @@ public GHCommitComment() { GHUser user; // not fully populated. beware. /** - * Gets owner. + * Create default GHCommitComment instance + */ + public GHCommitComment() { + } + + /** + * Creates the reaction. * - * @return the owner + * @param content + * the content + * @return the GH reaction + * @throws IOException + * Signals that an I/O exception has occurred. */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHRepository getOwner() { - return owner; + public GHReaction createReaction(ReactionContent content) throws IOException { + return owner.root() + .createRequest() + .method("POST") + .with("content", content.getContent()) + .withUrlPath(getApiTail() + "/reactions") + .fetch(GHReaction.class); } /** - * URL like - * 'https://github.com/kohsuke/sandbox-ant/commit/8ae38db0ea5837313ab5f39d43a6f73de3bd9000#commitcomment-1252827' to - * show this commit comment in a browser. + * Deletes this comment. * - * @return the html url + * @throws IOException + * the io exception */ - public URL getHtmlUrl() { - return GitHubClient.parseURL(html_url); + public void delete() throws IOException { + owner.root().createRequest().method("DELETE").withUrlPath(getApiTail()).send(); } /** - * Gets sha 1. + * Delete reaction. * - * @return the sha 1 + * @param reaction + * the reaction + * @throws IOException + * Signals that an I/O exception has occurred. */ - public String getSHA1() { - return commit_id; + public void deleteReaction(GHReaction reaction) throws IOException { + owner.root() + .createRequest() + .method("DELETE") + .withUrlPath(getApiTail(), "reactions", String.valueOf(reaction.getId())) + .send(); } /** @@ -79,97 +93,75 @@ public String getBody() { } /** - * A commit comment can be on a specific line of a specific file, if so, this field points to a file. Otherwise - * null. + * Gets the commit to which this comment is associated with. * - * @return the path + * @return the commit + * @throws IOException + * the io exception */ - public String getPath() { - return path; + public GHCommit getCommit() throws IOException { + return getOwner().getCommit(getSHA1()); } /** - * A commit comment can be on a specific line of a specific file, if so, this field points to the line number in the - * file. Otherwise -1. + * URL like + * 'https://github.com/kohsuke/sandbox-ant/commit/8ae38db0ea5837313ab5f39d43a6f73de3bd9000#commitcomment-1252827' to + * show this commit comment in a browser. * - * @return the line + * @return the html url */ - public int getLine() { - return line != null ? line : -1; + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); } /** - * Gets the user who put this comment. + * A commit comment can be on a specific line of a specific file, if so, this field points to the line number in the + * file. Otherwise -1. * - * @return the user - * @throws IOException - * the io exception + * @return the line */ - public GHUser getUser() throws IOException { - return owner == null || owner.isOffline() ? user : owner.root().getUser(user.login); + public int getLine() { + return line != null ? line : -1; } /** - * Gets the commit to which this comment is associated with. + * Gets owner. * - * @return the commit - * @throws IOException - * the io exception + * @return the owner */ - public GHCommit getCommit() throws IOException { - return getOwner().getCommit(getSHA1()); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHRepository getOwner() { + return owner; } /** - * Updates the body of the commit message. + * A commit comment can be on a specific line of a specific file, if so, this field points to a file. Otherwise + * null. * - * @param body - * the body - * @throws IOException - * the io exception + * @return the path */ - public void update(String body) throws IOException { - owner.root() - .createRequest() - .method("PATCH") - .with("body", body) - .withUrlPath(getApiTail()) - .fetch(GHCommitComment.class); - this.body = body; + public String getPath() { + return path; } /** - * Creates the reaction. + * Gets sha 1. * - * @param content - * the content - * @return the GH reaction - * @throws IOException - * Signals that an I/O exception has occurred. + * @return the sha 1 */ - public GHReaction createReaction(ReactionContent content) throws IOException { - return owner.root() - .createRequest() - .method("POST") - .with("content", content.getContent()) - .withUrlPath(getApiTail() + "/reactions") - .fetch(GHReaction.class); + public String getSHA1() { + return commitId; } /** - * Delete reaction. + * Gets the user who put this comment. * - * @param reaction - * the reaction + * @return the user * @throws IOException - * Signals that an I/O exception has occurred. + * the io exception */ - public void deleteReaction(GHReaction reaction) throws IOException { - owner.root() - .createRequest() - .method("DELETE") - .withUrlPath(getApiTail(), "reactions", String.valueOf(reaction.getId())) - .send(); + public GHUser getUser() throws IOException { + return owner == null || owner.isOffline() ? user : owner.root().getUser(user.login); } /** @@ -185,13 +177,21 @@ public PagedIterable listReactions() { } /** - * Deletes this comment. + * Updates the body of the commit message. * + * @param body + * the body * @throws IOException * the io exception */ - public void delete() throws IOException { - owner.root().createRequest().method("DELETE").withUrlPath(getApiTail()).send(); + public void update(String body) throws IOException { + owner.root() + .createRequest() + .method("PATCH") + .with("body", body) + .withUrlPath(getApiTail()) + .fetch(GHCommitComment.class); + this.body = body; } private String getApiTail() { diff --git a/src/main/java/org/kohsuke/github/GHCommitFileIterable.java b/src/main/java/org/kohsuke/github/GHCommitFileIterable.java index 8a3f02a6fe..808f036017 100644 --- a/src/main/java/org/kohsuke/github/GHCommitFileIterable.java +++ b/src/main/java/org/kohsuke/github/GHCommitFileIterable.java @@ -21,9 +21,9 @@ class GHCommitFileIterable extends PagedIterable { */ private static final int GH_FILE_LIMIT_PER_COMMIT_PAGE = 300; + private final File[] files; private final GHRepository owner; private final String sha; - private final File[] files; /** * Instantiates a new GH commit iterable. diff --git a/src/main/java/org/kohsuke/github/GHCommitPointer.java b/src/main/java/org/kohsuke/github/GHCommitPointer.java index a466239729..41cb15114c 100644 --- a/src/main/java/org/kohsuke/github/GHCommitPointer.java +++ b/src/main/java/org/kohsuke/github/GHCommitPointer.java @@ -35,36 +35,34 @@ */ public class GHCommitPointer { + private String ref, sha, label; + + private GHRepository repo; + private GHUser user; /** * Create default GHCommitPointer instance */ public GHCommitPointer() { } - private String ref, sha, label; - private GHUser user; - private GHRepository repo; - /** - * This points to the user who owns the {@link #getRepository()}. + * Obtains the commit that this pointer is referring to. * - * @return the user + * @return the commit + * @throws IOException + * the io exception */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHUser getUser() { - if (user != null) - return user.root().intern(user); - return user; + public GHCommit getCommit() throws IOException { + return getRepository().getCommit(getSha()); } /** - * The repository that contains the commit. + * String that looks like "USERNAME:REF". * - * @return the repository + * @return the label */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHRepository getRepository() { - return repo; + public String getLabel() { + return label; } /** @@ -77,32 +75,34 @@ public String getRef() { } /** - * SHA1 of the commit. + * The repository that contains the commit. * - * @return the sha + * @return the repository */ - public String getSha() { - return sha; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHRepository getRepository() { + return repo; } /** - * String that looks like "USERNAME:REF". + * SHA1 of the commit. * - * @return the label + * @return the sha */ - public String getLabel() { - return label; + public String getSha() { + return sha; } /** - * Obtains the commit that this pointer is referring to. + * This points to the user who owns the {@link #getRepository()}. * - * @return the commit - * @throws IOException - * the io exception + * @return the user */ - public GHCommit getCommit() throws IOException { - return getRepository().getCommit(getSha()); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHUser getUser() { + if (user != null) + return user.root().intern(user); + return user; } } diff --git a/src/main/java/org/kohsuke/github/GHCommitQueryBuilder.java b/src/main/java/org/kohsuke/github/GHCommitQueryBuilder.java index e9b8f0cca8..8a03adb62f 100644 --- a/src/main/java/org/kohsuke/github/GHCommitQueryBuilder.java +++ b/src/main/java/org/kohsuke/github/GHCommitQueryBuilder.java @@ -1,5 +1,6 @@ package org.kohsuke.github; +import java.time.Instant; import java.util.Date; // TODO: Auto-generated Javadoc @@ -20,8 +21,8 @@ * @see GHRepository#queryCommits() GHRepository#queryCommits() */ public class GHCommitQueryBuilder { - private final Requester req; private final GHRepository repo; + private final Requester req; /** * Instantiates a new GH commit query builder. @@ -46,18 +47,6 @@ public GHCommitQueryBuilder author(String author) { return this; } - /** - * Only commits containing this file path will be returned. - * - * @param path - * the path - * @return the gh commit query builder - */ - public GHCommitQueryBuilder path(String path) { - req.with("path", path); - return this; - } - /** * Specifies the SHA1 commit / tag / branch / etc to start listing commits from. * @@ -70,6 +59,15 @@ public GHCommitQueryBuilder from(String ref) { return this; } + /** + * Lists up the commits with the criteria built so far. + * + * @return the paged iterable + */ + public PagedIterable list() { + return req.withUrlPath(repo.getApiTailUrl("commits")).toIterable(GHCommit[].class, item -> item.wrapUp(repo)); + } + /** * Page size gh commit query builder. * @@ -82,15 +80,40 @@ public GHCommitQueryBuilder pageSize(int pageSize) { return this; } + /** + * Only commits containing this file path will be returned. + * + * @param path + * the path + * @return the gh commit query builder + */ + public GHCommitQueryBuilder path(String path) { + req.with("path", path); + return this; + } + /** * Only commits after this date will be returned. * * @param dt * the dt * @return the gh commit query builder + * @deprecated use {@link #since(Instant)} */ + @Deprecated public GHCommitQueryBuilder since(Date dt) { - req.with("since", GitHubClient.printDate(dt)); + return since(GitHubClient.toInstantOrNull(dt)); + } + + /** + * Only commits after this date will be returned. + * + * @param dt + * the dt + * @return the gh commit query builder + */ + public GHCommitQueryBuilder since(Instant dt) { + req.with("since", GitHubClient.printInstant(dt)); return this; } @@ -102,7 +125,7 @@ public GHCommitQueryBuilder since(Date dt) { * @return the gh commit query builder */ public GHCommitQueryBuilder since(long timestamp) { - return since(new Date(timestamp)); + return since(Instant.ofEpochMilli(timestamp)); } /** @@ -111,29 +134,33 @@ public GHCommitQueryBuilder since(long timestamp) { * @param dt * the dt * @return the gh commit query builder + * @deprecated use {@link #until(Instant)} */ + @Deprecated public GHCommitQueryBuilder until(Date dt) { - req.with("until", GitHubClient.printDate(dt)); - return this; + return until(GitHubClient.toInstantOrNull(dt)); } /** * Only commits before this date will be returned. * - * @param timestamp - * the timestamp + * @param dt + * the dt * @return the gh commit query builder */ - public GHCommitQueryBuilder until(long timestamp) { - return until(new Date(timestamp)); + public GHCommitQueryBuilder until(Instant dt) { + req.with("until", GitHubClient.printInstant(dt)); + return this; } /** - * Lists up the commits with the criteria built so far. + * Only commits before this date will be returned. * - * @return the paged iterable + * @param timestamp + * the timestamp + * @return the gh commit query builder */ - public PagedIterable list() { - return req.withUrlPath(repo.getApiTailUrl("commits")).toIterable(GHCommit[].class, item -> item.wrapUp(repo)); + public GHCommitQueryBuilder until(long timestamp) { + return until(Instant.ofEpochMilli(timestamp)); } } diff --git a/src/main/java/org/kohsuke/github/GHCommitSearchBuilder.java b/src/main/java/org/kohsuke/github/GHCommitSearchBuilder.java index 28e34d66b8..3a1ddbffce 100644 --- a/src/main/java/org/kohsuke/github/GHCommitSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHCommitSearchBuilder.java @@ -15,69 +15,80 @@ public class GHCommitSearchBuilder extends GHSearchBuilder { /** - * Instantiates a new GH commit search builder. - * - * @param root - * the root + * The enum Sort. */ - GHCommitSearchBuilder(GitHub root) { - super(root, CommitSearchResult.class); + public enum Sort { + + /** The author date. */ + AUTHOR_DATE, + /** The committer date. */ + COMMITTER_DATE } - /** - * Search terms. - * - * @param term - * the term - * @return the GH commit search builder - */ - public GHCommitSearchBuilder q(String term) { - super.q(term); - return this; + @SuppressFBWarnings( + value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, + justification = "JSON API") + private static class CommitSearchResult extends SearchResult { + private GHCommit[] items; + + @Override + GHCommit[] getItems(GitHub root) { + for (GHCommit commit : items) { + String repoName = getRepoName(commit.url); + try { + GHRepository repo = root.getRepository(repoName); + commit.wrapUp(repo); + } catch (IOException ioe) { + } + } + return items; + } } /** - * Author gh commit search builder. - * - * @param v - * the v - * @return the gh commit search builder + * @param commitUrl + * a commit URL + * @return the repo name ("username/reponame") */ - public GHCommitSearchBuilder author(String v) { - return q("author:" + v); + private static String getRepoName(String commitUrl) { + if (StringUtils.isBlank(commitUrl)) { + return null; + } + int indexOfUsername = (GitHubClient.GITHUB_URL + "/repos/").length(); + String[] tokens = commitUrl.substring(indexOfUsername).split("/", 3); + return tokens[0] + '/' + tokens[1]; } /** - * Committer gh commit search builder. + * Instantiates a new GH commit search builder. * - * @param v - * the v - * @return the gh commit search builder + * @param root + * the root */ - public GHCommitSearchBuilder committer(String v) { - return q("committer:" + v); + GHCommitSearchBuilder(GitHub root) { + super(root, CommitSearchResult.class); } /** - * Author name gh commit search builder. + * Author gh commit search builder. * * @param v * the v * @return the gh commit search builder */ - public GHCommitSearchBuilder authorName(String v) { - return q("author-name:" + v); + public GHCommitSearchBuilder author(String v) { + return q("author:" + v); } /** - * Committer name gh commit search builder. + * Author date gh commit search builder. * * @param v * the v * @return the gh commit search builder */ - public GHCommitSearchBuilder committerName(String v) { - return q("committer-name:" + v); + public GHCommitSearchBuilder authorDate(String v) { + return q("author-date:" + v); } /** @@ -92,25 +103,25 @@ public GHCommitSearchBuilder authorEmail(String v) { } /** - * Committer email gh commit search builder. + * Author name gh commit search builder. * * @param v * the v * @return the gh commit search builder */ - public GHCommitSearchBuilder committerEmail(String v) { - return q("committer-email:" + v); + public GHCommitSearchBuilder authorName(String v) { + return q("author-name:" + v); } /** - * Author date gh commit search builder. + * Committer gh commit search builder. * * @param v * the v * @return the gh commit search builder */ - public GHCommitSearchBuilder authorDate(String v) { - return q("author-date:" + v); + public GHCommitSearchBuilder committer(String v) { + return q("committer:" + v); } /** @@ -125,69 +136,70 @@ public GHCommitSearchBuilder committerDate(String v) { } /** - * Merge gh commit search builder. + * Committer email gh commit search builder. * - * @param merge - * the merge + * @param v + * the v * @return the gh commit search builder */ - public GHCommitSearchBuilder merge(boolean merge) { - return q("merge:" + Boolean.valueOf(merge).toString().toLowerCase()); + public GHCommitSearchBuilder committerEmail(String v) { + return q("committer-email:" + v); } /** - * Hash gh commit search builder. + * Committer name gh commit search builder. * * @param v * the v * @return the gh commit search builder */ - public GHCommitSearchBuilder hash(String v) { - return q("hash:" + v); + public GHCommitSearchBuilder committerName(String v) { + return q("committer-name:" + v); } /** - * Parent gh commit search builder. + * Hash gh commit search builder. * * @param v * the v * @return the gh commit search builder */ - public GHCommitSearchBuilder parent(String v) { - return q("parent:" + v); + public GHCommitSearchBuilder hash(String v) { + return q("hash:" + v); } /** - * Tree gh commit search builder. + * Is gh commit search builder. * * @param v * the v * @return the gh commit search builder */ - public GHCommitSearchBuilder tree(String v) { - return q("tree:" + v); + public GHCommitSearchBuilder is(String v) { + return q("is:" + v); } /** - * Is gh commit search builder. + * Merge gh commit search builder. * - * @param v - * the v + * @param merge + * the merge * @return the gh commit search builder */ - public GHCommitSearchBuilder is(String v) { - return q("is:" + v); + public GHCommitSearchBuilder merge(boolean merge) { + return q("merge:" + Boolean.valueOf(merge).toString().toLowerCase()); } /** - * User gh commit search builder. + * Order gh commit search builder. * * @param v * the v * @return the gh commit search builder */ - public GHCommitSearchBuilder user(String v) { - return q("user:" + v); + public GHCommitSearchBuilder order(GHDirection v) { + req.with("order", v); + return this; } /** @@ -202,26 +214,37 @@ public GHCommitSearchBuilder org(String v) { } /** - * Repo gh commit search builder. + * Parent gh commit search builder. * * @param v * the v * @return the gh commit search builder */ - public GHCommitSearchBuilder repo(String v) { - return q("repo:" + v); + public GHCommitSearchBuilder parent(String v) { + return q("parent:" + v); } /** - * Order gh commit search builder. + * Search terms. + * + * @param term + * the term + * @return the GH commit search builder + */ + public GHCommitSearchBuilder q(String term) { + super.q(term); + return this; + } + + /** + * Repo gh commit search builder. * * @param v * the v * @return the gh commit search builder */ - public GHCommitSearchBuilder order(GHDirection v) { - req.with("order", v); - return this; + public GHCommitSearchBuilder repo(String v) { + return q("repo:" + v); } /** @@ -237,48 +260,25 @@ public GHCommitSearchBuilder sort(Sort sort) { } /** - * The enum Sort. + * Tree gh commit search builder. + * + * @param v + * the v + * @return the gh commit search builder */ - public enum Sort { - - /** The author date. */ - AUTHOR_DATE, - /** The committer date. */ - COMMITTER_DATE - } - - @SuppressFBWarnings( - value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, - justification = "JSON API") - private static class CommitSearchResult extends SearchResult { - private GHCommit[] items; - - @Override - GHCommit[] getItems(GitHub root) { - for (GHCommit commit : items) { - String repoName = getRepoName(commit.url); - try { - GHRepository repo = root.getRepository(repoName); - commit.wrapUp(repo); - } catch (IOException ioe) { - } - } - return items; - } + public GHCommitSearchBuilder tree(String v) { + return q("tree:" + v); } /** - * @param commitUrl - * a commit URL - * @return the repo name ("username/reponame") + * User gh commit search builder. + * + * @param v + * the v + * @return the gh commit search builder */ - private static String getRepoName(String commitUrl) { - if (StringUtils.isBlank(commitUrl)) { - return null; - } - int indexOfUsername = (GitHubClient.GITHUB_URL + "/repos/").length(); - String[] tokens = commitUrl.substring(indexOfUsername).split("/", 3); - return tokens[0] + '/' + tokens[1]; + public GHCommitSearchBuilder user(String v) { + return q("user:" + v); } /** diff --git a/src/main/java/org/kohsuke/github/GHCommitState.java b/src/main/java/org/kohsuke/github/GHCommitState.java index bdefc01446..7a89a3dc72 100644 --- a/src/main/java/org/kohsuke/github/GHCommitState.java +++ b/src/main/java/org/kohsuke/github/GHCommitState.java @@ -9,12 +9,12 @@ */ public enum GHCommitState { - /** The pending. */ - PENDING, - /** The success. */ - SUCCESS, /** The error. */ ERROR, /** The failure. */ - FAILURE + FAILURE, + /** The pending. */ + PENDING, + /** The success. */ + SUCCESS } diff --git a/src/main/java/org/kohsuke/github/GHCommitStatus.java b/src/main/java/org/kohsuke/github/GHCommitStatus.java index 880b10191b..fe61d61ea7 100644 --- a/src/main/java/org/kohsuke/github/GHCommitStatus.java +++ b/src/main/java/org/kohsuke/github/GHCommitStatus.java @@ -12,46 +12,40 @@ */ public class GHCommitStatus extends GHObject { - /** - * Create default GHCommitStatus instance - */ - public GHCommitStatus() { - } + /** The context. */ + String context; + + /** The creator. */ + GHUser creator; /** The state. */ String state; /** The description. */ - String target_url, description; - - /** The context. */ - String context; + String targetUrl, description; - /** The creator. */ - GHUser creator; + /** + * Create default GHCommitStatus instance + */ + public GHCommitStatus() { + } /** - * Gets state. + * Gets context. * - * @return the state + * @return the context */ - public GHCommitState getState() { - for (GHCommitState s : GHCommitState.values()) { - if (s.name().equalsIgnoreCase(state)) - return s; - } - throw new IllegalStateException("Unexpected state: " + state); + public String getContext() { + return context; } /** - * The URL that this status is linked to. - *

- * This is the URL specified when creating a commit status. + * Gets creator. * - * @return the target url + * @return the creator */ - public String getTargetUrl() { - return target_url; + public GHUser getCreator() { + return root().intern(creator); } /** @@ -64,21 +58,27 @@ public String getDescription() { } /** - * Gets creator. + * Gets state. * - * @return the creator + * @return the state */ - public GHUser getCreator() { - return root().intern(creator); + public GHCommitState getState() { + for (GHCommitState s : GHCommitState.values()) { + if (s.name().equalsIgnoreCase(state)) + return s; + } + throw new IllegalStateException("Unexpected state: " + state); } /** - * Gets context. + * The URL that this status is linked to. + *

+ * This is the URL specified when creating a commit status. * - * @return the context + * @return the target url */ - public String getContext() { - return context; + public String getTargetUrl() { + return targetUrl; } } diff --git a/src/main/java/org/kohsuke/github/GHCompare.java b/src/main/java/org/kohsuke/github/GHCompare.java index cba09389a1..48340fda36 100644 --- a/src/main/java/org/kohsuke/github/GHCompare.java +++ b/src/main/java/org/kohsuke/github/GHCompare.java @@ -18,208 +18,6 @@ */ public class GHCompare { - /** - * Create default GHCompare instance - */ - public GHCompare() { - } - - private String url, html_url, permalink_url, diff_url, patch_url; - private Status status; - private int ahead_by, behind_by, total_commits; - private Commit base_commit, merge_base_commit; - private Commit[] commits; - private GHCommit.File[] files; - - private GHRepository owner; - - @JacksonInject("GHCompare_usePaginatedCommits") - private boolean usePaginatedCommits; - - /** - * Gets url. - * - * @return the url - */ - public URL getUrl() { - return GitHubClient.parseURL(url); - } - - /** - * Gets html url. - * - * @return the html url - */ - public URL getHtmlUrl() { - return GitHubClient.parseURL(html_url); - } - - /** - * Gets permalink url. - * - * @return the permalink url - */ - public URL getPermalinkUrl() { - return GitHubClient.parseURL(permalink_url); - } - - /** - * Gets diff url. - * - * @return the diff url - */ - public URL getDiffUrl() { - return GitHubClient.parseURL(diff_url); - } - - /** - * Gets patch url. - * - * @return the patch url - */ - public URL getPatchUrl() { - return GitHubClient.parseURL(patch_url); - } - - /** - * Gets status. - * - * @return the status - */ - public Status getStatus() { - return status; - } - - /** - * Gets ahead by. - * - * @return the ahead by - */ - public int getAheadBy() { - return ahead_by; - } - - /** - * Gets behind by. - * - * @return the behind by - */ - public int getBehindBy() { - return behind_by; - } - - /** - * Gets total commits. - * - * @return the total commits - */ - public int getTotalCommits() { - return total_commits; - } - - /** - * Gets base commit. - * - * @return the base commit - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public Commit getBaseCommit() { - return base_commit; - } - - /** - * Gets merge base commit. - * - * @return the merge base commit - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public Commit getMergeBaseCommit() { - return merge_base_commit; - } - - /** - * Gets an array of commits. - * - * By default, the commit list is limited to 250 results. - * - * Since - * 2021-03-22, - * compare supports pagination of commits. This makes the initial {@link GHCompare} response return faster and - * supports comparisons with more than 250 commits. To read commits progressively using pagination, set - * {@link GHRepository#setCompareUsePaginatedCommits(boolean)} to true before calling - * {@link GHRepository#getCompare(String, String)}. - * - * @return A copy of the array being stored in the class. - */ - public Commit[] getCommits() { - try { - return listCommits().withPageSize(100).toArray(); - } catch (IOException e) { - throw new GHException(e.getMessage(), e); - } - } - - /** - * Iterable of commits for this comparison. - * - * By default, the commit list is limited to 250 results. - * - * Since - * 2021-03-22, - * compare supports pagination of commits. This makes the initial {@link GHCompare} response return faster and - * supports comparisons with more than 250 commits. To read commits progressively using pagination, set - * {@link GHRepository#setCompareUsePaginatedCommits(boolean)} to true before calling - * {@link GHRepository#getCompare(String, String)}. - * - * @return iterable of commits - */ - public PagedIterable listCommits() { - if (usePaginatedCommits) { - return new GHCompareCommitsIterable(); - } else { - // if not using paginated commits, adapt the returned commits array - return new PagedIterable() { - @Nonnull - @Override - public PagedIterator _iterator(int pageSize) { - return new PagedIterator<>(Collections.singleton(commits).iterator(), null); - } - }; - } - } - - /** - * Gets an array of files. - * - * By default, the file array is limited to 300 results. To retrieve the full list of files, iterate over each - * commit returned by {@link GHCompare#listCommits} and use {@link GHCommit#listFiles} to get the files for each - * commit. - * - * @return A copy of the array being stored in the class. - */ - public GHCommit.File[] getFiles() { - GHCommit.File[] newValue = new GHCommit.File[files.length]; - System.arraycopy(files, 0, newValue, 0, files.length); - return newValue; - } - - /** - * Wrap gh compare. - * - * @param owner - * the owner - * @return the gh compare - */ - GHCompare lateBind(GHRepository owner) { - this.owner = owner; - for (Commit commit : commits) { - commit.wrapUp(owner); - } - merge_base_commit.wrapUp(owner); - base_commit.wrapUp(owner); - return this; - } - /** * Compare commits had a child commit element with additional details we want to capture. This extension of GHCommit * provides that. @@ -228,14 +26,14 @@ GHCompare lateBind(GHRepository owner) { justification = "JSON API") public static class Commit extends GHCommit { + private InnerCommit commit; + /** * Create default Commit instance */ public Commit() { } - private InnerCommit commit; - /** * Gets commit. * @@ -251,32 +49,32 @@ public InnerCommit getCommit() { */ public static class InnerCommit { + private GitUser author, committer; + + private Tree tree; + private String url, sha, message; /** * Create default InnerCommit instance */ public InnerCommit() { } - private String url, sha, message; - private GitUser author, committer; - private Tree tree; - /** - * Gets url. + * Gets author. * - * @return the url + * @return the author */ - public String getUrl() { - return url; + public GitUser getAuthor() { + return author; } /** - * Gets sha. + * Gets committer. * - * @return the sha + * @return the committer */ - public String getSha() { - return sha; + public GitUser getCommitter() { + return committer; } /** @@ -289,53 +87,57 @@ public String getMessage() { } /** - * Gets author. + * Gets sha. * - * @return the author + * @return the sha */ - public GitUser getAuthor() { - return author; + public String getSha() { + return sha; } /** - * Gets committer. + * Gets tree. * - * @return the committer + * @return the tree */ - public GitUser getCommitter() { - return committer; + public Tree getTree() { + return tree; } /** - * Gets tree. + * Gets url. * - * @return the tree + * @return the url */ - public Tree getTree() { - return tree; + public String getUrl() { + return url; } } + /** + * The enum Status. + */ + public static enum Status { + /** The ahead. */ + ahead, + /** The behind. */ + behind, + /** The diverged. */ + diverged, + /** The identical. */ + identical + } /** * The type Tree. */ public static class Tree { - /** - * Create default Tree instance - */ - public Tree() { - } - private String url, sha; /** - * Gets url. - * - * @return the url + * Create default Tree instance */ - public String getUrl() { - return url; + public Tree() { } /** @@ -346,23 +148,16 @@ public String getUrl() { public String getSha() { return sha; } - } - - /** - * The enum Status. - */ - public static enum Status { - /** The behind. */ - behind, - /** The ahead. */ - ahead, - /** The identical. */ - identical, - /** The diverged. */ - diverged + /** + * Gets url. + * + * @return the url + */ + public String getUrl() { + return url; + } } - /** * Iterable for commit listing. */ @@ -424,4 +219,209 @@ public Commit[] next() { }; } } + private int aheadBy, behindBy, totalCommits; + private Commit baseCommit, mergeBaseCommit; + + private Commit[] commits; + + private GHCommit.File[] files; + + private GHRepository owner; + + private Status status; + + private String url, htmlUrl, permalinkUrl, diffUrl, patchUrl; + + @JacksonInject("GHCompare_usePaginatedCommits") + private boolean usePaginatedCommits; + + /** + * Create default GHCompare instance + */ + public GHCompare() { + } + + /** + * Gets ahead by. + * + * @return the ahead by + */ + public int getAheadBy() { + return aheadBy; + } + + /** + * Gets base commit. + * + * @return the base commit + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public Commit getBaseCommit() { + return baseCommit; + } + + /** + * Gets behind by. + * + * @return the behind by + */ + public int getBehindBy() { + return behindBy; + } + + /** + * Gets an array of commits. + * + * By default, the commit list is limited to 250 results. + * + * Since + * 2021-03-22, + * compare supports pagination of commits. This makes the initial {@link GHCompare} response return faster and + * supports comparisons with more than 250 commits. To read commits progressively using pagination, set + * {@link GHRepository#setCompareUsePaginatedCommits(boolean)} to true before calling + * {@link GHRepository#getCompare(String, String)}. + * + * @return A copy of the array being stored in the class. + */ + public Commit[] getCommits() { + try { + return listCommits().withPageSize(100).toArray(); + } catch (IOException e) { + throw new GHException(e.getMessage(), e); + } + } + + /** + * Gets diff url. + * + * @return the diff url + */ + public URL getDiffUrl() { + return GitHubClient.parseURL(diffUrl); + } + + /** + * Gets an array of files. + * + * By default, the file array is limited to 300 results. To retrieve the full list of files, iterate over each + * commit returned by {@link GHCompare#listCommits} and use {@link GHCommit#listFiles} to get the files for each + * commit. + * + * @return A copy of the array being stored in the class. + */ + public GHCommit.File[] getFiles() { + GHCommit.File[] newValue = new GHCommit.File[files.length]; + System.arraycopy(files, 0, newValue, 0, files.length); + return newValue; + } + + /** + * Gets html url. + * + * @return the html url + */ + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); + } + + /** + * Gets merge base commit. + * + * @return the merge base commit + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public Commit getMergeBaseCommit() { + return mergeBaseCommit; + } + + /** + * Gets patch url. + * + * @return the patch url + */ + public URL getPatchUrl() { + return GitHubClient.parseURL(patchUrl); + } + + /** + * Gets permalink url. + * + * @return the permalink url + */ + public URL getPermalinkUrl() { + return GitHubClient.parseURL(permalinkUrl); + } + + /** + * Gets status. + * + * @return the status + */ + public Status getStatus() { + return status; + } + + /** + * Gets total commits. + * + * @return the total commits + */ + public int getTotalCommits() { + return totalCommits; + } + + /** + * Gets url. + * + * @return the url + */ + public URL getUrl() { + return GitHubClient.parseURL(url); + } + + /** + * Iterable of commits for this comparison. + * + * By default, the commit list is limited to 250 results. + * + * Since + * 2021-03-22, + * compare supports pagination of commits. This makes the initial {@link GHCompare} response return faster and + * supports comparisons with more than 250 commits. To read commits progressively using pagination, set + * {@link GHRepository#setCompareUsePaginatedCommits(boolean)} to true before calling + * {@link GHRepository#getCompare(String, String)}. + * + * @return iterable of commits + */ + public PagedIterable listCommits() { + if (usePaginatedCommits) { + return new GHCompareCommitsIterable(); + } else { + // if not using paginated commits, adapt the returned commits array + return new PagedIterable() { + @Nonnull + @Override + public PagedIterator _iterator(int pageSize) { + return new PagedIterator<>(Collections.singleton(commits).iterator(), null); + } + }; + } + } + + /** + * Wrap gh compare. + * + * @param owner + * the owner + * @return the gh compare + */ + GHCompare lateBind(GHRepository owner) { + this.owner = owner; + for (Commit commit : commits) { + commit.wrapUp(owner); + } + mergeBaseCommit.wrapUp(owner); + baseCommit.wrapUp(owner); + return this; + } } diff --git a/src/main/java/org/kohsuke/github/GHContent.java b/src/main/java/org/kohsuke/github/GHContent.java index b25a786504..98d94da4ab 100644 --- a/src/main/java/org/kohsuke/github/GHContent.java +++ b/src/main/java/org/kohsuke/github/GHContent.java @@ -19,101 +19,111 @@ public class GHContent extends GitHubInteractiveObject implements Refreshable { /** - * Create default GHContent instance + * Gets the api route. + * + * @param repository + * the repository + * @param path + * the path + * @return the api route */ - public GHContent() { + static String getApiRoute(GHRepository repository, String path) { + return repository.getApiTailUrl("contents/" + path); } + private String content; + + private String downloadUrl; + private String encoding; + private String gitUrl; // this is the Blob url + private String htmlUrl; // this is the UI + private String name; + private String path; /* * In normal use of this class, repository field is set via wrap(), but in the code search API, there's a nested * 'repository' field that gets populated from JSON. */ private GHRepository repository; - - private String type; - private String encoding; - private long size; private String sha; - private String name; - private String path; + private long size; private String target; - private String content; + private String type; private String url; // this is the API url - private String git_url; // this is the Blob url - private String html_url; // this is the UI - private String download_url; /** - * Gets owner. - * - * @return the owner + * Create default GHContent instance */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHRepository getOwner() { - return repository; + public GHContent() { } /** - * Gets type. + * Creates a builder that can be used to delete this file. * - * @return the type - */ - public String getType() { - return type; - } - - /** - * Gets encoding. + *

+ * Unlike the {@link #delete(String)} convenience methods, this builder supports setting the author and committer of + * the resulting commit. * - * @return the encoding + * @return a content deleter + * @see GHContentDeleter */ - public String getEncoding() { - return encoding; + public GHContentDeleter createDelete() { + return new GHContentDeleter(this); } /** - * Gets size. + * Creates a builder that can be used to update this file's content. * - * @return the size - */ - public long getSize() { - return size; - } - - /** - * Gets sha. + *

+ * Unlike the {@link #update(String, String)} convenience methods, this builder supports setting the author and + * committer of the resulting commit. * - * @return the sha + * @return a content updater + * @see GHContentUpdater */ - public String getSha() { - return sha; + public GHContentUpdater createUpdate() { + return new GHContentUpdater(this); } /** - * Gets name. + * Delete gh content update response. * - * @return the name + * @param message + * the message + * @return the gh content update response + * @throws IOException + * the io exception */ - public String getName() { - return name; + public GHContentUpdateResponse delete(String message) throws IOException { + return delete(message, null); } /** - * Gets path. + * Delete gh content update response. * - * @return the path + * @param commitMessage + * the commit message + * @param branch + * the branch + * @return the gh content update response + * @throws IOException + * the io exception */ - public String getPath() { - return path; - } + public GHContentUpdateResponse delete(String commitMessage, String branch) throws IOException { + Requester requester = root().createRequest() + .method("DELETE") + .with("path", path) + .with("message", commitMessage) + .with("sha", sha); - /** - * Gets target of a symlink. This will only be set if {@code "symlink".equals(getType())} - * - * @return the target - */ - public String getTarget() { - return target; + if (branch != null) { + requester.with("branch", branch); + } + + GHContentUpdateResponse response = requester.withUrlPath(getApiRoute(repository, path)) + .fetch(GHContentUpdateResponse.class); + + response.getCommit().wrapUp(repository); + return response; } /** @@ -134,6 +144,18 @@ public String getContent() throws IOException { return new String(readDecodedContent()); } + /** + * URL to retrieve the raw content of the file. Null if this is a directory. + * + * @return the download url + * @throws IOException + * the io exception + */ + public String getDownloadUrl() throws IOException { + refresh(downloadUrl); + return downloadUrl; + } + /** * Retrieve the base64-encoded content that is stored at this location. * @@ -153,12 +175,12 @@ public String getEncodedContent() throws IOException { } /** - * Gets url. + * Gets encoding. * - * @return the url + * @return the encoding */ - public String getUrl() { - return url; + public String getEncoding() { + return encoding; } /** @@ -167,7 +189,7 @@ public String getUrl() { * @return the git url */ public String getGitUrl() { - return git_url; + return gitUrl; } /** @@ -176,60 +198,80 @@ public String getGitUrl() { * @return the html url */ public String getHtmlUrl() { - return html_url; + return htmlUrl; } /** - * Retrieves the actual bytes of the blob. + * Gets name. * - * @return the input stream - * @throws IOException - * the io exception + * @return the name */ - public InputStream read() throws IOException { - return new ByteArrayInputStream(readDecodedContent()); + public String getName() { + return name; } /** - * Retrieves the decoded bytes of the blob. + * Gets owner. * - * @return the input stream - * @throws IOException - * the io exception + * @return the owner */ - private byte[] readDecodedContent() throws IOException { - String encodedContent = getEncodedContent(); - if (encoding.equals("base64")) { - try { - Base64.Decoder decoder = Base64.getMimeDecoder(); - return decoder.decode(encodedContent.getBytes(StandardCharsets.US_ASCII)); - } catch (IllegalArgumentException e) { - throw new AssertionError(e); // US-ASCII is mandatory - } - } + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHRepository getOwner() { + return repository; + } - throw new UnsupportedOperationException("Unrecognized encoding: " + encoding); + /** + * Gets path. + * + * @return the path + */ + public String getPath() { + return path; } /** - * URL to retrieve the raw content of the file. Null if this is a directory. + * Gets sha. * - * @return the download url - * @throws IOException - * the io exception + * @return the sha */ - public String getDownloadUrl() throws IOException { - refresh(download_url); - return download_url; + public String getSha() { + return sha; } /** - * Is file boolean. + * Gets size. * - * @return the boolean + * @return the size */ - public boolean isFile() { - return "file".equals(type); + public long getSize() { + return size; + } + + /** + * Gets target of a symlink. This will only be set if {@code "symlink".equals(getType())} + * + * @return the target + */ + public String getTarget() { + return target; + } + + /** + * Gets type. + * + * @return the type + */ + public String getType() { + return type; + } + + /** + * Gets url. + * + * @return the url + */ + public String getUrl() { + return url; } /** @@ -242,15 +284,12 @@ public boolean isDirectory() { } /** - * Fully populate the data by retrieving missing data. - *

- * Depending on the original API call where this object is created, it may not contain everything. + * Is file boolean. * - * @throws IOException - * the io exception + * @return the boolean */ - protected synchronized void populate() throws IOException { - root().createRequest().withUrlPath(url).fetchInto(this); + public boolean isFile() { + return "file".equals(type); } /** @@ -265,6 +304,30 @@ public PagedIterable listDirectoryContent() { return root().createRequest().setRawUrlPath(url).toIterable(GHContent[].class, item -> item.wrap(repository)); } + /** + * Retrieves the actual bytes of the blob. + * + * @return the input stream + * @throws IOException + * the io exception + */ + public InputStream read() throws IOException { + return new ByteArrayInputStream(readDecodedContent()); + } + + /** + * Fully populate the data by retrieving missing data. + * + * Depending on the original API call where this object is created, it may not contain everything. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Override + public synchronized void refresh() throws IOException { + root().createRequest().setRawUrlPath(url).fetchInto(this); + } + /** * Update gh content update response. * @@ -353,58 +416,36 @@ public GHContentUpdateResponse update(byte[] newContentBytes, String commitMessa } /** - * Delete gh content update response. - * - * @param message - * the message - * @return the gh content update response - * @throws IOException - * the io exception - */ - public GHContentUpdateResponse delete(String message) throws IOException { - return delete(message, null); - } - - /** - * Delete gh content update response. + * Retrieves the decoded bytes of the blob. * - * @param commitMessage - * the commit message - * @param branch - * the branch - * @return the gh content update response + * @return the input stream * @throws IOException * the io exception */ - public GHContentUpdateResponse delete(String commitMessage, String branch) throws IOException { - Requester requester = root().createRequest() - .method("DELETE") - .with("path", path) - .with("message", commitMessage) - .with("sha", sha); - - if (branch != null) { - requester.with("branch", branch); + private byte[] readDecodedContent() throws IOException { + String encodedContent = getEncodedContent(); + if (encoding.equals("base64")) { + try { + Base64.Decoder decoder = Base64.getMimeDecoder(); + return decoder.decode(encodedContent.getBytes(StandardCharsets.US_ASCII)); + } catch (IllegalArgumentException e) { + throw new AssertionError(e); // US-ASCII is mandatory + } } - GHContentUpdateResponse response = requester.withUrlPath(getApiRoute(repository, path)) - .fetch(GHContentUpdateResponse.class); - - response.getCommit().wrapUp(repository); - return response; + throw new UnsupportedOperationException("Unrecognized encoding: " + encoding); } /** - * Gets the api route. + * Fully populate the data by retrieving missing data. + *

+ * Depending on the original API call where this object is created, it may not contain everything. * - * @param repository - * the repository - * @param path - * the path - * @return the api route + * @throws IOException + * the io exception */ - static String getApiRoute(GHRepository repository, String path) { - return repository.getApiTailUrl("contents/" + path); + protected synchronized void populate() throws IOException { + root().createRequest().withUrlPath(url).fetchInto(this); } /** @@ -418,17 +459,4 @@ GHContent wrap(GHRepository owner) { this.repository = owner; return this; } - - /** - * Fully populate the data by retrieving missing data. - * - * Depending on the original API call where this object is created, it may not contain everything. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Override - public synchronized void refresh() throws IOException { - root().createRequest().setRawUrlPath(url).fetchInto(this); - } } diff --git a/src/main/java/org/kohsuke/github/GHContentBuilder.java b/src/main/java/org/kohsuke/github/GHContentBuilder.java index 9b24af92b0..3ae0e8a941 100644 --- a/src/main/java/org/kohsuke/github/GHContentBuilder.java +++ b/src/main/java/org/kohsuke/github/GHContentBuilder.java @@ -1,8 +1,13 @@ package org.kohsuke.github; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.time.Instant; import java.util.Base64; +import java.util.Date; // TODO: Auto-generated Javadoc /** @@ -15,9 +20,22 @@ * @see GHRepository#createContent() GHRepository#createContent() */ public final class GHContentBuilder { + @JsonInclude(Include.NON_NULL) + private static final class UserInfo { + private final String date; + private final String email; + private final String name; + + private UserInfo(String name, String email, Instant date) { + this.name = name; + this.email = email; + this.date = date != null ? GitHubClient.printInstant(date) : null; + } + } + + private String path; private final GHRepository repo; private final Requester req; - private String path; /** * Instantiates a new GH content builder. @@ -31,15 +49,48 @@ public final class GHContentBuilder { } /** - * Path gh content builder. + * Configures the author of the commit. If not specified, the authenticated user is used as the author. * - * @param path - * the path + * @param name + * the name of the author + * @param email + * the email of the author * @return the gh content builder */ - public GHContentBuilder path(String path) { - this.path = path; - req.with("path", path); + public GHContentBuilder author(String name, String email) { + return author(name, email, (Instant) null); + } + + /** + * Configures the author of the commit. If not specified, the authenticated user is used as the author. + * + * @param name + * the name of the author + * @param email + * the email of the author + * @param date + * the date of the authoring + * @return the gh content builder + * @deprecated use {@link #author(String, String, Instant)} instead + */ + @Deprecated + public GHContentBuilder author(String name, String email, Date date) { + return author(name, email, GitHubClient.toInstantOrNull(date)); + } + + /** + * Configures the author of the commit. If not specified, the authenticated user is used as the author. + * + * @param name + * the name of the author + * @param email + * the email of the author + * @param date + * the timestamp for the authoring + * @return the gh content builder + */ + public GHContentBuilder author(String name, String email, Instant date) { + req.with("author", new UserInfo(name, email, date)); return this; } @@ -56,14 +107,65 @@ public GHContentBuilder branch(String branch) { } /** - * Used when updating (but not creating a new content) to specify the blob SHA of the file being replaced. + * Commits a new content. * - * @param sha - * the sha + * @return the gh content update response + * @throws IOException + * the io exception + */ + public GHContentUpdateResponse commit() throws IOException { + GHContentUpdateResponse response = req.withUrlPath(GHContent.getApiRoute(repo, path)) + .fetch(GHContentUpdateResponse.class); + + response.getContent().wrap(repo); + response.getCommit().wrapUp(repo); + + return response; + } + + /** + * Configures the committer of the commit. If not specified, the authenticated user is used as the committer. + * + * @param name + * the name of the committer + * @param email + * the email of the committer * @return the gh content builder */ - public GHContentBuilder sha(String sha) { - req.with("sha", sha); + public GHContentBuilder committer(String name, String email) { + return committer(name, email, (Instant) null); + } + + /** + * Configures the committer of the commit. If not specified, the authenticated user is used as the committer. + * + * @param name + * the name of the committer + * @param email + * the email of the committer + * @param date + * the date of the commit + * @return the gh content builder + * @deprecated use {@link #committer(String, String, Instant)} instead + */ + @Deprecated + public GHContentBuilder committer(String name, String email, Date date) { + return committer(name, email, GitHubClient.toInstantOrNull(date)); + } + + /** + * Configures the committer of the commit. If not specified, the authenticated user is used as the committer. + * + * @param name + * the name of the committer + * @param email + * the email of the committer + * @param date + * the timestamp of the commit + * @return the gh content builder + */ + public GHContentBuilder committer(String name, String email, Instant date) { + req.with("committer", new UserInfo(name, email, date)); return this; } @@ -74,9 +176,8 @@ public GHContentBuilder sha(String sha) { * the content * @return the gh content builder */ - public GHContentBuilder content(byte[] content) { - req.with("content", Base64.getEncoder().encodeToString(content)); - return this; + public GHContentBuilder content(String content) { + return content(content.getBytes(StandardCharsets.UTF_8)); } /** @@ -86,8 +187,9 @@ public GHContentBuilder content(byte[] content) { * the content * @return the gh content builder */ - public GHContentBuilder content(String content) { - return content(content.getBytes(StandardCharsets.UTF_8)); + public GHContentBuilder content(byte[] content) { + req.with("content", Base64.getEncoder().encodeToString(content)); + return this; } /** @@ -103,19 +205,27 @@ public GHContentBuilder message(String commitMessage) { } /** - * Commits a new content. + * Path gh content builder. * - * @return the gh content update response - * @throws IOException - * the io exception + * @param path + * the path + * @return the gh content builder */ - public GHContentUpdateResponse commit() throws IOException { - GHContentUpdateResponse response = req.withUrlPath(GHContent.getApiRoute(repo, path)) - .fetch(GHContentUpdateResponse.class); - - response.getContent().wrap(repo); - response.getCommit().wrapUp(repo); + public GHContentBuilder path(String path) { + this.path = path; + req.with("path", path); + return this; + } - return response; + /** + * Used when updating (but not creating a new content) to specify the blob SHA of the file being replaced. + * + * @param sha + * the sha + * @return the gh content builder + */ + public GHContentBuilder sha(String sha) { + req.with("sha", sha); + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHContentDeleter.java b/src/main/java/org/kohsuke/github/GHContentDeleter.java new file mode 100644 index 0000000000..03d4c31e28 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHContentDeleter.java @@ -0,0 +1,177 @@ +package org.kohsuke.github; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +import java.io.IOException; +import java.time.Instant; +import java.util.Date; + +/** + * Builder for deleting repository content with support for specifying author and committer information. + * + *

+ * Obtain an instance via {@link GHContent#createDelete()}. + * + * @see GHContent#createDelete() + */ +public final class GHContentDeleter { + @JsonInclude(Include.NON_NULL) + private static final class UserInfo { + private final String date; + private final String email; + private final String name; + + private UserInfo(String name, String email, Instant date) { + this.name = name; + this.email = email; + this.date = date != null ? GitHubClient.printInstant(date) : null; + } + } + + private final GHContent content; + private final Requester req; + + GHContentDeleter(GHContent content) { + this.content = content; + final GHRepository repository = content.getOwner(); + this.req = repository.root() + .createRequest() + .method("DELETE") + .inBody() + .with("path", content.getPath()) + .with("sha", content.getSha()); + } + + /** + * Configures the author of the commit. If not specified, the authenticated user is used as the author. + * + * @param name + * the name of the author + * @param email + * the email of the author + * @return this deleter + */ + public GHContentDeleter author(String name, String email) { + return author(name, email, (Instant) null); + } + + /** + * Configures the author of the commit. If not specified, the authenticated user is used as the author. + * + * @param name + * the name of the author + * @param email + * the email of the author + * @param date + * the date of the authoring + * @return this deleter + * @deprecated use {@link #author(String, String, Instant)} instead + */ + @Deprecated + public GHContentDeleter author(String name, String email, Date date) { + return author(name, email, GitHubClient.toInstantOrNull(date)); + } + + /** + * Configures the author of the commit. If not specified, the authenticated user is used as the author. + * + * @param name + * the name of the author + * @param email + * the email of the author + * @param date + * the timestamp for the authoring + * @return this deleter + */ + public GHContentDeleter author(String name, String email, Instant date) { + req.with("author", new UserInfo(name, email, date)); + return this; + } + + /** + * Sets the branch to delete the content from. + * + * @param branch + * the branch name + * @return this deleter + */ + public GHContentDeleter branch(String branch) { + req.with("branch", branch); + return this; + } + + /** + * Commits the deletion. + * + * @return the response containing the commit information + * @throws IOException + * the io exception + */ + public GHContentUpdateResponse commit() throws IOException { + final GHRepository repository = content.getOwner(); + GHContentUpdateResponse response = req.withUrlPath(GHContent.getApiRoute(repository, content.getPath())) + .fetch(GHContentUpdateResponse.class); + + response.getCommit().wrapUp(repository); + return response; + } + + /** + * Configures the committer of the commit. If not specified, the authenticated user is used as the committer. + * + * @param name + * the name of the committer + * @param email + * the email of the committer + * @return this deleter + */ + public GHContentDeleter committer(String name, String email) { + return committer(name, email, (Instant) null); + } + + /** + * Configures the committer of the commit. If not specified, the authenticated user is used as the committer. + * + * @param name + * the name of the committer + * @param email + * the email of the committer + * @param date + * the date of the commit + * @return this deleter + * @deprecated use {@link #committer(String, String, Instant)} instead + */ + @Deprecated + public GHContentDeleter committer(String name, String email, Date date) { + return committer(name, email, GitHubClient.toInstantOrNull(date)); + } + + /** + * Configures the committer of the commit. If not specified, the authenticated user is used as the committer. + * + * @param name + * the name of the committer + * @param email + * the email of the committer + * @param date + * the timestamp of the commit + * @return this deleter + */ + public GHContentDeleter committer(String name, String email, Instant date) { + req.with("committer", new UserInfo(name, email, date)); + return this; + } + + /** + * Sets the commit message. + * + * @param message + * the commit message + * @return this deleter + */ + public GHContentDeleter message(String message) { + req.with("message", message); + return this; + } +} diff --git a/src/main/java/org/kohsuke/github/GHContentSearchBuilder.java b/src/main/java/org/kohsuke/github/GHContentSearchBuilder.java index e29449e6bb..bdd16cea3e 100644 --- a/src/main/java/org/kohsuke/github/GHContentSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHContentSearchBuilder.java @@ -10,53 +10,55 @@ public class GHContentSearchBuilder extends GHSearchBuilder { /** - * Instantiates a new GH content search builder. - * - * @param root - * the root + * The enum Sort. */ - GHContentSearchBuilder(GitHub root) { - super(root, ContentSearchResult.class); + public enum Sort { + + /** The best match. */ + BEST_MATCH, + /** The indexed. */ + INDEXED } - /** - * {@inheritDoc} - */ - @Override - public GHContentSearchBuilder q(String term) { - super.q(term); - return this; + private static class ContentSearchResult extends SearchResult { + private GHContent[] items; + + @Override + GHContent[] getItems(GitHub root) { + return items; + } } /** - * {@inheritDoc} + * Instantiates a new GH content search builder. + * + * @param root + * the root */ - @Override - GHContentSearchBuilder q(String qualifier, String value) { - super.q(qualifier, value); - return this; + GHContentSearchBuilder(GitHub root) { + super(root, ContentSearchResult.class); } /** - * In gh content search builder. + * Extension gh content search builder. * * @param v * the v * @return the gh content search builder */ - public GHContentSearchBuilder in(String v) { - return q("in:" + v); + public GHContentSearchBuilder extension(String v) { + return q("extension:" + v); } /** - * Language gh content search builder. + * Filename gh content search builder. * * @param v * the v * @return the gh content search builder */ - public GHContentSearchBuilder language(String v) { - return q("language:" + v); + public GHContentSearchBuilder filename(String v) { + return q("filename:" + v); } /** @@ -76,58 +78,57 @@ public GHContentSearchBuilder fork(GHFork fork) { } /** - * Size gh content search builder. + * In gh content search builder. * * @param v * the v * @return the gh content search builder */ - public GHContentSearchBuilder size(String v) { - return q("size:" + v); + public GHContentSearchBuilder in(String v) { + return q("in:" + v); } /** - * Path gh content search builder. + * Language gh content search builder. * * @param v * the v * @return the gh content search builder */ - public GHContentSearchBuilder path(String v) { - return q("path:" + v); + public GHContentSearchBuilder language(String v) { + return q("language:" + v); } /** - * Filename gh content search builder. + * Order gh content search builder. * * @param v * the v * @return the gh content search builder */ - public GHContentSearchBuilder filename(String v) { - return q("filename:" + v); + public GHContentSearchBuilder order(GHDirection v) { + req.with("order", v); + return this; } /** - * Extension gh content search builder. + * Path gh content search builder. * * @param v * the v * @return the gh content search builder */ - public GHContentSearchBuilder extension(String v) { - return q("extension:" + v); + public GHContentSearchBuilder path(String v) { + return q("path:" + v); } /** - * User gh content search builder. - * - * @param v - * the v - * @return the gh content search builder + * {@inheritDoc} */ - public GHContentSearchBuilder user(String v) { - return q("user:" + v); + @Override + public GHContentSearchBuilder q(String term) { + super.q(term); + return this; } /** @@ -142,15 +143,14 @@ public GHContentSearchBuilder repo(String v) { } /** - * Order gh content search builder. + * Size gh content search builder. * * @param v * the v * @return the gh content search builder */ - public GHContentSearchBuilder order(GHDirection v) { - req.with("order", v); - return this; + public GHContentSearchBuilder size(String v) { + return q("size:" + v); } /** @@ -170,23 +170,14 @@ public GHContentSearchBuilder sort(GHContentSearchBuilder.Sort sort) { } /** - * The enum Sort. + * User gh content search builder. + * + * @param v + * the v + * @return the gh content search builder */ - public enum Sort { - - /** The best match. */ - BEST_MATCH, - /** The indexed. */ - INDEXED - } - - private static class ContentSearchResult extends SearchResult { - private GHContent[] items; - - @Override - GHContent[] getItems(GitHub root) { - return items; - } + public GHContentSearchBuilder user(String v) { + return q("user:" + v); } /** @@ -198,4 +189,13 @@ GHContent[] getItems(GitHub root) { protected String getApiUrl() { return "/search/code"; } + + /** + * {@inheritDoc} + */ + @Override + GHContentSearchBuilder q(String qualifier, String value) { + super.q(qualifier, value); + return this; + } } diff --git a/src/main/java/org/kohsuke/github/GHContentUpdateResponse.java b/src/main/java/org/kohsuke/github/GHContentUpdateResponse.java index 3703023140..193ed2bd1f 100644 --- a/src/main/java/org/kohsuke/github/GHContentUpdateResponse.java +++ b/src/main/java/org/kohsuke/github/GHContentUpdateResponse.java @@ -8,33 +8,33 @@ */ public class GHContentUpdateResponse { + private GitCommit commit; + + private GHContent content; /** * Create default GHContentUpdateResponse instance */ public GHContentUpdateResponse() { } - private GHContent content; - private GitCommit commit; - /** - * Gets content. + * Gets commit. * - * @return the content + * @return the commit */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHContent getContent() { - return content; + public GitCommit getCommit() { + return commit; } /** - * Gets commit. + * Gets content. * - * @return the commit + * @return the content */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GitCommit getCommit() { - return commit; + public GHContent getContent() { + return content; } } diff --git a/src/main/java/org/kohsuke/github/GHContentUpdater.java b/src/main/java/org/kohsuke/github/GHContentUpdater.java new file mode 100644 index 0000000000..41111af339 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHContentUpdater.java @@ -0,0 +1,205 @@ +package org.kohsuke.github; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.Base64; +import java.util.Date; + +/** + * Builder for updating existing repository content with support for specifying author and committer information. + * + *

+ * Obtain an instance via {@link GHContent#createUpdate()}. + * + * @see GHContent#createUpdate() + */ +public final class GHContentUpdater { + @JsonInclude(Include.NON_NULL) + private static final class UserInfo { + private final String date; + private final String email; + private final String name; + + private UserInfo(String name, String email, Instant date) { + this.name = name; + this.email = email; + this.date = date != null ? GitHubClient.printInstant(date) : null; + } + } + + private final GHContent content; + private String encodedContent; + private final Requester req; + + GHContentUpdater(GHContent content) { + this.content = content; + final GHRepository repository = content.getOwner(); + this.req = repository.root() + .createRequest() + .method("PUT") + .with("path", content.getPath()) + .with("sha", content.getSha()); + } + + /** + * Configures the author of the commit. If not specified, the authenticated user is used as the author. + * + * @param name + * the name of the author + * @param email + * the email of the author + * @return this updater + */ + public GHContentUpdater author(String name, String email) { + return author(name, email, (Instant) null); + } + + /** + * Configures the author of the commit. If not specified, the authenticated user is used as the author. + * + * @param name + * the name of the author + * @param email + * the email of the author + * @param date + * the date of the authoring + * @return this updater + * @deprecated use {@link #author(String, String, Instant)} instead + */ + @Deprecated + public GHContentUpdater author(String name, String email, Date date) { + return author(name, email, GitHubClient.toInstantOrNull(date)); + } + + /** + * Configures the author of the commit. If not specified, the authenticated user is used as the author. + * + * @param name + * the name of the author + * @param email + * the email of the author + * @param date + * the timestamp for the authoring + * @return this updater + */ + public GHContentUpdater author(String name, String email, Instant date) { + req.with("author", new UserInfo(name, email, date)); + return this; + } + + /** + * Sets the branch to update the content on. + * + * @param branch + * the branch name + * @return this updater + */ + public GHContentUpdater branch(String branch) { + req.with("branch", branch); + return this; + } + + /** + * Commits the update. + * + * @return the response containing the updated content and commit information + * @throws IOException + * the io exception + */ + public GHContentUpdateResponse commit() throws IOException { + final GHRepository repository = content.getOwner(); + GHContentUpdateResponse response = req.withUrlPath(GHContent.getApiRoute(repository, content.getPath())) + .fetch(GHContentUpdateResponse.class); + + response.getContent().wrap(repository); + response.getCommit().wrapUp(repository); + + return response; + } + + /** + * Configures the committer of the commit. If not specified, the authenticated user is used as the committer. + * + * @param name + * the name of the committer + * @param email + * the email of the committer + * @return this updater + */ + public GHContentUpdater committer(String name, String email) { + return committer(name, email, (Instant) null); + } + + /** + * Configures the committer of the commit. If not specified, the authenticated user is used as the committer. + * + * @param name + * the name of the committer + * @param email + * the email of the committer + * @param date + * the date of the commit + * @return this updater + * @deprecated use {@link #committer(String, String, Instant)} instead + */ + @Deprecated + public GHContentUpdater committer(String name, String email, Date date) { + return committer(name, email, GitHubClient.toInstantOrNull(date)); + } + + /** + * Configures the committer of the commit. If not specified, the authenticated user is used as the committer. + * + * @param name + * the name of the committer + * @param email + * the email of the committer + * @param date + * the timestamp of the commit + * @return this updater + */ + public GHContentUpdater committer(String name, String email, Instant date) { + req.with("committer", new UserInfo(name, email, date)); + return this; + } + + /** + * Sets the new file content as a string. + * + * @param newContent + * the new content + * @return this updater + */ + public GHContentUpdater content(String newContent) { + return content(newContent.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Sets the new file content as raw bytes. + * + * @param newContent + * the new content bytes + * @return this updater + */ + public GHContentUpdater content(byte[] newContent) { + this.encodedContent = Base64.getEncoder().encodeToString(newContent); + req.with("content", encodedContent); + return this; + } + + /** + * Sets the commit message. + * + * @param message + * the commit message + * @return this updater + */ + public GHContentUpdater message(String message) { + req.with("message", message); + return this; + } +} diff --git a/src/main/java/org/kohsuke/github/GHCreateRepositoryBuilder.java b/src/main/java/org/kohsuke/github/GHCreateRepositoryBuilder.java index 15bceb2ebd..c4957b1072 100644 --- a/src/main/java/org/kohsuke/github/GHCreateRepositoryBuilder.java +++ b/src/main/java/org/kohsuke/github/GHCreateRepositoryBuilder.java @@ -33,109 +33,122 @@ public GHCreateRepositoryBuilder(String name, GitHub root, String apiTail) { } /** - * Creates a default .gitignore + * If true, create an initial commit with empty README. * - * @param language - * template to base the ignore file on - * @return a builder to continue with building See https://developer.github.com/v3/repos/#create + * @param enabled + * true if enabled + * @return a builder to continue with building * @throws IOException * In case of any networking error or error from the server. */ - public GHCreateRepositoryBuilder gitignoreTemplate(String language) throws IOException { - return with("gitignore_template", language); + public GHCreateRepositoryBuilder autoInit(boolean enabled) throws IOException { + return with("auto_init", enabled); } /** - * Desired license template to apply. + * Creates a repository with all the parameters. * - * @param license - * template to base the license file on - * @return a builder to continue with building See https://developer.github.com/v3/repos/#create + * @return the gh repository * @throws IOException - * In case of any networking error or error from the server. + * if repository cannot be created */ - public GHCreateRepositoryBuilder licenseTemplate(String license) throws IOException { - return with("license_template", license); + public GHRepository create() throws IOException { + return done(); } /** - * If true, create an initial commit with empty README. + * Create repository from template repository. * - * @param enabled - * true if enabled + * @param templateRepository + * the template repository as a GHRepository * @return a builder to continue with building - * @throws IOException - * In case of any networking error or error from the server. */ - public GHCreateRepositoryBuilder autoInit(boolean enabled) throws IOException { - return with("auto_init", enabled); + public GHCreateRepositoryBuilder fromTemplateRepository(GHRepository templateRepository) { + Objects.requireNonNull(templateRepository, "templateRepository cannot be null"); + if (!templateRepository.isTemplate()) { + throw new IllegalArgumentException("The provided repository is not a template repository."); + } + return fromTemplateRepository(templateRepository.getOwnerName(), templateRepository.getName()); } /** - * The team that gets granted access to this repository. Only valid for creating a repository in an organization. + * Create repository from template repository. * - * @param team - * team to grant access to + * @param templateOwner + * template repository owner + * @param templateRepo + * template repository * @return a builder to continue with building + */ + public GHCreateRepositoryBuilder fromTemplateRepository(String templateOwner, String templateRepo) { + requester.withUrlPath("/repos/" + templateOwner + "/" + templateRepo + "/generate"); + return this; + } + + /** + * Creates a default .gitignore + * + * @param language + * template to base the ignore file on + * @return a builder to continue with building See https://developer.github.com/v3/repos/#create * @throws IOException * In case of any networking error or error from the server. */ - public GHCreateRepositoryBuilder team(GHTeam team) throws IOException { - if (team != null) - return with("team_id", team.getId()); - return this; + public GHCreateRepositoryBuilder gitignoreTemplate(String language) throws IOException { + return with("gitignore_template", language); } /** - * Specifies the ownership of the repository. + * Include all branches when creating from a template repository * - * @param owner - * organization or personage + * @param includeAllBranches + * whether or not to include all branches from the template repository * @return a builder to continue with building * @throws IOException * In case of any networking error or error from the server. */ - public GHCreateRepositoryBuilder owner(String owner) throws IOException { - return with("owner", owner); + public GHCreateRepositoryBuilder includeAllBranches(boolean includeAllBranches) throws IOException { + return with("include_all_branches", includeAllBranches); } /** - * Create repository from template repository. + * Desired license template to apply. * - * @param templateOwner - * template repository owner - * @param templateRepo - * template repository - * @return a builder to continue with building + * @param license + * template to base the license file on + * @return a builder to continue with building See https://developer.github.com/v3/repos/#create + * @throws IOException + * In case of any networking error or error from the server. */ - public GHCreateRepositoryBuilder fromTemplateRepository(String templateOwner, String templateRepo) { - requester.withUrlPath("/repos/" + templateOwner + "/" + templateRepo + "/generate"); - return this; + public GHCreateRepositoryBuilder licenseTemplate(String license) throws IOException { + return with("license_template", license); } /** - * Create repository from template repository. + * Specifies the ownership of the repository. * - * @param templateRepository - * the template repository as a GHRepository + * @param owner + * organization or personage * @return a builder to continue with building + * @throws IOException + * In case of any networking error or error from the server. */ - public GHCreateRepositoryBuilder fromTemplateRepository(GHRepository templateRepository) { - Objects.requireNonNull(templateRepository, "templateRepository cannot be null"); - if (!templateRepository.isTemplate()) { - throw new IllegalArgumentException("The provided repository is not a template repository."); - } - return fromTemplateRepository(templateRepository.getOwnerName(), templateRepository.getName()); + public GHCreateRepositoryBuilder owner(String owner) throws IOException { + return with("owner", owner); } /** - * Creates a repository with all the parameters. + * The team that gets granted access to this repository. Only valid for creating a repository in an organization. * - * @return the gh repository + * @param team + * team to grant access to + * @return a builder to continue with building * @throws IOException - * if repository cannot be created + * In case of any networking error or error from the server. */ - public GHRepository create() throws IOException { - return done(); + public GHCreateRepositoryBuilder team(GHTeam team) throws IOException { + if (team != null) + return with("team_id", team.getId()); + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHDeployKey.java b/src/main/java/org/kohsuke/github/GHDeployKey.java index 50a7743688..cba3e243c7 100644 --- a/src/main/java/org/kohsuke/github/GHDeployKey.java +++ b/src/main/java/org/kohsuke/github/GHDeployKey.java @@ -1,8 +1,10 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import org.apache.commons.lang3.builder.ToStringBuilder; import java.io.IOException; +import java.time.Instant; import java.util.Date; // TODO: Auto-generated Javadoc @@ -11,11 +13,21 @@ */ public class GHDeployKey { - /** - * Create default GHDeployKey instance - */ - public GHDeployKey() { - } + /** Name of user that added the deploy key */ + private String addedBy; + + /** Creation date of the deploy key */ + private String createdAt; + + /** Last used date of the deploy key */ + private String lastUsed; + + private GHRepository owner; + /** Whether the deploykey has readonly permission or full access */ + private boolean readOnly; + + /** The id. */ + protected long id; /** The title. */ protected String url, key, title; @@ -23,21 +35,55 @@ public GHDeployKey() { /** The verified. */ protected boolean verified; - /** The id. */ - protected long id; - private GHRepository owner; + /** + * Create default GHDeployKey instance + */ + public GHDeployKey() { + } - /** Creation date of the deploy key */ - private String created_at; + /** + * Delete. + * + * @throws IOException + * the io exception + */ + public void delete() throws IOException { + owner.root() + .createRequest() + .method("DELETE") + .withUrlPath(String.format("/repos/%s/%s/keys/%d", owner.getOwnerName(), owner.getName(), id)) + .send(); + } - /** Last used date of the deploy key */ - private String last_used; + /** + * Gets added_by + * + * @return the added_by + */ + public String getAddedBy() { + return addedBy; + } - /** Name of user that added the deploy key */ - private String added_by; + /** + * Gets added_by + * + * @return the added_by + * @deprecated Use {@link #getAddedBy()} + */ + @Deprecated + public String getAdded_by() { + return getAddedBy(); + } - /** Whether the deploykey has readonly permission or full access */ - private boolean read_only; + /** + * Gets createdAt. + * + * @return the createdAt + */ + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getCreatedAt() { + return GitHubClient.parseInstant(createdAt); + } /** * Gets id. @@ -57,6 +103,16 @@ public String getKey() { return key; } + /** + * Gets last_used. + * + * @return the last_used + */ + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getLastUsedAt() { + return GitHubClient.parseInstant(lastUsed); + } + /** * Gets title. * @@ -76,60 +132,32 @@ public String getUrl() { } /** - * Is verified boolean. - * - * @return the boolean - */ - public boolean isVerified() { - return verified; - } - - /** - * Gets created_at. - * - * @return the created_at - */ - public Date getCreatedAt() { - return GitHubClient.parseDate(created_at); - } - - /** - * Gets last_used. - * - * @return the last_used - */ - public Date getLastUsedAt() { - return GitHubClient.parseDate(last_used); - } - - /** - * Gets added_by + * Is read_only * - * @return the added_by + * @return true if the key can only read. False if the key has write permission as well. */ - public String getAdded_by() { - return added_by; + public boolean isReadOnly() { + return readOnly; } /** * Is read_only * * @return true if the key can only read. False if the key has write permission as well. + * @deprecated {@link #isReadOnly()} */ + @Deprecated public boolean isRead_only() { - return read_only; + return isReadOnly(); } /** - * Wrap gh deploy key. + * Is verified boolean. * - * @param repo - * the repo - * @return the gh deploy key + * @return the boolean */ - GHDeployKey lateBind(GHRepository repo) { - this.owner = repo; - return this; + public boolean isVerified() { + return verified; } /** @@ -141,24 +169,22 @@ public String toString() { return new ToStringBuilder(this).append("title", title) .append("id", id) .append("key", key) - .append("created_at", created_at) - .append("last_used", last_used) - .append("added_by", added_by) - .append("read_only", read_only) + .append("created_at", createdAt) + .append("last_used", lastUsed) + .append("added_by", addedBy) + .append("read_only", readOnly) .toString(); } /** - * Delete. + * Wrap gh deploy key. * - * @throws IOException - * the io exception + * @param repo + * the repo + * @return the gh deploy key */ - public void delete() throws IOException { - owner.root() - .createRequest() - .method("DELETE") - .withUrlPath(String.format("/repos/%s/%s/keys/%d", owner.getOwnerName(), owner.getName(), id)) - .send(); + GHDeployKey lateBind(GHRepository repo) { + this.owner = repo; + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHDeployment.java b/src/main/java/org/kohsuke/github/GHDeployment.java index d441fd5a72..95770de8c4 100644 --- a/src/main/java/org/kohsuke/github/GHDeployment.java +++ b/src/main/java/org/kohsuke/github/GHDeployment.java @@ -15,87 +15,86 @@ */ public class GHDeployment extends GHObject { - /** - * Create default GHDeployment instance - */ - public GHDeployment() { - } - private GHRepository owner; - /** The sha. */ - protected String sha; + /** The creator. */ + protected GHUser creator; - /** The ref. */ - protected String ref; + /** The description. */ + protected String description; - /** The task. */ - protected String task; + /** The environment. */ + protected String environment; + + /** The original environment. */ + protected String originalEnvironment; /** The payload. */ protected Object payload; - /** The environment. */ - protected String environment; - - /** The description. */ - protected String description; + /** The production environment. */ + protected boolean productionEnvironment; - /** The statuses url. */ - protected String statuses_url; + /** The ref. */ + protected String ref; /** The repository url. */ - protected String repository_url; + protected String repositoryUrl; - /** The creator. */ - protected GHUser creator; + /** The sha. */ + protected String sha; - /** The original environment. */ - protected String original_environment; + /** The statuses url. */ + protected String statusesUrl; + + /** The task. */ + protected String task; /** The transient environment. */ - protected boolean transient_environment; + protected boolean transientEnvironment; - /** The production environment. */ - protected boolean production_environment; + /** + * Create default GHDeployment instance + */ + public GHDeployment() { + } /** - * Wrap. + * Create status gh deployment status builder. * - * @param owner - * the owner - * @return the GH deployment + * @param state + * the state + * @return the gh deployment status builder */ - GHDeployment wrap(GHRepository owner) { - this.owner = owner; - return this; + public GHDeploymentStatusBuilder createStatus(GHDeploymentState state) { + return new GHDeploymentStatusBuilder(owner, getId(), state); } /** - * Gets statuses url. + * Gets creator. * - * @return the statuses url + * @return the creator */ - public URL getStatusesUrl() { - return GitHubClient.parseURL(statuses_url); + public GHUser getCreator() { + return root().intern(creator); } /** - * Gets repository url. + * Gets environment. * - * @return the repository url + * @return the environment */ - public URL getRepositoryUrl() { - return GitHubClient.parseURL(repository_url); + public String getEnvironment() { + return environment; } /** - * Gets task. + * The environment defined when the deployment was first created. * - * @return the task + * @return the original deployment environment */ - public String getTask() { - return task; + public String getOriginalEnvironment() { + return originalEnvironment; } /** @@ -128,78 +127,67 @@ public Object getPayloadObject() { } /** - * The environment defined when the deployment was first created. - * - * @return the original deployment environment - */ - public String getOriginalEnvironment() { - return original_environment; - } - - /** - * Gets environment. + * Gets ref. * - * @return the environment + * @return the ref */ - public String getEnvironment() { - return environment; + public String getRef() { + return ref; } /** - * Specifies if the given environment is specific to the deployment and will no longer exist at some point in the - * future. + * Gets repository url. * - * @return the environment is transient + * @return the repository url */ - public boolean isTransientEnvironment() { - return transient_environment; + public URL getRepositoryUrl() { + return GitHubClient.parseURL(repositoryUrl); } /** - * Specifies if the given environment is one that end-users directly interact with. + * Gets sha. * - * @return the environment is used by end-users directly + * @return the sha */ - public boolean isProductionEnvironment() { - return production_environment; + public String getSha() { + return sha; } /** - * Gets creator. + * Gets statuses url. * - * @return the creator + * @return the statuses url */ - public GHUser getCreator() { - return root().intern(creator); + public URL getStatusesUrl() { + return GitHubClient.parseURL(statusesUrl); } /** - * Gets ref. + * Gets task. * - * @return the ref + * @return the task */ - public String getRef() { - return ref; + public String getTask() { + return task; } /** - * Gets sha. + * Specifies if the given environment is one that end-users directly interact with. * - * @return the sha + * @return the environment is used by end-users directly */ - public String getSha() { - return sha; + public boolean isProductionEnvironment() { + return productionEnvironment; } /** - * Create status gh deployment status builder. + * Specifies if the given environment is specific to the deployment and will no longer exist at some point in the + * future. * - * @param state - * the state - * @return the gh deployment status builder + * @return the environment is transient */ - public GHDeploymentStatusBuilder createStatus(GHDeploymentState state) { - return new GHDeploymentStatusBuilder(owner, getId(), state); + public boolean isTransientEnvironment() { + return transientEnvironment; } /** @@ -209,7 +197,7 @@ public GHDeploymentStatusBuilder createStatus(GHDeploymentState state) { */ public PagedIterable listStatuses() { return root().createRequest() - .withUrlPath(statuses_url) + .withUrlPath(statusesUrl) .toIterable(GHDeploymentStatus[].class, item -> item.lateBind(owner)); } @@ -222,4 +210,16 @@ public PagedIterable listStatuses() { GHRepository getOwner() { return owner; } + + /** + * Wrap. + * + * @param owner + * the owner + * @return the GH deployment + */ + GHDeployment wrap(GHRepository owner) { + this.owner = owner; + return this; + } } diff --git a/src/main/java/org/kohsuke/github/GHDeploymentBuilder.java b/src/main/java/org/kohsuke/github/GHDeploymentBuilder.java index 0463b425bc..3340558bbe 100644 --- a/src/main/java/org/kohsuke/github/GHDeploymentBuilder.java +++ b/src/main/java/org/kohsuke/github/GHDeploymentBuilder.java @@ -11,8 +11,8 @@ */ // Based on https://developer.github.com/v3/repos/deployments/#create-a-deployment public class GHDeploymentBuilder { - private final GHRepository repo; private final Requester builder; + private final GHRepository repo; /** * Instantiates a new Gh deployment builder. @@ -40,54 +40,53 @@ public GHDeploymentBuilder(GHRepository repo, String ref) { } /** - * Ref gh deployment builder. + * Auto merge gh deployment builder. * - * @param branch - * the branch + * @param autoMerge + * the auto merge * * @return the gh deployment builder */ - public GHDeploymentBuilder ref(String branch) { - builder.with("ref", branch); + public GHDeploymentBuilder autoMerge(boolean autoMerge) { + builder.with("auto_merge", autoMerge); return this; } /** - * Task gh deployment builder. + * Create gh deployment. * - * @param task - * the task + * @return the gh deployment * - * @return the gh deployment builder + * @throws IOException + * the io exception */ - public GHDeploymentBuilder task(String task) { - builder.with("task", task); - return this; + public GHDeployment create() throws IOException { + return builder.withUrlPath(repo.getApiTailUrl("deployments")).fetch(GHDeployment.class).wrap(repo); } /** - * Auto merge gh deployment builder. + * Description gh deployment builder. * - * @param autoMerge - * the auto merge + * @param description + * the description * * @return the gh deployment builder */ - public GHDeploymentBuilder autoMerge(boolean autoMerge) { - builder.with("auto_merge", autoMerge); + public GHDeploymentBuilder description(String description) { + builder.with("description", description); return this; } /** - * Required contexts gh deployment builder. + * Environment gh deployment builder. * - * @param requiredContexts - * the required contexts + * @param environment + * the environment * * @return the gh deployment builder */ - public GHDeploymentBuilder requiredContexts(List requiredContexts) { - builder.with("required_contexts", requiredContexts); + public GHDeploymentBuilder environment(String environment) { + builder.with("environment", environment); return this; } @@ -105,65 +104,66 @@ public GHDeploymentBuilder payload(String payload) { } /** - * Environment gh deployment builder. - * - * @param environment - * the environment + * Specifies if the given environment is one that end-users directly interact with. * + * @param productionEnvironment + * the environment is used by end-users directly * @return the gh deployment builder */ - public GHDeploymentBuilder environment(String environment) { - builder.with("environment", environment); + public GHDeploymentBuilder productionEnvironment(boolean productionEnvironment) { + builder.with("production_environment", productionEnvironment); return this; } /** - * Specifies if the given environment is specific to the deployment and will no longer exist at some point in the - * future. + * Ref gh deployment builder. + * + * @param branch + * the branch * - * @param transientEnvironment - * the environment is transient * @return the gh deployment builder */ - public GHDeploymentBuilder transientEnvironment(boolean transientEnvironment) { - builder.with("transient_environment", transientEnvironment); + public GHDeploymentBuilder ref(String branch) { + builder.with("ref", branch); return this; } /** - * Specifies if the given environment is one that end-users directly interact with. + * Required contexts gh deployment builder. + * + * @param requiredContexts + * the required contexts * - * @param productionEnvironment - * the environment is used by end-users directly * @return the gh deployment builder */ - public GHDeploymentBuilder productionEnvironment(boolean productionEnvironment) { - builder.with("production_environment", productionEnvironment); + public GHDeploymentBuilder requiredContexts(List requiredContexts) { + builder.with("required_contexts", requiredContexts); return this; } /** - * Description gh deployment builder. + * Task gh deployment builder. * - * @param description - * the description + * @param task + * the task * * @return the gh deployment builder */ - public GHDeploymentBuilder description(String description) { - builder.with("description", description); + public GHDeploymentBuilder task(String task) { + builder.with("task", task); return this; } /** - * Create gh deployment. - * - * @return the gh deployment + * Specifies if the given environment is specific to the deployment and will no longer exist at some point in the + * future. * - * @throws IOException - * the io exception + * @param transientEnvironment + * the environment is transient + * @return the gh deployment builder */ - public GHDeployment create() throws IOException { - return builder.withUrlPath(repo.getApiTailUrl("deployments")).fetch(GHDeployment.class).wrap(repo); + public GHDeploymentBuilder transientEnvironment(boolean transientEnvironment) { + builder.with("transient_environment", transientEnvironment); + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHDeploymentState.java b/src/main/java/org/kohsuke/github/GHDeploymentState.java index 718e57c478..cefb3bc8ac 100644 --- a/src/main/java/org/kohsuke/github/GHDeploymentState.java +++ b/src/main/java/org/kohsuke/github/GHDeploymentState.java @@ -6,30 +6,30 @@ */ public enum GHDeploymentState { - /** The pending. */ - PENDING, - - /** The success. */ - SUCCESS, - /** The error. */ ERROR, /** The failure. */ FAILURE, + /** + * The state of the deployment currently reflects it's no longer active. + */ + INACTIVE, + /** * The state of the deployment currently reflects it's in progress. */ IN_PROGRESS, + /** The pending. */ + PENDING, + /** * The state of the deployment currently reflects it's queued up for processing. */ QUEUED, - /** - * The state of the deployment currently reflects it's no longer active. - */ - INACTIVE + /** The success. */ + SUCCESS } diff --git a/src/main/java/org/kohsuke/github/GHDeploymentStatus.java b/src/main/java/org/kohsuke/github/GHDeploymentStatus.java index f48cd089ff..3cf39fecf9 100644 --- a/src/main/java/org/kohsuke/github/GHDeploymentStatus.java +++ b/src/main/java/org/kohsuke/github/GHDeploymentStatus.java @@ -9,58 +9,36 @@ */ public class GHDeploymentStatus extends GHObject { - /** - * Create default GHDeploymentStatus instance - */ - public GHDeploymentStatus() { - } - private GHRepository owner; /** The creator. */ protected GHUser creator; - /** The state. */ - protected String state; + /** The deployment url. */ + protected String deploymentUrl; /** The description. */ protected String description; - /** The target url. */ - protected String target_url; + /** The environment url. */ + protected String environmentUrl; /** The log url. */ - protected String log_url; - - /** The deployment url. */ - protected String deployment_url; + protected String logUrl; /** The repository url. */ - protected String repository_url; + protected String repositoryUrl; - /** The environment url. */ - protected String environment_url; + /** The state. */ + protected String state; - /** - * Wrap gh deployment status. - * - * @param owner - * the owner - * - * @return the gh deployment status - */ - GHDeploymentStatus lateBind(GHRepository owner) { - this.owner = owner; - return this; - } + /** The target url. */ + protected String targetUrl; /** - * Gets target url. - * - * @return the target url + * Create default GHDeploymentStatus instance */ - public URL getLogUrl() { - return GitHubClient.parseURL(log_url); + public GHDeploymentStatus() { } /** @@ -69,7 +47,7 @@ public URL getLogUrl() { * @return the deployment url */ public URL getDeploymentUrl() { - return GitHubClient.parseURL(deployment_url); + return GitHubClient.parseURL(deploymentUrl); } /** @@ -78,7 +56,16 @@ public URL getDeploymentUrl() { * @return the deployment environment url */ public URL getEnvironmentUrl() { - return GitHubClient.parseURL(environment_url); + return GitHubClient.parseURL(environmentUrl); + } + + /** + * Gets target url. + * + * @return the target url + */ + public URL getLogUrl() { + return GitHubClient.parseURL(logUrl); } /** @@ -87,7 +74,7 @@ public URL getEnvironmentUrl() { * @return the repository url */ public URL getRepositoryUrl() { - return GitHubClient.parseURL(repository_url); + return GitHubClient.parseURL(repositoryUrl); } /** @@ -108,4 +95,17 @@ public GHDeploymentState getState() { GHRepository getOwner() { return owner; } + + /** + * Wrap gh deployment status. + * + * @param owner + * the owner + * + * @return the gh deployment status + */ + GHDeploymentStatus lateBind(GHRepository owner) { + this.owner = owner; + return this; + } } diff --git a/src/main/java/org/kohsuke/github/GHDeploymentStatusBuilder.java b/src/main/java/org/kohsuke/github/GHDeploymentStatusBuilder.java index e003758fb8..23406e0580 100644 --- a/src/main/java/org/kohsuke/github/GHDeploymentStatusBuilder.java +++ b/src/main/java/org/kohsuke/github/GHDeploymentStatusBuilder.java @@ -10,8 +10,8 @@ */ public class GHDeploymentStatusBuilder { private final Requester builder; - private GHRepository repo; private long deploymentId; + private GHRepository repo; /** * Instantiates a new GH deployment status builder. @@ -44,6 +44,20 @@ public GHDeploymentStatusBuilder autoInactive(boolean autoInactive) { return this; } + /** + * Create gh deployment status. + * + * @return the gh deployment status + * + * @throws IOException + * the io exception + */ + public GHDeploymentStatus create() throws IOException { + return builder.withUrlPath(repo.getApiTailUrl("deployments/" + deploymentId + "/statuses")) + .fetch(GHDeploymentStatus.class) + .lateBind(repo); + } + /** * Description gh deployment status builder. * @@ -92,18 +106,4 @@ public GHDeploymentStatusBuilder logUrl(String logUrl) { this.builder.with("log_url", logUrl); return this; } - - /** - * Create gh deployment status. - * - * @return the gh deployment status - * - * @throws IOException - * the io exception - */ - public GHDeploymentStatus create() throws IOException { - return builder.withUrlPath(repo.getApiTailUrl("deployments/" + deploymentId + "/statuses")) - .fetch(GHDeploymentStatus.class) - .lateBind(repo); - } } diff --git a/src/main/java/org/kohsuke/github/GHDiscussion.java b/src/main/java/org/kohsuke/github/GHDiscussion.java index d2fbaa3f67..99e8801d08 100644 --- a/src/main/java/org/kohsuke/github/GHDiscussion.java +++ b/src/main/java/org/kohsuke/github/GHDiscussion.java @@ -20,96 +20,56 @@ public class GHDiscussion extends GHObject { /** - * Create default GHDiscussion instance - */ - public GHDiscussion() { - } - - private GHTeam team; - private long number; - private String body, title, htmlUrl; - - @JsonProperty(value = "private") - private boolean isPrivate; - - /** - * Gets the html url. - * - * @return the html url - */ - public URL getHtmlUrl() { - return GitHubClient.parseURL(htmlUrl); - } - - /** - * Wrap up. - * - * @param team - * the team - * @return the GH discussion - */ - GHDiscussion wrapUp(GHTeam team) { - this.team = team; - return this; - } - - /** - * Get the team to which this discussion belongs. + * A {@link GHLabelBuilder} that creates a new {@link GHLabel} * - * @return the team for this discussion + * Consumer must call {@link Creator#done()} to create the new instance. */ - @Nonnull - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHTeam getTeam() { - return team; - } + public static class Creator extends GHDiscussionBuilder { - /** - * Get the title of the discussion. - * - * @return the title - */ - public String getTitle() { - return title; - } + private Creator(@Nonnull GHTeam team) { + super(GHDiscussion.Creator.class, team, null); + requester.method("POST").setRawUrlPath(getRawUrlPath(team, null)); + } - /** - * The description of this discussion. - * - * @return the body - */ - public String getBody() { - return body; + /** + * Sets whether this discussion is private to this team. + * + * @param value + * privacy of this discussion + * @return either a continuing builder or an updated {@link GHDiscussion} + * @throws IOException + * if there is an I/O Exception + */ + @Nonnull + public Creator private_(boolean value) throws IOException { + return with("private", value); + } } /** - * The number of this discussion. + * A {@link GHLabelBuilder} that updates a single property per request * - * @return the number + * {@link GitHubRequestBuilderDone#done()} is called automatically after the property is set. */ - public long getNumber() { - return number; + public static class Setter extends GHDiscussionBuilder { + private Setter(@Nonnull GHDiscussion base) { + super(GHDiscussion.class, base.team, base); + requester.method("PATCH").setRawUrlPath(base.getUrl().toString()); + } } - /** - * The id number of this discussion. GitHub discussions have "number" instead of "id". This is provided for - * convenience. + * A {@link GHLabelBuilder} that allows multiple properties to be updated per request. * - * @return the id number for this discussion - * @see #getNumber() + * Consumer must call {@link Updater#done()} to commit changes. */ - @Override - public long getId() { - return getNumber(); + public static class Updater extends GHDiscussionBuilder { + private Updater(@Nonnull GHDiscussion base) { + super(GHDiscussion.Updater.class, base.team, base); + requester.method("PATCH").setRawUrlPath(base.getUrl().toString()); + } } - - /** - * Whether the discussion is private to the team. - * - * @return {@code true} if discussion is private. - */ - public boolean isPrivate() { - return isPrivate; + private static String getRawUrlPath(@Nonnull GHTeam team, @CheckForNull Long discussionNumber) { + return team.getUrl().toString() + "/discussions" + (discussionNumber == null ? "" : "/" + discussionNumber); } /** @@ -158,24 +118,19 @@ static PagedIterable readAll(GHTeam team) { .toIterable(GHDiscussion[].class, item -> item.wrapUp(team)); } - /** - * Begins a batch update - * - * Consumer must call {@link GHDiscussion.Updater#done()} to commit changes. - * - * @return a {@link GHDiscussion.Updater} - */ - public GHDiscussion.Updater update() { - return new GHDiscussion.Updater(this); - } + private String body, title, htmlUrl; + + @JsonProperty(value = "private") + private boolean isPrivate; + + private long number; + + private GHTeam team; /** - * Begins a single property update. - * - * @return a {@link GHDiscussion.Setter} + * Create default GHDiscussion instance */ - public GHDiscussion.Setter set() { - return new GHDiscussion.Setter(this); + public GHDiscussion() { } /** @@ -188,79 +143,83 @@ public void delete() throws IOException { team.root().createRequest().method("DELETE").setRawUrlPath(getRawUrlPath(team, number)).send(); } - private static String getRawUrlPath(@Nonnull GHTeam team, @CheckForNull Long discussionNumber) { - return team.getUrl().toString() + "/discussions" + (discussionNumber == null ? "" : "/" + discussionNumber); + /** + * Equals. + * + * @param o + * the o + * @return true, if successful + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + GHDiscussion that = (GHDiscussion) o; + return number == that.number && Objects.equals(getUrl(), that.getUrl()) && Objects.equals(team, that.team) + && Objects.equals(body, that.body) && Objects.equals(title, that.title); } /** - * A {@link GHLabelBuilder} that updates a single property per request + * The description of this discussion. * - * {@link GitHubRequestBuilderDone#done()} is called automatically after the property is set. + * @return the body */ - public static class Setter extends GHDiscussionBuilder { - private Setter(@Nonnull GHDiscussion base) { - super(GHDiscussion.class, base.team, base); - requester.method("PATCH").setRawUrlPath(base.getUrl().toString()); - } + public String getBody() { + return body; } /** - * A {@link GHLabelBuilder} that allows multiple properties to be updated per request. + * Gets the html url. * - * Consumer must call {@link Updater#done()} to commit changes. + * @return the html url */ - public static class Updater extends GHDiscussionBuilder { - private Updater(@Nonnull GHDiscussion base) { - super(GHDiscussion.Updater.class, base.team, base); - requester.method("PATCH").setRawUrlPath(base.getUrl().toString()); - } + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); } /** - * A {@link GHLabelBuilder} that creates a new {@link GHLabel} + * The id number of this discussion. GitHub discussions have "number" instead of "id". This is provided for + * convenience. * - * Consumer must call {@link Creator#done()} to create the new instance. + * @return the id number for this discussion + * @see #getNumber() */ - public static class Creator extends GHDiscussionBuilder { + @Override + public long getId() { + return getNumber(); + } - private Creator(@Nonnull GHTeam team) { - super(GHDiscussion.Creator.class, team, null); - requester.method("POST").setRawUrlPath(getRawUrlPath(team, null)); - } + /** + * The number of this discussion. + * + * @return the number + */ + public long getNumber() { + return number; + } - /** - * Sets whether this discussion is private to this team. - * - * @param value - * privacy of this discussion - * @return either a continuing builder or an updated {@link GHDiscussion} - * @throws IOException - * if there is an I/O Exception - */ - @Nonnull - public Creator private_(boolean value) throws IOException { - return with("private", value); - } + /** + * Get the team to which this discussion belongs. + * + * @return the team for this discussion + */ + @Nonnull + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHTeam getTeam() { + return team; } /** - * Equals. + * Get the title of the discussion. * - * @param o - * the o - * @return true, if successful + * @return the title */ - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - GHDiscussion that = (GHDiscussion) o; - return number == that.number && Objects.equals(getUrl(), that.getUrl()) && Objects.equals(team, that.team) - && Objects.equals(body, that.body) && Objects.equals(title, that.title); + public String getTitle() { + return title; } /** @@ -272,4 +231,45 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(team, number, body, title); } + + /** + * Whether the discussion is private to the team. + * + * @return {@code true} if discussion is private. + */ + public boolean isPrivate() { + return isPrivate; + } + + /** + * Begins a single property update. + * + * @return a {@link GHDiscussion.Setter} + */ + public GHDiscussion.Setter set() { + return new GHDiscussion.Setter(this); + } + + /** + * Begins a batch update + * + * Consumer must call {@link GHDiscussion.Updater#done()} to commit changes. + * + * @return a {@link GHDiscussion.Updater} + */ + public GHDiscussion.Updater update() { + return new GHDiscussion.Updater(this); + } + + /** + * Wrap up. + * + * @param team + * the team + * @return the GH discussion + */ + GHDiscussion wrapUp(GHTeam team) { + this.team = team; + return this; + } } diff --git a/src/main/java/org/kohsuke/github/GHDiscussionBuilder.java b/src/main/java/org/kohsuke/github/GHDiscussionBuilder.java index 39dfd287d1..30d1986731 100644 --- a/src/main/java/org/kohsuke/github/GHDiscussionBuilder.java +++ b/src/main/java/org/kohsuke/github/GHDiscussionBuilder.java @@ -43,20 +43,6 @@ protected GHDiscussionBuilder(@Nonnull Class intermediateReturnType, } } - /** - * Title for this discussion. - * - * @param value - * title of discussion - * @return either a continuing builder or an updated {@link GHDiscussion} - * @throws IOException - * if there is an I/O Exception - */ - @Nonnull - public S title(String value) throws IOException { - return with("title", value); - } - /** * Body content for this discussion. * @@ -79,4 +65,18 @@ public S body(String value) throws IOException { public GHDiscussion done() throws IOException { return super.done().wrapUp(team); } + + /** + * Title for this discussion. + * + * @param value + * title of discussion + * @return either a continuing builder or an updated {@link GHDiscussion} + * @throws IOException + * if there is an I/O Exception + */ + @Nonnull + public S title(String value) throws IOException { + return with("title", value); + } } diff --git a/src/main/java/org/kohsuke/github/GHEmail.java b/src/main/java/org/kohsuke/github/GHEmail.java index 75dcc0b6ba..f5446a338d 100644 --- a/src/main/java/org/kohsuke/github/GHEmail.java +++ b/src/main/java/org/kohsuke/github/GHEmail.java @@ -37,12 +37,6 @@ justification = "JSON API") public class GHEmail { - /** - * Create default GHEmail instance - */ - public GHEmail() { - } - /** The email. */ protected String email; @@ -52,6 +46,28 @@ public GHEmail() { /** The verified. */ protected boolean verified; + /** + * Create default GHEmail instance + */ + public GHEmail() { + } + + /** + * Equals. + * + * @param obj + * the obj + * @return true, if successful + */ + @Override + public boolean equals(Object obj) { + if (obj instanceof GHEmail) { + GHEmail that = (GHEmail) obj; + return this.email.equals(that.email); + } + return false; + } + /** * Gets email. * @@ -61,6 +77,16 @@ public String getEmail() { return email; } + /** + * Hash code. + * + * @return the int + */ + @Override + public int hashCode() { + return email.hashCode(); + } + /** * Is primary boolean. * @@ -88,30 +114,4 @@ public boolean isVerified() { public String toString() { return "Email:" + email; } - - /** - * Hash code. - * - * @return the int - */ - @Override - public int hashCode() { - return email.hashCode(); - } - - /** - * Equals. - * - * @param obj - * the obj - * @return true, if successful - */ - @Override - public boolean equals(Object obj) { - if (obj instanceof GHEmail) { - GHEmail that = (GHEmail) obj; - return this.email.equals(that.email); - } - return false; - } } diff --git a/src/main/java/org/kohsuke/github/GHError.java b/src/main/java/org/kohsuke/github/GHError.java index 9455ff31fe..3602703945 100644 --- a/src/main/java/org/kohsuke/github/GHError.java +++ b/src/main/java/org/kohsuke/github/GHError.java @@ -13,37 +13,28 @@ */ public class GHError implements Serializable { - /** - * Create default GHError instance - */ - public GHError() { - } - /** * The serial version UID of the error */ private static final long serialVersionUID = 2008071901; /** - * The error message. + * The URL to the documentation for the error. */ + @JsonProperty("documentation_url") @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") - private String message; + private String documentation; /** - * The URL to the documentation for the error. + * The error message. */ - @JsonProperty("documentation_url") @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") - private String documentation; + private String message; /** - * Get the error message. - * - * @return the message + * Create default GHError instance */ - public String getMessage() { - return message; + public GHError() { } /** @@ -55,4 +46,13 @@ public URL getDocumentationUrl() { return GitHubClient.parseURL(documentation); } + /** + * Get the error message. + * + * @return the message + */ + public String getMessage() { + return message; + } + } diff --git a/src/main/java/org/kohsuke/github/GHEvent.java b/src/main/java/org/kohsuke/github/GHEvent.java index 1170743ac3..b83aa820b9 100644 --- a/src/main/java/org/kohsuke/github/GHEvent.java +++ b/src/main/java/org/kohsuke/github/GHEvent.java @@ -12,6 +12,9 @@ */ public enum GHEvent { + /** Special event type that means "every possible event". */ + ALL, + /** The branch protection rule. */ BRANCH_PROTECTION_RULE, @@ -36,15 +39,15 @@ public enum GHEvent { /** The delete. */ DELETE, - /** The deploy key. */ - DEPLOY_KEY, - /** The deployment. */ DEPLOYMENT, /** The deployment status. */ DEPLOYMENT_STATUS, + /** The deploy key. */ + DEPLOY_KEY, + /** The discussion. */ DISCUSSION, @@ -54,6 +57,9 @@ public enum GHEvent { /** The download. */ DOWNLOAD, + /** The dynamic events like Dependabot autorun. */ + DYNAMIC, + /** The follow. */ FOLLOW, @@ -63,12 +69,12 @@ public enum GHEvent { /** The fork apply. */ FORK_APPLY, - /** The github app authorization. */ - GITHUB_APP_AUTHORIZATION, - /** The gist. */ GIST, + /** The github app authorization. */ + GITHUB_APP_AUTHORIZATION, + /** The gollum. */ GOLLUM, @@ -81,12 +87,12 @@ public enum GHEvent { /** The integration installation repositories. */ INTEGRATION_INSTALLATION_REPOSITORIES, - /** The issue comment. */ - ISSUE_COMMENT, - /** The issues. */ ISSUES, + /** The issue comment. */ + ISSUE_COMMENT, + /** The label. */ LABEL, @@ -99,12 +105,12 @@ public enum GHEvent { /** The membership. */ MEMBERSHIP, - /** The merge queue entry. */ - MERGE_QUEUE_ENTRY, - /** The merge group entry. */ MERGE_GROUP, + /** The merge queue entry. */ + MERGE_QUEUE_ENTRY, + /** The meta. */ META, @@ -123,18 +129,18 @@ public enum GHEvent { /** The page build. */ PAGE_BUILD, + /** The ping. */ + PING, + + /** The project. */ + PROJECT, + /** The project card. */ PROJECT_CARD, /** The project column. */ PROJECT_COLUMN, - /** The project. */ - PROJECT, - - /** The ping. */ - PING, - /** The public. */ PUBLIC, @@ -158,13 +164,13 @@ public enum GHEvent { /** The release. */ RELEASE, - - /** The repository dispatch. */ - REPOSITORY_DISPATCH, /** The repository. */ // only valid for org hooks REPOSITORY, + /** The repository dispatch. */ + REPOSITORY_DISPATCH, + /** The repository import. */ REPOSITORY_IMPORT, @@ -189,25 +195,22 @@ public enum GHEvent { /** The team add. */ TEAM_ADD, + /** + * Special event type that means we haven't found an enum value corresponding to the event. + */ + UNKNOWN, + /** The watch. */ WATCH, - /** The workflow job. */ - WORKFLOW_JOB, - /** The workflow dispatch. */ WORKFLOW_DISPATCH, - /** The workflow run. */ - WORKFLOW_RUN, - - /** - * Special event type that means we haven't found an enum value corresponding to the event. - */ - UNKNOWN, + /** The workflow job. */ + WORKFLOW_JOB, - /** Special event type that means "every possible event". */ - ALL; + /** The workflow run. */ + WORKFLOW_RUN; /** * Returns GitHub's internal representation of this event. diff --git a/src/main/java/org/kohsuke/github/GHEventInfo.java b/src/main/java/org/kohsuke/github/GHEventInfo.java index 050b141f31..b22f498bff 100644 --- a/src/main/java/org/kohsuke/github/GHEventInfo.java +++ b/src/main/java/org/kohsuke/github/GHEventInfo.java @@ -1,9 +1,10 @@ package org.kohsuke.github; -import com.fasterxml.jackson.databind.node.ObjectNode; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; +import java.time.Instant; import java.util.*; // TODO: Auto-generated Javadoc @@ -15,34 +16,6 @@ @SuppressFBWarnings(value = "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", justification = "JSON API") public class GHEventInfo extends GitHubInteractiveObject { - /** - * Create default GHEventInfo instance - */ - public GHEventInfo() { - } - - // we don't want to expose Jackson dependency to the user. This needs databinding - private ObjectNode payload; - - private long id; - private String created_at; - - /** - * Representation of GitHub Event API Event Type. - * - * This is not the same as the values used for hook methods such as - * {@link GHRepository#createHook(String, Map, Collection, boolean)}. - * - * @see GitHub event - * types - */ - private String type; - - // these are all shallow objects - private GHEventRepository repo; - private GHUser actor; - private GHOrganization org; - /** * Inside the event JSON model, GitHub uses a slightly different format. */ @@ -52,17 +25,17 @@ public GHEventInfo() { justification = "JSON API") public static class GHEventRepository { + @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") + private long id; + + private String name; // owner/repo + @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") + private String url; // repository API URL /** * Create default GHEventRepository instance */ public GHEventRepository() { } - - @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") - private long id; - @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") - private String url; // repository API URL - private String name; // owner/repo } /** The Constant mapTypeStringToEvent. */ @@ -93,7 +66,6 @@ private static Map createEventMap() { map.put("WatchEvent", GHEvent.WATCH); return Collections.unmodifiableMap(map); } - /** * Transform type to GH event. * @@ -105,44 +77,32 @@ static GHEvent transformTypeToGHEvent(String type) { return mapTypeStringToEvent.getOrDefault(type, GHEvent.UNKNOWN); } - /** - * Gets type. - * - * @return the type - */ - public GHEvent getType() { - return transformTypeToGHEvent(type); - } + private GHUser actor; - /** - * Gets id. - * - * @return the id - */ - public long getId() { - return id; - } + private String createdAt; + private long id; + private GHOrganization org; + + private Object payload; + + // these are all shallow objects + private GHEventRepository repo; /** - * Gets created at. + * Representation of GitHub Event API Event Type. * - * @return the created at + * This is not the same as the values used for hook methods such as + * {@link GHRepository#createHook(String, Map, Collection, boolean)}. + * + * @see GitHub event + * types */ - public Date getCreatedAt() { - return GitHubClient.parseDate(created_at); - } + private String type; /** - * Gets repository. - * - * @return Repository where the change was made. - * @throws IOException - * on error + * Create default GHEventInfo instance */ - @SuppressFBWarnings(value = { "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" }, - justification = "The field comes from JSON deserialization") - public GHRepository getRepository() throws IOException { - return root().getRepository(repo.name); + public GHEventInfo() { } /** @@ -167,6 +127,25 @@ public String getActorLogin() { return actor.getLogin(); } + /** + * Gets created at. + * + * @return the created at + */ + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getCreatedAt() { + return GitHubClient.parseInstant(createdAt); + } + + /** + * Gets id. + * + * @return the id + */ + public long getId() { + return id; + } + /** * Gets organization. * @@ -193,8 +172,32 @@ public GHOrganization getOrganization() throws IOException { * if payload cannot be parsed */ public T getPayload(Class type) throws IOException { - T v = GitHubClient.getMappingObjectReader(root()).readValue(payload.traverse(), type); + T v = GitHubClient.getMappingObjectReader(root()) + .forType(type) + .readValue(GitHubClient.getMappingObjectWriter().writeValueAsString(payload)); v.lateBind(); return v; } + + /** + * Gets repository. + * + * @return Repository where the change was made. + * @throws IOException + * on error + */ + @SuppressFBWarnings(value = { "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" }, + justification = "The field comes from JSON deserialization") + public GHRepository getRepository() throws IOException { + return root().getRepository(repo.name); + } + + /** + * Gets type. + * + * @return the type + */ + public GHEvent getType() { + return transformTypeToGHEvent(type); + } } diff --git a/src/main/java/org/kohsuke/github/GHEventPayload.java b/src/main/java/org/kohsuke/github/GHEventPayload.java index 845b95bcaa..12a95e0979 100644 --- a/src/main/java/org/kohsuke/github/GHEventPayload.java +++ b/src/main/java/org/kohsuke/github/GHEventPayload.java @@ -2,10 +2,13 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; +import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.io.Reader; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -23,84 +26,6 @@ */ @SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, justification = "JSON API") public abstract class GHEventPayload extends GitHubInteractiveObject { - // https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/webhook-events-and-payloads#webhook-payload-object-common-properties - // Webhook payload object common properties: action, sender, repository, organization, installation - private String action; - private GHUser sender; - private GHRepository repository; - private GHOrganization organization; - private GHAppInstallation installation; - - /** - * Instantiates a new GH event payload. - */ - GHEventPayload() { - } - - /** - * Gets the action for the triggered event. Most but not all webhook payloads contain an action property that - * contains the specific activity that triggered the event. - * - * @return event action - */ - public String getAction() { - return action; - } - - /** - * Gets the sender or {@code null} if accessed via the events API. - * - * @return the sender or {@code null} if accessed via the events API. - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHUser getSender() { - return sender; - } - - /** - * Gets repository. - * - * @return the repository - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHRepository getRepository() { - return repository; - } - - /** - * Gets organization. - * - * @return the organization - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHOrganization getOrganization() { - return organization; - } - - /** - * Gets installation. - * - * @return the installation - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHAppInstallation getInstallation() { - return installation; - } - - // List of events that still need to be added: - // ContentReferenceEvent - // DeployKeyEvent DownloadEvent FollowEvent ForkApplyEvent GitHubAppAuthorizationEvent GistEvent GollumEvent - // InstallationEvent InstallationRepositoriesEvent IssuesEvent LabelEvent MarketplacePurchaseEvent MemberEvent - // MembershipEvent MetaEvent MilestoneEvent OrganizationEvent OrgBlockEvent PackageEvent PageBuildEvent - // ProjectCardEvent ProjectColumnEvent ProjectEvent RepositoryDispatchEvent RepositoryImportEvent - // RepositoryVulnerabilityAlertEvent SecurityAdvisoryEvent StarEvent StatusEvent TeamEvent TeamAddEvent WatchEvent - - /** - * Late bind. - */ - void lateBind() { - } - /** * A check run event has been created, rerequested, completed, or has a requested_action. * @@ -110,23 +35,14 @@ void lateBind() { */ public static class CheckRun extends GHEventPayload { - /** - * Create default CheckRun instance - */ - public CheckRun() { - } + private GHCheckRun checkRun; private int number; - private GHCheckRun checkRun; private GHRequestedAction requestedAction; - /** - * Gets number. - * - * @return the number + * Create default CheckRun instance */ - public int getNumber() { - return number; + public CheckRun() { } /** @@ -139,6 +55,15 @@ public GHCheckRun getCheckRun() { return checkRun; } + /** + * Gets number. + * + * @return the number + */ + public int getNumber() { + return number; + } + /** * Gets the Requested Action object. * @@ -166,7 +91,6 @@ void lateBind() { } } } - /** * A check suite event has been requested, rerequested or completed. * @@ -176,14 +100,14 @@ void lateBind() { */ public static class CheckSuite extends GHEventPayload { + private GHCheckSuite checkSuite; + /** * Create default CheckSuite instance */ public CheckSuite() { } - private GHCheckSuite checkSuite; - /** * Gets the Check Suite object. * @@ -211,62 +135,81 @@ void lateBind() { } } } - /** - * An installation has been installed, uninstalled, or its permissions have been changed. + * Wrapper for changes on issue and pull request review comments action="edited". * - * @see - * installation event - * @see GitHub App Installation + * @see GHEventPayload.IssueComment + * @see GHEventPayload.PullRequestReviewComment */ - public static class Installation extends GHEventPayload { + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "JSON API") + public static class CommentChanges { /** - * Create default Installation instance + * Wrapper for changed values. */ - public Installation() { + public static class GHFrom { + + private String from; + + /** + * Create default GHFrom instance + */ + public GHFrom() { + } + + /** + * Previous comment value that was changed. + * + * @return previous value + */ + public String getFrom() { + return from; + } } - private List repositories; - private List ghRepositories = null; + private GHFrom body; /** - * Gets repositories. For the "deleted" action please rather call {@link #getRawRepositories()} + * Create default CommentChanges instance + */ + public CommentChanges() { + } + + /** + * Gets the previous comment body. * - * @return the repositories + * @return previous comment body (or null if not changed) */ - public List getRepositories() { - if ("deleted".equalsIgnoreCase(getAction())) { - throw new IllegalStateException("Can't call #getRepositories() on Installation event " - + "with 'deleted' action. Call #getRawRepositories() instead."); - } + public GHFrom getBody() { + return body; + } + } + /** + * A comment was added to a commit. + * + * @see + * commit comment + * @see Comments + */ + public static class CommitComment extends GHEventPayload { - if (ghRepositories == null) { - ghRepositories = new ArrayList<>(repositories.size()); - try { - for (Repository singleRepo : repositories) { - // populate each repository - // the repository information provided here is so limited - // as to be unusable without populating, so we do it eagerly - ghRepositories.add(this.root().getRepositoryById(singleRepo.getId())); - } - } catch (IOException e) { - throw new GHException("Failed to refresh repositories", e); - } - } + private GHCommitComment comment; - return Collections.unmodifiableList(ghRepositories); + /** + * Create default CommitComment instance + */ + public CommitComment() { } /** - * Returns a list of raw, unpopulated repositories. Useful when calling from within Installation event with - * action "deleted". You can't fetch the info for repositories of an already deleted installation. + * Gets comment. * - * @return the list of raw Repository records + * @return the comment */ - public List getRawRepositories() { - return Collections.unmodifiableList(repositories); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHCommitComment getComment() { + return comment; } /** @@ -274,214 +217,134 @@ public List getRawRepositories() { */ @Override void lateBind() { - if (getInstallation() == null) { - throw new IllegalStateException( - "Expected installation payload, but got something else. Maybe we've got another type of event?"); - } super.lateBind(); + GHRepository repository = getRepository(); + if (repository != null) { + comment.wrap(repository); + } } + } + /** + * A repository, branch, or tag was created. + * + * @see + * create event + * @see Git data + */ + public static class Create extends GHEventPayload { + + private String description; + private String masterBranch; + private String ref; + private String refType; /** - * A special minimal implementation of a {@link GHRepository} which contains only fields from "Properties of - * repositories" from here + * Create default Create instance */ - public static class Repository { - - /** - * Create default Repository instance - */ - public Repository() { - } + public Create() { + } - private long id; - private String fullName; - private String name; - private String nodeId; - @JsonProperty(value = "private") - private boolean isPrivate; - - /** - * Get the id. - * - * @return the id - */ - public long getId() { - return id; - } - - /** - * Gets the full name. - * - * @return the full name - */ - public String getFullName() { - return fullName; - } - - /** - * Gets the name. - * - * @return the name - */ - public String getName() { - return name; - } - - /** - * Gets the node id. - * - * @return the node id - */ - public String getNodeId() { - return nodeId; - } - - /** - * Gets the repository private flag. - * - * @return whether the repository is private - */ - public boolean isPrivate() { - return isPrivate; - } - } - } - - /** - * A repository has been added or removed from an installation. - * - * @see - * installation_repositories event - * @see GitHub App installation - */ - public static class InstallationRepositories extends GHEventPayload { - - /** - * Create default InstallationRepositories instance - */ - public InstallationRepositories() { - } - - private String repositorySelection; - private List repositoriesAdded; - private List repositoriesRemoved; + /** + * Gets description. + * + * @return the description + */ + public String getDescription() { + return description; + } /** - * Gets installation selection. + * Gets default branch. * - * @return the installation selection - */ - public String getRepositorySelection() { - return repositorySelection; - } - - /** - * Gets repositories added. + * Name is an artifact of when "master" was the most common default. * - * @return the repositories + * @return the default branch */ - public List getRepositoriesAdded() { - return Collections.unmodifiableList(repositoriesAdded); + public String getMasterBranch() { + return masterBranch; } /** - * Gets repositories removed. + * Gets ref. * - * @return the repositories + * @return the ref */ - public List getRepositoriesRemoved() { - return Collections.unmodifiableList(repositoriesRemoved); + public String getRef() { + return ref; } /** - * Late bind. + * Gets ref type. + * + * @return the ref type */ - @Override - void lateBind() { - if (getInstallation() == null) { - throw new IllegalStateException( - "Expected installation_repositories payload, but got something else. Maybe we've got another type of event?"); - } - super.lateBind(); - List repositories; - if ("added".equals(getAction())) - repositories = repositoriesAdded; - else // action == "removed" - repositories = repositoriesRemoved; - - if (repositories != null && !repositories.isEmpty()) { - try { - for (GHRepository singleRepo : repositories) { // warp each of the repository - singleRepo.populate(); - } - } catch (IOException e) { - throw new GHException("Failed to refresh repositories", e); - } - } + public String getRefType() { + return refType; } } /** - * A pull request status has changed. + * A branch, or tag was deleted. * - * @see - * pull_request event - * @see Pull Requests + * @see + * delete event + * @see Git data */ - @SuppressFBWarnings(value = { "NP_UNWRITTEN_FIELD" }, justification = "JSON API") - public static class PullRequest extends GHEventPayload { + public static class Delete extends GHEventPayload { + + private String ref; + private String refType; /** - * Create default PullRequest instance + * Create default Delete instance */ - public PullRequest() { + public Delete() { } - private int number; - private GHPullRequest pullRequest; - private GHLabel label; - private GHPullRequestChanges changes; - /** - * Gets number. + * Gets ref. * - * @return the number + * @return the ref */ - public int getNumber() { - return number; + public String getRef() { + return ref; } /** - * Gets pull request. + * Gets ref type. * - * @return the pull request + * @return the ref type */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHPullRequest getPullRequest() { - return pullRequest; + public String getRefType() { + return refType; } + } + + /** + * A deployment. + * + * @see + * deployment event + * @see Deployments + */ + public static class Deployment extends GHEventPayload { + + private GHDeployment deployment; /** - * Gets the added or removed label for labeled/unlabeled events. - * - * @return label the added or removed label + * Create default Deployment instance */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHLabel getLabel() { - return label; + public Deployment() { } /** - * Get changes (for action="edited"). + * Gets deployment. * - * @return changes + * @return the deployment */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHPullRequestChanges getChanges() { - return changes; + public GHDeployment getDeployment() { + return deployment; } /** @@ -489,54 +352,51 @@ public GHPullRequestChanges getChanges() { */ @Override void lateBind() { - if (pullRequest == null) - throw new IllegalStateException( - "Expected pull_request payload, but got something else. Maybe we've got another type of event?"); super.lateBind(); GHRepository repository = getRepository(); if (repository != null) { - pullRequest.wrapUp(repository); + deployment.wrap(repository); } } } /** - * A review was added to a pull request. + * A deployment status. * * @see - * pull_request_review event - * @see Pull Request Reviews + * "https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#deployment_status"> + * deployment_status event + * @see Deployments */ - public static class PullRequestReview extends GHEventPayload { + public static class DeploymentStatus extends GHEventPayload { + private GHDeployment deployment; + + private GHDeploymentStatus deploymentStatus; /** - * Create default PullRequestReview instance + * Create default DeploymentStatus instance */ - public PullRequestReview() { + public DeploymentStatus() { } - private GHPullRequestReview review; - private GHPullRequest pullRequest; - /** - * Gets review. + * Gets deployment. * - * @return the review + * @return the deployment */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHPullRequestReview getReview() { - return review; + public GHDeployment getDeployment() { + return deployment; } /** - * Gets pull request. + * Gets deployment status. * - * @return the pull request + * @return the deployment status */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHPullRequest getPullRequest() { - return pullRequest; + public GHDeploymentStatus getDeploymentStatus() { + return deploymentStatus; } /** @@ -544,187 +404,256 @@ public GHPullRequest getPullRequest() { */ @Override void lateBind() { - if (review == null) - throw new IllegalStateException( - "Expected pull_request_review payload, but got something else. Maybe we've got another type of event?"); super.lateBind(); - - review.wrapUp(pullRequest); - GHRepository repository = getRepository(); if (repository != null) { - pullRequest.wrapUp(repository); + deployment.wrap(repository); + deploymentStatus.lateBind(repository); } } } /** - * Wrapper for changes on issue and pull request review comments action="edited". + * A discussion was closed, reopened, created, edited, deleted, pinned, unpinned, locked, unlocked, transferred, + * category_changed, answered, or unanswered. * - * @see GHEventPayload.IssueComment - * @see GHEventPayload.PullRequestReviewComment + * @see + * discussion event */ - @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "JSON API") - public static class CommentChanges { + public static class Discussion extends GHEventPayload { + + private GHRepositoryDiscussion discussion; + + private GHLabel label; /** - * Create default CommentChanges instance + * Create default Discussion instance */ - public CommentChanges() { + public Discussion() { } - private GHFrom body; - /** - * Gets the previous comment body. + * Gets discussion. * - * @return previous comment body (or null if not changed) + * @return the discussion */ - public GHFrom getBody() { - return body; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHRepositoryDiscussion getDiscussion() { + return discussion; } /** - * Wrapper for changed values. + * Gets the added or removed label for labeled/unlabeled events. + * + * May be {@code null} when the unlabeled event was triggered by deleting the label from the repository. + * + * @return label the added or removed label, or {@code null} if the label was deleted */ - public static class GHFrom { - - /** - * Create default GHFrom instance - */ - public GHFrom() { - } - - private String from; - - /** - * Previous comment value that was changed. - * - * @return previous value - */ - public String getFrom() { - return from; - } + @CheckForNull + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHLabel getLabel() { + return label; } } /** - * A review comment was added to a pull request. + * A discussion comment was created, deleted, or edited. * * @see - * pull_request_review_comment event - * @see Pull Request Review Comments + * "https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#discussion_comment"> + * discussion event */ - public static class PullRequestReviewComment extends GHEventPayload { + public static class DiscussionComment extends GHEventPayload { + + private GHRepositoryDiscussionComment comment; + + private GHRepositoryDiscussion discussion; /** - * Create default PullRequestReviewComment instance + * Create default DiscussionComment instance */ - public PullRequestReviewComment() { + public DiscussionComment() { } - private GHPullRequestReviewComment comment; - private GHPullRequest pullRequest; - private CommentChanges changes; - /** - * Gets comment. + * Gets discussion comment. * - * @return the comment + * @return the discussion */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHPullRequestReviewComment getComment() { + public GHRepositoryDiscussionComment getComment() { return comment; } /** - * Get changes (for action="edited"). + * Gets discussion. * - * @return changes + * @return the discussion */ - public CommentChanges getChanges() { - return changes; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHRepositoryDiscussion getDiscussion() { + return discussion; } + } + + /** + * A user forked a repository. + * + * @see fork + * event + * @see Forks + */ + public static class Fork extends GHEventPayload { + + private GHRepository forkee; /** - * Gets pull request. - * - * @return the pull request + * Create default Fork instance */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHPullRequest getPullRequest() { - return pullRequest; + public Fork() { } /** - * Late bind. + * Gets forkee. + * + * @return the forkee */ - @Override - void lateBind() { - if (comment == null) - throw new IllegalStateException( - "Expected pull_request_review_comment payload, but got something else. Maybe we've got another type of event?"); - super.lateBind(); - comment.wrapUp(pullRequest); - - GHRepository repository = getRepository(); - if (repository != null) { - pullRequest.wrapUp(repository); - } + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHRepository getForkee() { + return forkee; } } + // List of events that still need to be added: + // ContentReferenceEvent + // DeployKeyEvent DownloadEvent FollowEvent ForkApplyEvent GitHubAppAuthorizationEvent GistEvent GollumEvent + // InstallationEvent InstallationRepositoriesEvent IssuesEvent LabelEvent MarketplacePurchaseEvent MemberEvent + // MembershipEvent MetaEvent MilestoneEvent OrganizationEvent OrgBlockEvent PackageEvent PageBuildEvent + // ProjectCardEvent ProjectColumnEvent ProjectEvent RepositoryDispatchEvent RepositoryImportEvent + // RepositoryVulnerabilityAlertEvent SecurityAdvisoryEvent StarEvent StatusEvent TeamEvent TeamAddEvent WatchEvent + /** - * A Issue has been assigned, unassigned, labeled, unlabeled, opened, edited, milestoned, demilestoned, closed, or - * reopened. + * An installation has been installed, uninstalled, or its permissions have been changed. * - * @see - * issues events - * @see Issues Comments + * @see + * installation event + * @see GitHub App Installation */ - public static class Issue extends GHEventPayload { + public static class Installation extends GHEventPayload { /** - * Create default Issue instance + * A special minimal implementation of a {@link GHRepository} which contains only fields from "Properties of + * repositories" from here */ - public Issue() { - } + public static class Repository { - private GHIssue issue; + private String fullName; - private GHLabel label; + private long id; + @JsonProperty(value = "private") + private boolean isPrivate; + private String name; + private String nodeId; + /** + * Create default Repository instance + */ + public Repository() { + } - private GHIssueChanges changes; + /** + * Gets the full name. + * + * @return the full name + */ + public String getFullName() { + return fullName; + } + + /** + * Get the id. + * + * @return the id + */ + public long getId() { + return id; + } + + /** + * Gets the name. + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * Gets the node id. + * + * @return the node id + */ + public String getNodeId() { + return nodeId; + } + + /** + * Gets the repository private flag. + * + * @return whether the repository is private + */ + public boolean isPrivate() { + return isPrivate; + } + } + + private List ghRepositories = null; + private List repositories; /** - * Gets issue. - * - * @return the issue + * Create default Installation instance */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHIssue getIssue() { - return issue; + public Installation() { } /** - * Gets the added or removed label for labeled/unlabeled events. + * Returns a list of raw, unpopulated repositories. Useful when calling from within Installation event with + * action "deleted". You can't fetch the info for repositories of an already deleted installation. * - * @return label the added or removed label + * @return the list of raw Repository records */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHLabel getLabel() { - return label; + public List getRawRepositories() { + return Collections.unmodifiableList(repositories); } /** - * Get changes (for action="edited"). + * Gets repositories. For the "deleted" action please rather call {@link #getRawRepositories()} * - * @return changes + * @return the repositories */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHIssueChanges getChanges() { - return changes; + public List getRepositories() { + if ("deleted".equalsIgnoreCase(getAction())) { + throw new IllegalStateException("Can't call #getRepositories() on Installation event " + + "with 'deleted' action. Call #getRawRepositories() instead."); + } + + if (ghRepositories == null) { + ghRepositories = new ArrayList<>(repositories.size()); + try { + for (Repository singleRepo : repositories) { + // populate each repository + // the repository information provided here is so limited + // as to be unusable without populating, so we do it eagerly + ghRepositories.add(this.root().getRepositoryById(singleRepo.getId())); + } + } catch (IOException e) { + throw new GHException("Failed to refresh repositories", e); + } + } + + return Collections.unmodifiableList(ghRepositories); } /** @@ -732,61 +661,59 @@ public GHIssueChanges getChanges() { */ @Override void lateBind() { - super.lateBind(); - GHRepository repository = getRepository(); - if (repository != null) { - issue.wrap(repository); + if (getInstallation() == null) { + throw new IllegalStateException( + "Expected installation payload, but got something else. Maybe we've got another type of event?"); } + super.lateBind(); } } /** - * A comment was added to an issue. + * A repository has been added or removed from an installation. * * @see - * issue_comment event - * @see Issue Comments + * "https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#installation_repositories"> + * installation_repositories event + * @see GitHub App installation */ - public static class IssueComment extends GHEventPayload { + public static class InstallationRepositories extends GHEventPayload { + + private List repositoriesAdded; + private List repositoriesRemoved; + private String repositorySelection; /** - * Create default IssueComment instance + * Create default InstallationRepositories instance */ - public IssueComment() { + public InstallationRepositories() { } - private GHIssueComment comment; - private GHIssue issue; - private CommentChanges changes; - /** - * Gets comment. + * Gets repositories added. * - * @return the comment + * @return the repositories */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHIssueComment getComment() { - return comment; + public List getRepositoriesAdded() { + return Collections.unmodifiableList(repositoriesAdded); } /** - * Get changes (for action="edited"). + * Gets repositories removed. * - * @return changes + * @return the repositories */ - public CommentChanges getChanges() { - return changes; + public List getRepositoriesRemoved() { + return Collections.unmodifiableList(repositoriesRemoved); } /** - * Gets issue. + * Gets installation selection. * - * @return the issue + * @return the installation selection */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHIssue getIssue() { - return issue; + public String getRepositorySelection() { + return repositorySelection; } /** @@ -794,41 +721,82 @@ public GHIssue getIssue() { */ @Override void lateBind() { + if (getInstallation() == null) { + throw new IllegalStateException( + "Expected installation_repositories payload, but got something else. Maybe we've got another type of event?"); + } super.lateBind(); - GHRepository repository = getRepository(); - if (repository != null) { - issue.wrap(repository); + List repositories; + if ("added".equals(getAction())) + repositories = repositoriesAdded; + else // action == "removed" + repositories = repositoriesRemoved; + + if (repositories != null && !repositories.isEmpty()) { + try { + for (GHRepository singleRepo : repositories) { // warp each of the repository + singleRepo.populate(); + } + } catch (IOException e) { + throw new GHException("Failed to refresh repositories", e); + } } - comment.wrapUp(issue); } } /** - * A comment was added to a commit. + * A Issue has been assigned, unassigned, labeled, unlabeled, opened, edited, milestoned, demilestoned, closed, or + * reopened. * - * @see - * commit comment - * @see Comments + * @see + * issues events + * @see Issues Comments */ - public static class CommitComment extends GHEventPayload { + public static class Issue extends GHEventPayload { + + private GHIssueChanges changes; + + private GHIssue issue; + + private GHLabel label; /** - * Create default CommitComment instance + * Create default Issue instance */ - public CommitComment() { + public Issue() { } - private GHCommitComment comment; + /** + * Get changes (for action="edited"). + * + * @return changes + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHIssueChanges getChanges() { + return changes; + } /** - * Gets comment. + * Gets issue. * - * @return the comment + * @return the issue */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHCommitComment getComment() { - return comment; + public GHIssue getIssue() { + return issue; + } + + /** + * Gets the added or removed label for labeled/unlabeled events. + * + * May be {@code null} when the unlabeled event was triggered by deleting the label from the repository. + * + * @return label the added or removed label, or {@code null} if the label was deleted + */ + @CheckForNull + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHLabel getLabel() { + return label; } /** @@ -839,184 +807,184 @@ void lateBind() { super.lateBind(); GHRepository repository = getRepository(); if (repository != null) { - comment.wrap(repository); + issue.wrap(repository); } } } /** - * A repository, branch, or tag was created. + * A comment was added to an issue. * - * @see - * create event - * @see Git data + * @see + * issue_comment event + * @see Issue Comments */ - public static class Create extends GHEventPayload { + public static class IssueComment extends GHEventPayload { + + private CommentChanges changes; + private GHIssueComment comment; + private GHIssue issue; /** - * Create default Create instance + * Create default IssueComment instance */ - public Create() { + public IssueComment() { } - private String ref; - private String refType; - private String masterBranch; - private String description; - /** - * Gets ref. + * Get changes (for action="edited"). * - * @return the ref + * @return changes */ - public String getRef() { - return ref; + public CommentChanges getChanges() { + return changes; } /** - * Gets ref type. + * Gets comment. * - * @return the ref type + * @return the comment */ - public String getRefType() { - return refType; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHIssueComment getComment() { + return comment; } /** - * Gets default branch. - * - * Name is an artifact of when "master" was the most common default. + * Gets issue. * - * @return the default branch + * @return the issue */ - public String getMasterBranch() { - return masterBranch; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHIssue getIssue() { + return issue; } /** - * Gets description. - * - * @return the description + * Late bind. */ - public String getDescription() { - return description; + @Override + void lateBind() { + super.lateBind(); + GHRepository repository = getRepository(); + if (repository != null) { + issue.wrap(repository); + } + comment.wrapUp(issue); } } /** - * A branch, or tag was deleted. + * A label was created, edited or deleted. * - * @see - * delete event - * @see Git data + * @see + * label event */ - public static class Delete extends GHEventPayload { + public static class Label extends GHEventPayload { + + private GHLabelChanges changes; + + private GHLabel label; /** - * Create default Delete instance + * Create default Label instance */ - public Delete() { + public Label() { } - private String ref; - private String refType; - /** - * Gets ref. + * Gets changes (for action="edited"). * - * @return the ref + * @return changes */ - public String getRef() { - return ref; + public GHLabelChanges getChanges() { + return changes; } /** - * Gets ref type. + * Gets the label. * - * @return the ref type + * @return the label */ - public String getRefType() { - return refType; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHLabel getLabel() { + return label; } } /** - * A deployment. + * A member event was triggered. * - * @see - * deployment event - * @see Deployments + * @see member event */ - public static class Deployment extends GHEventPayload { + public static class Member extends GHEventPayload { + + private GHMemberChanges changes; + + private GHUser member; /** - * Create default Deployment instance + * Create default Member instance */ - public Deployment() { + public Member() { } - private GHDeployment deployment; - /** - * Gets deployment. + * Gets the changes made to the member. * - * @return the deployment + * @return the changes made to the member */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHDeployment getDeployment() { - return deployment; + public GHMemberChanges getChanges() { + return changes; } /** - * Late bind. + * Gets the member. + * + * @return the member */ - @Override - void lateBind() { - super.lateBind(); - GHRepository repository = getRepository(); - if (repository != null) { - deployment.wrap(repository); - } + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHUser getMember() { + return member; } } /** - * A deployment status. + * A membership event was triggered. * - * @see - * deployment_status event - * @see Deployments + * @see membership event */ - public static class DeploymentStatus extends GHEventPayload { + public static class Membership extends GHEventPayload { + + private GHUser member; + + private GHTeam team; /** - * Create default DeploymentStatus instance + * Create default Membership instance */ - public DeploymentStatus() { + public Membership() { } - private GHDeploymentStatus deploymentStatus; - private GHDeployment deployment; - /** - * Gets deployment status. + * Gets the member. * - * @return the deployment status + * @return the member */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHDeploymentStatus getDeploymentStatus() { - return deploymentStatus; + public GHUser getMember() { + return member; } /** - * Gets deployment. + * Gets the team. * - * @return the deployment + * @return the team */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHDeployment getDeployment() { - return deployment; + public GHTeam getTeam() { + return team; } /** @@ -1024,57 +992,71 @@ public GHDeployment getDeployment() { */ @Override void lateBind() { + if (team == null) { + throw new IllegalStateException( + "Expected membership payload, but got something else. Maybe we've got another type of event?"); + } super.lateBind(); - GHRepository repository = getRepository(); - if (repository != null) { - deployment.wrap(repository); - deploymentStatus.lateBind(repository); + GHOrganization organization = getOrganization(); + if (organization == null) { + throw new IllegalStateException("Organization must not be null"); } + team.wrapUp(organization); } } /** - * A user forked a repository. + * A ping. * - * @see fork - * event - * @see Forks + * ping + * event */ - public static class Fork extends GHEventPayload { + public static class Ping extends GHEventPayload { /** - * Create default Fork instance + * Create default Ping instance */ - public Fork() { + public Ping() { } - private GHRepository forkee; - - /** - * Gets forkee. - * - * @return the forkee - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHRepository getForkee() { - return forkee; - } } /** - * A ping. + * A project v2 item was archived, converted, created, edited, restored, deleted, or reordered. * - * ping - * event + * @see projects_v2_item + * event */ - public static class Ping extends GHEventPayload { + public static class ProjectsV2Item extends GHEventPayload { + private GHProjectsV2ItemChanges changes; + + private GHProjectsV2Item projectsV2Item; /** - * Create default Ping instance + * Create default ProjectsV2Item instance */ - public Ping() { + public ProjectsV2Item() { + } + + /** + * Gets the changes. + * + * @return the changes + */ + public GHProjectsV2ItemChanges getChanges() { + return changes; } + /** + * Gets the projects V 2 item. + * + * @return the projects V 2 item + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHProjectsV2Item getProjectsV2Item() { + return projectsV2Item; + } } /** @@ -1094,183 +1076,243 @@ public Public() { } /** - * A commit was pushed. + * A pull request status has changed. * - * @see push - * event + * @see + * pull_request event + * @see Pull Requests */ - public static class Push extends GHEventPayload { + @SuppressFBWarnings(value = { "NP_UNWRITTEN_FIELD" }, justification = "JSON API") + public static class PullRequest extends GHEventPayload { + private GHPullRequestChanges changes; + + private GHLabel label; + private int number; + private GHPullRequest pullRequest; /** - * Create default Push instance + * Create default PullRequest instance */ - public Push() { + public PullRequest() { } - private String head, before; - private boolean created, deleted, forced; - private String ref; - private int size; - private List commits; - private PushCommit headCommit; - private Pusher pusher; - private String compare; + /** + * Get changes (for action="edited"). + * + * @return changes + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHPullRequestChanges getChanges() { + return changes; + } /** - * The SHA of the HEAD commit on the repository. + * Gets the added or removed label for labeled/unlabeled events. * - * @return the head + * May be {@code null} when the unlabeled event was triggered by deleting the label from the repository. + * + * @return label the added or removed label, or {@code null} if the label was deleted */ - public String getHead() { - return head; + @CheckForNull + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHLabel getLabel() { + return label; + } + + /** + * Gets number. + * + * @return the number + */ + public int getNumber() { + return number; } /** - * This is undocumented, but it looks like this captures the commit that the ref was pointing to before the - * push. + * Gets pull request. * - * @return the before + * @return the pull request */ - public String getBefore() { - return before; - } - - @JsonSetter // alias - private void setAfter(String after) { - head = after; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHPullRequest getPullRequest() { + return pullRequest; } /** - * The full Git ref that was pushed. Example: “refs/heads/main” - * - * @return the ref + * Late bind. */ - public String getRef() { - return ref; + @Override + void lateBind() { + if (pullRequest == null) + throw new IllegalStateException( + "Expected pull_request payload, but got something else. Maybe we've got another type of event?"); + super.lateBind(); + GHRepository repository = getRepository(); + if (repository != null) { + pullRequest.wrapUp(repository); + } } + } + /** + * A review was added to a pull request. + * + * @see + * pull_request_review event + * @see Pull Request Reviews + */ + public static class PullRequestReview extends GHEventPayload { + + private GHPullRequest pullRequest; + + private GHPullRequestReview review; /** - * The number of commits in the push. Is this always the same as {@code getCommits().size()}? - * - * @return the size + * Create default PullRequestReview instance */ - public int getSize() { - return size; + public PullRequestReview() { } /** - * Is created boolean. + * Gets pull request. * - * @return the boolean + * @return the pull request */ - public boolean isCreated() { - return created; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHPullRequest getPullRequest() { + return pullRequest; } /** - * Is deleted boolean. + * Gets review. * - * @return the boolean + * @return the review */ - public boolean isDeleted() { - return deleted; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHPullRequestReview getReview() { + return review; } /** - * Is forced boolean. - * - * @return the boolean + * Late bind. */ - public boolean isForced() { - return forced; + @Override + void lateBind() { + if (review == null) + throw new IllegalStateException( + "Expected pull_request_review payload, but got something else. Maybe we've got another type of event?"); + super.lateBind(); + + review.wrapUp(pullRequest); + + GHRepository repository = getRepository(); + if (repository != null) { + pullRequest.wrapUp(repository); + } } + } + + /** + * A review comment was added to a pull request. + * + * @see + * pull_request_review_comment event + * @see Pull Request Review Comments + */ + public static class PullRequestReviewComment extends GHEventPayload { + private CommentChanges changes; + + private GHPullRequestReviewComment comment; + private GHPullRequest pullRequest; /** - * The list of pushed commits. - * - * @return the commits + * Create default PullRequestReviewComment instance */ - public List getCommits() { - return Collections.unmodifiableList(commits); + public PullRequestReviewComment() { } /** - * The head commit of the push. + * Get changes (for action="edited"). * - * @return the commit + * @return changes */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public PushCommit getHeadCommit() { - return headCommit; + public CommentChanges getChanges() { + return changes; } /** - * Gets pusher. + * Gets comment. * - * @return the pusher + * @return the comment */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public Pusher getPusher() { - return pusher; + public GHPullRequestReviewComment getComment() { + return comment; } /** - * Gets compare. + * Gets pull request. * - * @return compare + * @return the pull request */ - public String getCompare() { - return compare; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHPullRequest getPullRequest() { + return pullRequest; } /** - * The type Pusher. + * Late bind. */ - public static class Pusher { - - /** - * Create default Pusher instance - */ - public Pusher() { - } - - private String name, email; - - /** - * Gets name. - * - * @return the name - */ - public String getName() { - return name; - } + @Override + void lateBind() { + if (comment == null) + throw new IllegalStateException( + "Expected pull_request_review_comment payload, but got something else. Maybe we've got another type of event?"); + super.lateBind(); + comment.wrapUp(pullRequest); - /** - * Gets email. - * - * @return the email - */ - public String getEmail() { - return email; + GHRepository repository = getRepository(); + if (repository != null) { + pullRequest.wrapUp(repository); } } + } + + /** + * A commit was pushed. + * + * @see push + * event + */ + public static class Push extends GHEventPayload { /** * Commit in a push. Note: sha is an alias for id. */ public static class PushCommit { + private List added, removed, modified; + + private GitUser author; + private GitUser committer; + private boolean distinct; + private String url, sha, message, timestamp; /** * Create default PushCommit instance */ public PushCommit() { } - private GitUser author; - private GitUser committer; - private String url, sha, message, timestamp; - private boolean distinct; - private List added, removed, modified; + /** + * Gets added. + * + * @return the added + */ + public List getAdded() { + return Collections.unmodifiableList(added); + } /** * Gets author. @@ -1291,564 +1333,454 @@ public GitUser getCommitter() { } /** - * Points to the commit API resource. - * - * @return the url - */ - public String getUrl() { - return url; - } - - /** - * Gets sha (id). + * Gets message. * - * @return the sha + * @return the message */ - public String getSha() { - return sha; - } - - @JsonSetter - private void setId(String id) { - sha = id; + public String getMessage() { + return message; } /** - * Gets message. + * Gets modified. * - * @return the message + * @return the modified */ - public String getMessage() { - return message; + public List getModified() { + return Collections.unmodifiableList(modified); } /** - * Whether this commit is distinct from any that have been pushed before. + * Gets removed. * - * @return the boolean + * @return the removed */ - public boolean isDistinct() { - return distinct; + public List getRemoved() { + return Collections.unmodifiableList(removed); } /** - * Gets added. + * Gets sha (id). * - * @return the added + * @return the sha */ - public List getAdded() { - return Collections.unmodifiableList(added); + public String getSha() { + return sha; } /** - * Gets removed. + * Obtains the timestamp of the commit. * - * @return the removed + * @return the timestamp */ - public List getRemoved() { - return Collections.unmodifiableList(removed); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getTimestamp() { + return GitHubClient.parseInstant(timestamp); } /** - * Gets modified. + * Points to the commit API resource. * - * @return the modified + * @return the url */ - public List getModified() { - return Collections.unmodifiableList(modified); + public String getUrl() { + return url; } /** - * Obtains the timestamp of the commit. + * Whether this commit is distinct from any that have been pushed before. * - * @return the timestamp + * @return the boolean */ - public Date getTimestamp() { - return GitHubClient.parseDate(timestamp); + public boolean isDistinct() { + return distinct; } - } - } - /** - * A release was added to the repo. - * - * @see - * release event - * @see Releases - */ - @SuppressFBWarnings(value = { "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" }, - justification = "Constructed by JSON deserialization") - public static class Release extends GHEventPayload { + @JsonSetter + private void setId(String id) { + sha = id; + } - /** - * Create default Release instance - */ - public Release() { } - private GHRelease release; - /** - * Gets release. - * - * @return the release + * The type Pusher. */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHRelease getRelease() { - return release; - } - } + public static class Pusher { - /** - * A repository was created, deleted, made public, or made private. - * - * @see - * repository event - * @see Repositories - */ - public static class Repository extends GHEventPayload { + private String name, email; - /** - * Create default Repository instance - */ - public Repository() { - } + /** + * Create default Pusher instance + */ + public Pusher() { + } - private GHRepositoryChanges changes; + /** + * Gets email. + * + * @return the email + */ + public String getEmail() { + return email; + } - /** - * Get changes. - * - * @return GHRepositoryChanges - */ - public GHRepositoryChanges getChanges() { - return changes; + /** + * Gets name. + * + * @return the name + */ + public String getName() { + return name; + } } + private List commits; + private String compare; + private boolean created, deleted, forced; + private String head, before; + private PushCommit headCommit; + private Pusher pusher; + private String ref; - } - - /** - * A git commit status was changed. - * - * @see - * status event - * @see Repository Statuses - */ - public static class Status extends GHEventPayload { + private int size; /** - * Create default Status instance + * Create default Push instance */ - public Status() { + public Push() { } - private String context; - private String description; - private GHCommitState state; - private GHCommit commit; - private String targetUrl; - /** - * Gets the status content. + * This is undocumented, but it looks like this captures the commit that the ref was pointing to before the + * push. * - * @return status content + * @return the before */ - public String getContext() { - return context; + public String getBefore() { + return before; } /** - * The optional link added to the status. + * The list of pushed commits. * - * @return a url + * @return the commits */ - public String getTargetUrl() { - return targetUrl; + public List getCommits() { + return Collections.unmodifiableList(commits); } /** - * Gets the status description. + * Gets compare. * - * @return status description + * @return compare */ - public String getDescription() { - return description; + public String getCompare() { + return compare; } /** - * Gets the status state. + * The SHA of the HEAD commit on the repository. * - * @return status state + * @return the head */ - public GHCommitState getState() { - return state; + public String getHead() { + return head; } /** - * Gets the commit associated with the status event. + * The head commit of the push. * - * @return commit + * @return the commit */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHCommit getCommit() { - return commit; - } - - /** - * Late bind. - */ - @Override - void lateBind() { - - if (state == null) { - throw new IllegalStateException( - "Expected status payload, but got something else. Maybe we've got another type of event?"); - } - super.lateBind(); - - GHRepository repository = getRepository(); - if (repository != null) { - commit.wrapUp(repository); - } - } - } - - /** - * Occurs when someone triggered a workflow run or sends a POST request to the "Create a workflow dispatch event" - * endpoint. - * - * @see - * workflow dispatch event - * @see Events that - * trigger workflows - */ - public static class WorkflowDispatch extends GHEventPayload { - - /** - * Create default WorkflowDispatch instance - */ - public WorkflowDispatch() { + public PushCommit getHeadCommit() { + return headCommit; } - private Map inputs; - private String ref; - private String workflow; - /** - * Gets the map of input parameters passed to the workflow. + * Gets pusher. * - * @return the map of input parameters + * @return the pusher */ - public Map getInputs() { - return Collections.unmodifiableMap(inputs); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public Pusher getPusher() { + return pusher; } /** - * Gets the ref of the branch (e.g. refs/heads/main) + * The full Git ref that was pushed. Example: “refs/heads/main” * - * @return the ref of the branch + * @return the ref */ public String getRef() { return ref; } /** - * Gets the path of the workflow file (e.g. .github/workflows/hello-world-workflow.yml). + * The number of commits in the push. Is this always the same as {@code getCommits().size()}? * - * @return the path of the workflow file - */ - public String getWorkflow() { - return workflow; - } - } - - /** - * A workflow run was requested or completed. - * - * @see - * workflow run event - * @see Actions Workflow Runs - */ - public static class WorkflowRun extends GHEventPayload { - - /** - * Create default WorkflowRun instance + * @return the size */ - public WorkflowRun() { + public int getSize() { + return size; } - private GHWorkflowRun workflowRun; - private GHWorkflow workflow; - /** - * Gets the workflow run. + * Is created boolean. * - * @return the workflow run + * @return the boolean */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHWorkflowRun getWorkflowRun() { - return workflowRun; + public boolean isCreated() { + return created; } /** - * Gets the associated workflow. + * Is deleted boolean. * - * @return the associated workflow + * @return the boolean */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHWorkflow getWorkflow() { - return workflow; + public boolean isDeleted() { + return deleted; } /** - * Late bind. + * Is forced boolean. + * + * @return the boolean */ - @Override - void lateBind() { - if (workflowRun == null || workflow == null) { - throw new IllegalStateException( - "Expected workflow and workflow_run payload, but got something else. Maybe we've got another type of event?"); - } - super.lateBind(); - GHRepository repository = getRepository(); - if (repository == null) { - throw new IllegalStateException("Repository must not be null"); - } - workflowRun.wrapUp(repository); - workflow.wrapUp(repository); + public boolean isForced() { + return forced; + } + + @JsonSetter // alias + private void setAfter(String after) { + head = after; } } /** - * A workflow job has been queued, is in progress, or has been completed. + * A release was added to the repo. * - * @see - * workflow job event - * @see Actions Workflow Jobs + * @see + * release event + * @see Releases */ - public static class WorkflowJob extends GHEventPayload { + @SuppressFBWarnings(value = { "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" }, + justification = "Constructed by JSON deserialization") + public static class Release extends GHEventPayload { + + private GHRelease release; /** - * Create default WorkflowJob instance + * Create default Release instance */ - public WorkflowJob() { + public Release() { } - private GHWorkflowJob workflowJob; - /** - * Gets the workflow job. + * Gets release. * - * @return the workflow job + * @return the release */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHWorkflowJob getWorkflowJob() { - return workflowJob; - } - - /** - * Late bind. - */ - @Override - void lateBind() { - if (workflowJob == null) { - throw new IllegalStateException( - "Expected workflow_job payload, but got something else. Maybe we've got another type of event?"); - } - super.lateBind(); - GHRepository repository = getRepository(); - if (repository == null) { - throw new IllegalStateException("Repository must not be null"); - } - workflowJob.wrapUp(repository); + public GHRelease getRelease() { + return release; } } /** - * A label was created, edited or deleted. + * A repository was created, deleted, made public, or made private. * - * @see - * label event + * @see + * repository event + * @see Repositories */ - public static class Label extends GHEventPayload { - - /** - * Create default Label instance - */ - public Label() { - } - - private GHLabel label; + public static class Repository extends GHEventPayload { - private GHLabelChanges changes; + private GHRepositoryChanges changes; /** - * Gets the label. - * - * @return the label + * Create default Repository instance */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHLabel getLabel() { - return label; + public Repository() { } /** - * Gets changes (for action="edited"). + * Get changes. * - * @return changes + * @return GHRepositoryChanges */ - public GHLabelChanges getChanges() { + public GHRepositoryChanges getChanges() { return changes; } + } /** - * A discussion was closed, reopened, created, edited, deleted, pinned, unpinned, locked, unlocked, transferred, - * category_changed, answered, or unanswered. + * A star was created or deleted on a repository. * * @see - * discussion event + * "https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#star">star + * event */ - public static class Discussion extends GHEventPayload { - - /** - * Create default Discussion instance - */ - public Discussion() { - } - - private GHRepositoryDiscussion discussion; + public static class Star extends GHEventPayload { - private GHLabel label; + private String starredAt; /** - * Gets discussion. - * - * @return the discussion + * Create default Star instance */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHRepositoryDiscussion getDiscussion() { - return discussion; + public Star() { } /** - * Gets the added or removed label for labeled/unlabeled events. + * Gets the date when the star is added. Is null when the star is deleted. * - * @return label the added or removed label + * @return the date when the star is added */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHLabel getLabel() { - return label; + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getStarredAt() { + return GitHubClient.parseInstant(starredAt); } } /** - * A discussion comment was created, deleted, or edited. + * A git commit status was changed. * - * @see - * discussion event + * @see + * status event + * @see Repository Statuses */ - public static class DiscussionComment extends GHEventPayload { + public static class Status extends GHEventPayload { + private GHCommit commit; + + private String context; + private String description; + private GHCommitState state; + private String targetUrl; /** - * Create default DiscussionComment instance + * Create default Status instance */ - public DiscussionComment() { + public Status() { } - private GHRepositoryDiscussion discussion; - - private GHRepositoryDiscussionComment comment; - /** - * Gets discussion. + * Gets the commit associated with the status event. * - * @return the discussion + * @return commit */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHRepositoryDiscussion getDiscussion() { - return discussion; + public GHCommit getCommit() { + return commit; } /** - * Gets discussion comment. + * Gets the status content. * - * @return the discussion + * @return status content */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHRepositoryDiscussionComment getComment() { - return comment; + public String getContext() { + return context; } - } - - /** - * A star was created or deleted on a repository. - * - * @see star - * event - */ - public static class Star extends GHEventPayload { /** - * Create default Star instance + * Gets the status description. + * + * @return status description */ - public Star() { + public String getDescription() { + return description; } - private String starredAt; + /** + * Gets the status state. + * + * @return status state + */ + public GHCommitState getState() { + return state; + } /** - * Gets the date when the star is added. Is null when the star is deleted. + * The optional link added to the status. * - * @return the date when the star is added + * @return a url + */ + public String getTargetUrl() { + return targetUrl; + } + + /** + * Late bind. */ - public Date getStarredAt() { - return GitHubClient.parseDate(starredAt); + @Override + void lateBind() { + + if (state == null) { + throw new IllegalStateException( + "Expected status payload, but got something else. Maybe we've got another type of event?"); + } + super.lateBind(); + + GHRepository repository = getRepository(); + if (repository != null) { + commit.wrapUp(repository); + } } } /** - * A project v2 item was archived, converted, created, edited, restored, deleted, or reordered. + * A team event was triggered. * - * @see projects_v2_item - * event + * @see team event */ - public static class ProjectsV2Item extends GHEventPayload { + public static class Team extends GHEventPayload { + + private GHTeamChanges changes; + + private GHTeam team; /** - * Create default ProjectsV2Item instance + * Create default Team instance */ - public ProjectsV2Item() { + public Team() { } - private GHProjectsV2Item projectsV2Item; - private GHProjectsV2ItemChanges changes; + /** + * Gets the changes made to the team. + * + * @return the changes made to the team, null unless action is "edited". + */ + public GHTeamChanges getChanges() { + return changes; + } /** - * Gets the projects V 2 item. + * Gets the team. * - * @return the projects V 2 item + * @return the team */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHProjectsV2Item getProjectsV2Item() { - return projectsV2Item; + public GHTeam getTeam() { + return team; } /** - * Gets the changes. - * - * @return the changes + * Late bind. */ - public GHProjectsV2ItemChanges getChanges() { - return changes; + @Override + void lateBind() { + if (team == null) { + throw new IllegalStateException( + "Expected team payload, but got something else. Maybe we've got another type of event?"); + } + super.lateBind(); + GHOrganization organization = getOrganization(); + if (organization == null) { + throw new IllegalStateException("Organization must not be null"); + } + team.wrapUp(organization); } } @@ -1859,14 +1791,14 @@ public GHProjectsV2ItemChanges getChanges() { */ public static class TeamAdd extends GHEventPayload { + private GHTeam team; + /** * Create default TeamAdd instance */ public TeamAdd() { } - private GHTeam team; - /** * Gets the team. * @@ -1896,131 +1828,139 @@ void lateBind() { } /** - * A team event was triggered. + * Occurs when someone triggered a workflow run or sends a POST request to the "Create a workflow dispatch event" + * endpoint. * - * @see team event + * @see + * workflow dispatch event + * @see Events that + * trigger workflows */ - public static class Team extends GHEventPayload { + public static class WorkflowDispatch extends GHEventPayload { + + private Map inputs; + private String ref; + private String workflow; /** - * Create default Team instance + * Create default WorkflowDispatch instance */ - public Team() { + public WorkflowDispatch() { } - private GHTeam team; - - private GHTeamChanges changes; - /** - * Gets the team. + * Gets the map of input parameters passed to the workflow. * - * @return the team + * @return the map of input parameters */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHTeam getTeam() { - return team; + public Map getInputs() { + return Collections.unmodifiableMap(inputs); } /** - * Gets the changes made to the team. + * Gets the ref of the branch (e.g. refs/heads/main) * - * @return the changes made to the team, null unless action is "edited". + * @return the ref of the branch */ - public GHTeamChanges getChanges() { - return changes; + public String getRef() { + return ref; } /** - * Late bind. + * Gets the path of the workflow file (e.g. .github/workflows/hello-world-workflow.yml). + * + * @return the path of the workflow file */ - @Override - void lateBind() { - if (team == null) { - throw new IllegalStateException( - "Expected team payload, but got something else. Maybe we've got another type of event?"); - } - super.lateBind(); - GHOrganization organization = getOrganization(); - if (organization == null) { - throw new IllegalStateException("Organization must not be null"); - } - team.wrapUp(organization); + public String getWorkflow() { + return workflow; } } /** - * A member event was triggered. + * A workflow job has been queued, is in progress, or has been completed. * - * @see member event + * @see + * workflow job event + * @see Actions Workflow Jobs */ - public static class Member extends GHEventPayload { + public static class WorkflowJob extends GHEventPayload { + + private GHWorkflowJob workflowJob; /** - * Create default Member instance + * Create default WorkflowJob instance */ - public Member() { + public WorkflowJob() { } - private GHUser member; - - private GHMemberChanges changes; - /** - * Gets the member. + * Gets the workflow job. * - * @return the member + * @return the workflow job */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHUser getMember() { - return member; + public GHWorkflowJob getWorkflowJob() { + return workflowJob; } /** - * Gets the changes made to the member. - * - * @return the changes made to the member + * Late bind. */ - public GHMemberChanges getChanges() { - return changes; + @Override + void lateBind() { + if (workflowJob == null) { + throw new IllegalStateException( + "Expected workflow_job payload, but got something else. Maybe we've got another type of event?"); + } + super.lateBind(); + GHRepository repository = getRepository(); + if (repository == null) { + throw new IllegalStateException("Repository must not be null"); + } + workflowJob.wrapUp(repository); } } /** - * A membership event was triggered. + * A workflow run was requested or completed. * - * @see membership event + * @see + * workflow run event + * @see Actions Workflow Runs */ - public static class Membership extends GHEventPayload { + public static class WorkflowRun extends GHEventPayload { + + private GHWorkflow workflow; + private GHWorkflowRun workflowRun; /** - * Create default Membership instance + * Create default WorkflowRun instance */ - public Membership() { + public WorkflowRun() { } - private GHTeam team; - - private GHUser member; - /** - * Gets the team. + * Gets the associated workflow. * - * @return the team + * @return the associated workflow */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHTeam getTeam() { - return team; + public GHWorkflow getWorkflow() { + return workflow; } /** - * Gets the member. + * Gets the workflow run. * - * @return the member + * @return the workflow run */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHUser getMember() { - return member; + public GHWorkflowRun getWorkflowRun() { + return workflowRun; } /** @@ -2028,16 +1968,91 @@ public GHUser getMember() { */ @Override void lateBind() { - if (team == null) { + if (workflowRun == null || workflow == null) { throw new IllegalStateException( - "Expected membership payload, but got something else. Maybe we've got another type of event?"); + "Expected workflow and workflow_run payload, but got something else. Maybe we've got another type of event?"); } super.lateBind(); - GHOrganization organization = getOrganization(); - if (organization == null) { - throw new IllegalStateException("Organization must not be null"); + GHRepository repository = getRepository(); + if (repository == null) { + throw new IllegalStateException("Repository must not be null"); } - team.wrapUp(organization); + workflowRun.wrapUp(repository); + workflow.wrapUp(repository); } } + + // https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/webhook-events-and-payloads#webhook-payload-object-common-properties + // Webhook payload object common properties: action, sender, repository, organization, installation + private String action; + + private GHAppInstallation installation; + + private GHOrganization organization; + + private GHRepository repository; + + private GHUser sender; + + /** + * Instantiates a new GH event payload. + */ + GHEventPayload() { + } + + /** + * Gets the action for the triggered event. Most but not all webhook payloads contain an action property that + * contains the specific activity that triggered the event. + * + * @return event action + */ + public String getAction() { + return action; + } + + /** + * Gets installation. + * + * @return the installation + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHAppInstallation getInstallation() { + return installation; + } + + /** + * Gets organization. + * + * @return the organization + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHOrganization getOrganization() { + return organization; + } + + /** + * Gets repository. + * + * @return the repository + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHRepository getRepository() { + return repository; + } + + /** + * Gets the sender or {@code null} if accessed via the events API. + * + * @return the sender or {@code null} if accessed via the events API. + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHUser getSender() { + return sender; + } + + /** + * Late bind. + */ + void lateBind() { + } } diff --git a/src/main/java/org/kohsuke/github/GHExternalGroup.java b/src/main/java/org/kohsuke/github/GHExternalGroup.java index 50518412e1..f5fb69a78d 100644 --- a/src/main/java/org/kohsuke/github/GHExternalGroup.java +++ b/src/main/java/org/kohsuke/github/GHExternalGroup.java @@ -1,8 +1,10 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; +import java.time.Instant; import java.util.Collections; import java.util.Date; import java.util.List; @@ -14,61 +16,16 @@ */ public class GHExternalGroup extends GitHubInteractiveObject implements Refreshable { - /** - * A reference of a team linked to an external group - * - * @author Miguel Esteban GutiÊrrez - */ - public static class GHLinkedTeam { - - /** - * Create default GHLinkedTeam instance - */ - public GHLinkedTeam() { - } - - /** - * The identifier of the team - */ - @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") - private long teamId; - - /** - * The name of the team - */ - @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") - private String teamName; - - /** - * Get the linked team identifier - * - * @return the id - */ - public long getId() { - return teamId; - } - - /** - * Get the linked team name - * - * @return the name - */ - public String getName() { - return teamName; - } - - } - /** * A reference of an external member linked to an external group */ public static class GHLinkedExternalMember { /** - * Create default GHLinkedExternalMember instance + * The email attached to the user */ - public GHLinkedExternalMember() { - } + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") + private String memberEmail; /** * The internal user ID of the identity @@ -89,10 +46,19 @@ public GHLinkedExternalMember() { private String memberName; /** - * The email attached to the user + * Create default GHLinkedExternalMember instance */ - @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") - private String memberEmail; + public GHLinkedExternalMember() { + } + + /** + * Get the linked member email + * + * @return the email + */ + public String getEmail() { + return memberEmail; + } /** * Get the linked member identifier @@ -121,13 +87,49 @@ public String getName() { return memberName; } + } + + /** + * A reference of a team linked to an external group + * + * @author Miguel Esteban GutiÊrrez + */ + public static class GHLinkedTeam { + + /** + * The identifier of the team + */ + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") + private long teamId; + + /** + * The name of the team + */ + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") + private String teamName; + + /** + * Create default GHLinkedTeam instance + */ + public GHLinkedTeam() { + } + /** - * Get the linked member email + * Get the linked team identifier * - * @return the email + * @return the id */ - public String getEmail() { - return memberEmail; + public long getId() { + return teamId; + } + + /** + * Get the linked team name + * + * @return the name + */ + public String getName() { + return teamName; } } @@ -145,10 +147,12 @@ public String getEmail() { private String groupName; /** - * The date when the group was last updated at + * The external members linked to this group */ @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") - private String updatedAt; + private List members; + + private GHOrganization organization; /** * The teams linked to this group @@ -157,56 +161,32 @@ public String getEmail() { private List teams; /** - * The external members linked to this group + * The date when the group was last updated at */ @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") - private List members; + private String updatedAt; GHExternalGroup() { this.teams = Collections.emptyList(); this.members = Collections.emptyList(); } - private GHOrganization organization; - /** - * Wrap up. - * - * @param owner - * the owner - */ - GHExternalGroup wrapUp(final GHOrganization owner) { - this.organization = owner; - return this; - } - - /** - * Wrap up. - * - * @param root - * the root - */ - void wrapUp(final GitHub root) { // auto-wrapUp when organization is known from GET /orgs/{org}/external-groups - wrapUp(organization); - } - - /** - * Gets organization. + * Get the external group id. * - * @return the organization + * @return the id */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHOrganization getOrganization() { - return organization; + public long getId() { + return groupId; } /** - * Get the external group id. + * Get the external members linked to this group. * - * @return the id + * @return the external members */ - public long getId() { - return groupId; + public List getMembers() { + return Collections.unmodifiableList(members); } /** @@ -219,12 +199,13 @@ public String getName() { } /** - * Get the external group last update date. + * Gets organization. * - * @return the date + * @return the organization */ - public Date getUpdatedAt() { - return GitHubClient.parseDate(updatedAt); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHOrganization getOrganization() { + return organization; } /** @@ -237,12 +218,13 @@ public List getTeams() { } /** - * Get the external members linked to this group. + * Get the external group last update date. * - * @return the external members + * @return the date */ - public List getMembers() { - return Collections.unmodifiableList(members); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getUpdatedAt() { + return GitHubClient.parseInstant(updatedAt); } /** @@ -260,4 +242,25 @@ private String api(final String tail) { return "/orgs/" + organization.getLogin() + "/external-group/" + getId() + tail; } + /** + * Wrap up. + * + * @param owner + * the owner + */ + GHExternalGroup wrapUp(final GHOrganization owner) { + this.organization = owner; + return this; + } + + /** + * Wrap up. + * + * @param root + * the root + */ + void wrapUp(final GitHub root) { // auto-wrapUp when organization is known from GET /orgs/{org}/external-groups + wrapUp(organization); + } + } diff --git a/src/main/java/org/kohsuke/github/GHFork.java b/src/main/java/org/kohsuke/github/GHFork.java index 34434e137f..620cab32e0 100644 --- a/src/main/java/org/kohsuke/github/GHFork.java +++ b/src/main/java/org/kohsuke/github/GHFork.java @@ -7,18 +7,18 @@ public enum GHFork { /** - * Search in the parent repository and in forks with more stars than the parent repository. + * Search only in forks with more stars than the parent repository. * - * Forks with the same or fewer stars than the parent repository are still ignored. + * The parent repository is ignored. If no forks have more stars than the parent, no results will be returned. */ - PARENT_AND_FORKS("true"), + FORKS_ONLY("only"), /** - * Search only in forks with more stars than the parent repository. + * Search in the parent repository and in forks with more stars than the parent repository. * - * The parent repository is ignored. If no forks have more stars than the parent, no results will be returned. + * Forks with the same or fewer stars than the parent repository are still ignored. */ - FORKS_ONLY("only"), + PARENT_AND_FORKS("true"), /** * (Default) Search only the parent repository. diff --git a/src/main/java/org/kohsuke/github/GHGist.java b/src/main/java/org/kohsuke/github/GHGist.java index 1103dd32d0..e7c4042f5b 100644 --- a/src/main/java/org/kohsuke/github/GHGist.java +++ b/src/main/java/org/kohsuke/github/GHGist.java @@ -23,21 +23,21 @@ */ public class GHGist extends GHObject { - /** The owner. */ - final GHUser owner; - - private String forks_url, commits_url, id, git_pull_url, git_push_url, html_url; + private int comments; - @JsonProperty("public") - private boolean _public; + private String commentsUrl; private String description; - private int comments; + private final Map files; - private String comments_url; + private String forksUrl, commitsUrl, id, gitPullUrl, gitPushUrl, htmlUrl; - private final Map files; + @JsonProperty("public") + private boolean isPublic; + + /** The owner. */ + final GHUser owner; @JsonCreator private GHGist(@JsonProperty("owner") GHUser owner, @JsonProperty("files") Map files) { @@ -49,46 +49,60 @@ private GHGist(@JsonProperty("owner") GHUser owner, @JsonProperty("files") Map getFiles() { + return Collections.unmodifiableMap(files); } /** - * Is public boolean. + * Gets forks url. * - * @return the boolean + * @return the forks url */ - public boolean isPublic() { - return _public; + public String getForksUrl() { + return forksUrl; } /** - * Gets description. + * Gets the id for this Gist. Unlike most other GitHub objects, the id for Gists can be non-numeric, such as + * "aa5a315d61ae9438b18d". This should be used instead of {@link #getId()}. * - * @return the description + * @return id of this Gist */ - public String getDescription() { - return description; + public String getGistId() { + return this.id; } /** - * Gets comment count. + * Gets git pull url. * - * @return the comment count + * @return URL like https://gist.github.com/gists/12345.git */ - public int getCommentCount() { - return comments; + public String getGitPullUrl() { + return gitPullUrl; } /** - * Gets comments url. + * Gets git push url. * - * @return API URL of listing comments. + * @return the git push url */ - public String getCommentsUrl() { - return comments_url; + public String getGitPushUrl() { + return gitPushUrl; } /** - * Gets file. + * Get the html url. * - * @param name - * the name - * @return the file + * @return the github html url */ - public GHGistFile getFile(String name) { - return files.get(name); + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); } /** - * Gets files. + * Unlike most other GitHub objects, the id for Gists can be non-numeric, such as "aa5a315d61ae9438b18d". If the id + * is numeric, this method will get it. If id is not numeric, this will throw a runtime + * {@link NumberFormatException}. * - * @return the files + * @return id of the Gist. + * @deprecated Use {@link #getGistId()} instead. */ - public Map getFiles() { - return Collections.unmodifiableMap(files); + @Deprecated + @Override + public long getId() { + return Long.parseLong(getGistId()); } /** - * Gets the api tail url. + * Gets owner. * - * @param tail - * the tail - * @return the api tail url + * @return User that owns this Gist. */ - String getApiTailUrl(String tail) { - String result = "/gists/" + id; - if (!StringUtils.isBlank(tail)) { - result += StringUtils.prependIfMissing(tail, "/"); - } - return result; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHUser getOwner() { + return owner; } /** - * Star. + * Hash code. * - * @throws IOException - * the io exception + * @return the int */ - public void star() throws IOException { - root().createRequest().method("PUT").withUrlPath(getApiTailUrl("star")).send(); + @Override + public int hashCode() { + return id.hashCode(); } /** - * Unstar. + * Is public boolean. * - * @throws IOException - * the io exception + * @return the boolean */ - public void unstar() throws IOException { - root().createRequest().method("DELETE").withUrlPath(getApiTailUrl("star")).send(); + public boolean isPublic() { + return isPublic; } /** @@ -229,17 +243,6 @@ public boolean isStarred() throws IOException { return root().createRequest().withUrlPath(getApiTailUrl("star")).fetchHttpStatusCode() / 100 == 2; } - /** - * Forks this gist into your own. - * - * @return the gh gist - * @throws IOException - * the io exception - */ - public GHGist fork() throws IOException { - return root().createRequest().method("POST").withUrlPath(getApiTailUrl("forks")).fetch(GHGist.class); - } - /** * List forks paged iterable. * @@ -250,49 +253,46 @@ public PagedIterable listForks() { } /** - * Deletes this gist. + * Star. * * @throws IOException * the io exception */ - public void delete() throws IOException { - root().createRequest().method("DELETE").withUrlPath("/gists/" + id).send(); + public void star() throws IOException { + root().createRequest().method("PUT").withUrlPath(getApiTailUrl("star")).send(); } /** - * Updates this gist via a builder. + * Unstar. * - * @return the gh gist updater + * @throws IOException + * the io exception */ - public GHGistUpdater update() { - return new GHGistUpdater(this); + public void unstar() throws IOException { + root().createRequest().method("DELETE").withUrlPath(getApiTailUrl("star")).send(); } /** - * Equals. + * Updates this gist via a builder. * - * @param o - * the o - * @return true, if successful + * @return the gh gist updater */ - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - GHGist ghGist = (GHGist) o; - return id.equals(ghGist.id); - + public GHGistUpdater update() { + return new GHGistUpdater(this); } /** - * Hash code. + * Gets the api tail url. * - * @return the int + * @param tail + * the tail + * @return the api tail url */ - @Override - public int hashCode() { - return id.hashCode(); + String getApiTailUrl(String tail) { + String result = "/gists/" + id; + if (!StringUtils.isBlank(tail)) { + result += StringUtils.prependIfMissing(tail, "/"); + } + return result; } } diff --git a/src/main/java/org/kohsuke/github/GHGistBuilder.java b/src/main/java/org/kohsuke/github/GHGistBuilder.java index c2797b628c..5d7cb908d9 100644 --- a/src/main/java/org/kohsuke/github/GHGistBuilder.java +++ b/src/main/java/org/kohsuke/github/GHGistBuilder.java @@ -14,8 +14,8 @@ * @see GitHub#createGist() GitHub#createGist() */ public class GHGistBuilder { - private final Requester req; private final LinkedHashMap files = new LinkedHashMap(); + private final Requester req; /** * Instantiates a new Gh gist builder. @@ -28,26 +28,26 @@ public GHGistBuilder(GitHub root) { } /** - * Description gh gist builder. + * Creates a Gist based on the parameters specified thus far. * - * @param desc - * the desc - * @return the gh gist builder + * @return created Gist + * @throws IOException + * if Gist cannot be created. */ - public GHGistBuilder description(String desc) { - req.with("description", desc); - return this; + public GHGist create() throws IOException { + req.with("files", files); + return req.withUrlPath("/gists").fetch(GHGist.class); } /** - * Public gh gist builder. + * Description gh gist builder. * - * @param v - * the v + * @param desc + * the desc * @return the gh gist builder */ - public GHGistBuilder public_(boolean v) { - req.with("public", v); + public GHGistBuilder description(String desc) { + req.with("description", desc); return this; } @@ -66,14 +66,14 @@ public GHGistBuilder file(@Nonnull String fileName, @Nonnull String content) { } /** - * Creates a Gist based on the parameters specified thus far. + * Public gh gist builder. * - * @return created Gist - * @throws IOException - * if Gist cannot be created. + * @param v + * the v + * @return the gh gist builder */ - public GHGist create() throws IOException { - req.with("files", files); - return req.withUrlPath("/gists").fetch(GHGist.class); + public GHGistBuilder public_(boolean v) { + req.with("public", v); + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHGistFile.java b/src/main/java/org/kohsuke/github/GHGistFile.java index 8a4b8f48e5..da60cc902d 100644 --- a/src/main/java/org/kohsuke/github/GHGistFile.java +++ b/src/main/java/org/kohsuke/github/GHGistFile.java @@ -10,18 +10,27 @@ */ public class GHGistFile { + private String rawUrl, type, language, content; + + private int size; + + private boolean truncated; + /** The file name. */ + /* package almost final */ String fileName; /** * Create default GHGistFile instance */ public GHGistFile() { } - /** The file name. */ - /* package almost final */ String fileName; - - private int size; - private String raw_url, type, language, content; - private boolean truncated; + /** + * Content of this file. + * + * @return the content + */ + public String getContent() { + return content; + } /** * Gets file name. @@ -33,12 +42,12 @@ public String getFileName() { } /** - * File size in bytes. + * Gets language. * - * @return the size + * @return the language */ - public int getSize() { - return size; + public String getLanguage() { + return language; } /** @@ -47,34 +56,25 @@ public int getSize() { * @return the raw url */ public String getRawUrl() { - return raw_url; + return rawUrl; } /** - * Content type of this Gist, such as "text/plain". - * - * @return the type - */ - public String getType() { - return type; - } - - /** - * Gets language. + * File size in bytes. * - * @return the language + * @return the size */ - public String getLanguage() { - return language; + public int getSize() { + return size; } /** - * Content of this file. + * Content type of this Gist, such as "text/plain". * - * @return the content + * @return the type */ - public String getContent() { - return content; + public String getType() { + return type; } /** diff --git a/src/main/java/org/kohsuke/github/GHGistUpdater.java b/src/main/java/org/kohsuke/github/GHGistUpdater.java index 8dbd15a479..cc67de6f89 100644 --- a/src/main/java/org/kohsuke/github/GHGistUpdater.java +++ b/src/main/java/org/kohsuke/github/GHGistUpdater.java @@ -59,6 +59,18 @@ public GHGistUpdater deleteFile(@Nonnull String fileName) { return this; } + /** + * Description gh gist updater. + * + * @param desc + * the desc + * @return the gh gist updater + */ + public GHGistUpdater description(String desc) { + builder.with("description", desc); + return this; + } + /** * Rename file gh gist updater. * @@ -74,6 +86,18 @@ public GHGistUpdater renameFile(@Nonnull String fileName, @Nonnull String newFil return this; } + /** + * Updates the Gist based on the parameters specified thus far. + * + * @return the gh gist + * @throws IOException + * the io exception + */ + public GHGist update() throws IOException { + builder.with("files", files); + return builder.method("PATCH").withUrlPath(base.getApiTailUrl("")).fetch(GHGist.class); + } + /** * Update file gh gist updater. * @@ -107,28 +131,4 @@ public GHGistUpdater updateFile(@Nonnull String fileName, @Nonnull String newFil files.put(fileName, file); return this; } - - /** - * Description gh gist updater. - * - * @param desc - * the desc - * @return the gh gist updater - */ - public GHGistUpdater description(String desc) { - builder.with("description", desc); - return this; - } - - /** - * Updates the Gist based on the parameters specified thus far. - * - * @return the gh gist - * @throws IOException - * the io exception - */ - public GHGist update() throws IOException { - builder.with("files", files); - return builder.method("PATCH").withUrlPath(base.getApiTailUrl("")).fetch(GHGist.class); - } } diff --git a/src/main/java/org/kohsuke/github/GHHook.java b/src/main/java/org/kohsuke/github/GHHook.java index d9e1fc3bd4..cc41288b7f 100644 --- a/src/main/java/org/kohsuke/github/GHHook.java +++ b/src/main/java/org/kohsuke/github/GHHook.java @@ -19,31 +19,41 @@ justification = "JSON API") public abstract class GHHook extends GHObject { - /** - * Create default GHHook instance - */ - public GHHook() { - } + /** The active. */ + boolean active; - /** The name. */ - String name; + /** The config. */ + Map config; /** The events. */ List events; - /** The active. */ - boolean active; + /** The name. */ + String name; - /** The config. */ - Map config; + /** + * Create default GHHook instance + */ + public GHHook() { + } /** - * Gets name. + * Deletes this hook. * - * @return the name + * @throws IOException + * the io exception */ - public String getName() { - return name; + public void delete() throws IOException { + root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); + } + + /** + * Gets config. + * + * @return the config + */ + public Map getConfig() { + return Collections.unmodifiableMap(config); } /** @@ -60,21 +70,21 @@ public EnumSet getEvents() { } /** - * Is active boolean. + * Gets name. * - * @return the boolean + * @return the name */ - public boolean isActive() { - return active; + public String getName() { + return name; } /** - * Gets config. + * Is active boolean. * - * @return the config + * @return the boolean */ - public Map getConfig() { - return Collections.unmodifiableMap(config); + public boolean isActive() { + return active; } /** @@ -89,14 +99,11 @@ public void ping() throws IOException { } /** - * Deletes this hook. + * Gets the api route. * - * @throws IOException - * the io exception + * @return the api route */ - public void delete() throws IOException { - root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); - } + abstract String getApiRoute(); /** * Root. @@ -104,11 +111,4 @@ public void delete() throws IOException { * @return the git hub */ abstract GitHub root(); - - /** - * Gets the api route. - * - * @return the api route - */ - abstract String getApiRoute(); } diff --git a/src/main/java/org/kohsuke/github/GHHooks.java b/src/main/java/org/kohsuke/github/GHHooks.java index addcad3179..25fe143d82 100644 --- a/src/main/java/org/kohsuke/github/GHHooks.java +++ b/src/main/java/org/kohsuke/github/GHHooks.java @@ -14,6 +14,66 @@ */ class GHHooks { + private static class OrgContext extends Context { + private final GHOrganization organization; + + private OrgContext(GHOrganization organization) { + super(organization.root()); + this.organization = organization; + } + + @Override + Class clazz() { + return GHOrgHook.class; + } + + @Override + String collection() { + return String.format("/orgs/%s/hooks", organization.getLogin()); + } + + @Override + Class collectionClass() { + return GHOrgHook[].class; + } + + @Override + GHHook wrap(GHHook hook) { + return ((GHOrgHook) hook).wrap(organization); + } + } + + private static class RepoContext extends Context { + private final GHUser owner; + private final GHRepository repository; + + private RepoContext(GHRepository repository, GHUser owner) { + super(repository.root()); + this.repository = repository; + this.owner = owner; + } + + @Override + Class clazz() { + return GHRepoHook.class; + } + + @Override + String collection() { + return String.format("/repos/%s/%s/hooks", owner.getLogin(), repository.getName()); + } + + @Override + Class collectionClass() { + return GHRepoHook[].class; + } + + @Override + GHHook wrap(GHHook hook) { + return ((GHRepoHook) hook).wrap(repository); + } + } + /** * The Class Context. */ @@ -23,39 +83,6 @@ private Context(GitHub root) { super(root); } - /** - * Gets hooks. - * - * @return the hooks - * @throws IOException - * the io exception - */ - public List getHooks() throws IOException { - - // jdk/eclipse bug - GHHook[] hookArray = root().createRequest().withUrlPath(collection()).fetch(collectionClass()); - // requires this - // to be on separate line - List list = new ArrayList(Arrays.asList(hookArray)); - for (GHHook h : list) - wrap(h); - return list; - } - - /** - * Gets hook. - * - * @param id - * the id - * @return the hook - * @throws IOException - * the io exception - */ - public GHHook getHook(int id) throws IOException { - GHHook hook = root().createRequest().withUrlPath(collection() + "/" + id).fetch(clazz()); - return wrap(hook); - } - /** * Create hook gh hook. * @@ -105,18 +132,37 @@ public void deleteHook(int id) throws IOException { } /** - * Collection. + * Gets hook. * - * @return the string + * @param id + * the id + * @return the hook + * @throws IOException + * the io exception */ - abstract String collection(); + public GHHook getHook(int id) throws IOException { + GHHook hook = root().createRequest().withUrlPath(collection() + "/" + id).fetch(clazz()); + return wrap(hook); + } /** - * Collection class. + * Gets hooks. * - * @return the class + * @return the hooks + * @throws IOException + * the io exception */ - abstract Class collectionClass(); + public List getHooks() throws IOException { + + // jdk/eclipse bug + GHHook[] hookArray = root().createRequest().withUrlPath(collection()).fetch(collectionClass()); + // requires this + // to be on separate line + List list = new ArrayList(Arrays.asList(hookArray)); + for (GHHook h : list) + wrap(h); + return list; + } /** * Clazz. @@ -125,6 +171,20 @@ public void deleteHook(int id) throws IOException { */ abstract Class clazz(); + /** + * Collection. + * + * @return the string + */ + abstract String collection(); + + /** + * Collection class. + * + * @return the class + */ + abstract Class collectionClass(); + /** * Wrap. * @@ -135,64 +195,15 @@ public void deleteHook(int id) throws IOException { abstract GHHook wrap(GHHook hook); } - private static class RepoContext extends Context { - private final GHRepository repository; - private final GHUser owner; - - private RepoContext(GHRepository repository, GHUser owner) { - super(repository.root()); - this.repository = repository; - this.owner = owner; - } - - @Override - String collection() { - return String.format("/repos/%s/%s/hooks", owner.getLogin(), repository.getName()); - } - - @Override - Class collectionClass() { - return GHRepoHook[].class; - } - - @Override - Class clazz() { - return GHRepoHook.class; - } - - @Override - GHHook wrap(GHHook hook) { - return ((GHRepoHook) hook).wrap(repository); - } - } - - private static class OrgContext extends Context { - private final GHOrganization organization; - - private OrgContext(GHOrganization organization) { - super(organization.root()); - this.organization = organization; - } - - @Override - String collection() { - return String.format("/orgs/%s/hooks", organization.getLogin()); - } - - @Override - Class collectionClass() { - return GHOrgHook[].class; - } - - @Override - Class clazz() { - return GHOrgHook.class; - } - - @Override - GHHook wrap(GHHook hook) { - return ((GHOrgHook) hook).wrap(organization); - } + /** + * Org context. + * + * @param organization + * the organization + * @return the context + */ + static Context orgContext(GHOrganization organization) { + return new OrgContext(organization); } /** @@ -207,15 +218,4 @@ GHHook wrap(GHHook hook) { static Context repoContext(GHRepository repository, GHUser owner) { return new RepoContext(repository, owner); } - - /** - * Org context. - * - * @param organization - * the organization - * @return the context - */ - static Context orgContext(GHOrganization organization) { - return new OrgContext(organization); - } } diff --git a/src/main/java/org/kohsuke/github/GHInvitation.java b/src/main/java/org/kohsuke/github/GHInvitation.java index a625597b73..726179d897 100644 --- a/src/main/java/org/kohsuke/github/GHInvitation.java +++ b/src/main/java/org/kohsuke/github/GHInvitation.java @@ -18,18 +18,18 @@ justification = "JSON API") public class GHInvitation extends GHObject { + private String htmlUrl; + + private int id; + private GHUser invitee, inviter; + private String permissions; + private GHRepository repository; /** * Create default GHInvitation instance */ public GHInvitation() { } - private int id; - private GHRepository repository; - private GHUser invitee, inviter; - private String permissions; - private String html_url; - /** * Accept a repository invitation. * @@ -56,6 +56,6 @@ public void decline() throws IOException { * @return the html url */ public URL getHtmlUrl() { - return GitHubClient.parseURL(html_url); + return GitHubClient.parseURL(htmlUrl); } } diff --git a/src/main/java/org/kohsuke/github/GHIssue.java b/src/main/java/org/kohsuke/github/GHIssue.java index 4e654c7474..e2d5e39bdb 100644 --- a/src/main/java/org/kohsuke/github/GHIssue.java +++ b/src/main/java/org/kohsuke/github/GHIssue.java @@ -24,12 +24,14 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.commons.lang3.StringUtils; import org.kohsuke.github.internal.EnumUtils; import java.io.IOException; import java.net.URL; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -54,15 +56,63 @@ public class GHIssue extends GHObject implements Reactable { /** - * Create default GHIssue instance + * The type PullRequest. */ - public GHIssue() { + @SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD" }, justification = "JSON API") + public static class PullRequest { + + private String diffUrl, patchUrl, htmlUrl; + + /** + * Create default PullRequest instance + */ + public PullRequest() { + } + + /** + * Gets diff url. + * + * @return the diff url + */ + public URL getDiffUrl() { + return GitHubClient.parseURL(diffUrl); + } + + /** + * Gets patch url. + * + * @return the patch url + */ + public URL getPatchUrl() { + return GitHubClient.parseURL(patchUrl); + } + + /** + * Gets url. + * + * @return the url + */ + public URL getUrl() { + return GitHubClient.parseURL(htmlUrl); + } } private static final String ASSIGNEES = "assignees"; - /** The owner. */ - GHRepository owner; + /** + * Gets the logins. + * + * @param users + * the users + * @return the logins + */ + protected static List getLogins(Collection users) { + List names = new ArrayList(users.size()); + for (GHUser a : users) { + names.add(a.getLogin()); + } + return names; + } /** The assignee. */ // API v3 @@ -71,191 +121,163 @@ public GHIssue() { /** The assignees. */ protected GHUser[] assignees; - /** The state. */ - protected String state; - - /** The state reason. */ - protected String state_reason; - - /** The number. */ - protected int number; + /** The body. */ + @SkipFromToString + protected String body; /** The closed at. */ - protected String closed_at; + protected String closedAt; + + /** The closed by. */ + protected GHUser closedBy; /** The comments. */ protected int comments; - /** The body. */ - @SkipFromToString - protected String body; - /** The labels. */ protected List labels; - /** The user. */ - protected GHUser user; + /** The locked. */ + protected boolean locked; - /** The html url. */ - protected String title, html_url; + /** The milestone. */ + protected GHMilestone milestone; + + /** The number. */ + protected int number; /** The pull request. */ - protected GHIssue.PullRequest pull_request; + protected GHIssue.PullRequest pullRequest; - /** The milestone. */ - protected GHMilestone milestone; + /** The state. */ + protected String state; - /** The closed by. */ - protected GHUser closed_by; + /** The state reason. */ + protected String stateReason; - /** The locked. */ - protected boolean locked; + /** The html url. */ + protected String title, htmlUrl; - /** - * Wrap. - * - * @param owner - * the owner - * @return the GH issue - */ - GHIssue wrap(GHRepository owner) { - this.owner = owner; - if (milestone != null) - milestone.lateBind(owner); - return this; - } + /** The user. */ + protected GHUser user; - private String getRepositoryUrlPath() { - String url = getUrl().toString(); - int index = url.indexOf("/issues"); - if (index == -1) { - index = url.indexOf("/pulls"); - } - return url.substring(0, index); - } + /** The owner. */ + GHRepository owner; /** - * Repository to which the issue belongs. - * - * @return the repository + * Create default GHIssue instance */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHRepository getRepository() { - try { - synchronized (this) { - if (owner == null) { - String repositoryUrlPath = getRepositoryUrlPath(); - wrap(root().createRequest().withUrlPath(repositoryUrlPath).fetch(GHRepository.class)); - } - } - } catch (IOException e) { - throw new GHException("Failed to fetch repository", e); - } - return owner; + public GHIssue() { } /** - * The description of this pull request. + * Add assignees. * - * @return the body + * @param assignees + * the assignees + * @throws IOException + * the io exception */ - public String getBody() { - return body; + public void addAssignees(Collection assignees) throws IOException { + root().createRequest() + .method("POST") + .with(ASSIGNEES, getLogins(assignees)) + .withUrlPath(getIssuesApiRoute() + "/assignees") + .fetchInto(this); } /** - * ID. + * Add assignees. * - * @return the number + * @param assignees + * the assignees + * @throws IOException + * the io exception */ - public int getNumber() { - return number; + public void addAssignees(GHUser... assignees) throws IOException { + addAssignees(Arrays.asList(assignees)); } /** - * The HTML page of this issue, like https://github.com/jenkinsci/jenkins/issues/100 + * Add labels. * - * @return the html url - */ - public URL getHtmlUrl() { - return GitHubClient.parseURL(html_url); - } - - /** - * Gets title. + * Labels that are already present on the target are ignored. * - * @return the title + * @param labels + * the labels + * @return the complete list of labels including the new additions + * @throws IOException + * the io exception */ - public String getTitle() { - return title; + public List addLabels(Collection labels) throws IOException { + return _addLabels(GHLabel.toNames(labels)); } /** - * Is locked boolean. + * Add labels. * - * @return the boolean - */ - public boolean isLocked() { - return locked; - } - - /** - * Gets state. + * Labels that are already present on the target are ignored. * - * @return the state + * @param labels + * the labels + * @return the complete list of labels including the new additions + * @throws IOException + * the io exception */ - public GHIssueState getState() { - return Enum.valueOf(GHIssueState.class, state.toUpperCase(Locale.ENGLISH)); + public List addLabels(GHLabel... labels) throws IOException { + return addLabels(Arrays.asList(labels)); } /** - * Gets state reason. + * Adds labels to the issue. * - * @return the state reason - */ - public GHIssueStateReason getStateReason() { - return EnumUtils.getNullableEnumOrDefault(GHIssueStateReason.class, state_reason, GHIssueStateReason.UNKNOWN); - } - - /** - * Gets labels. + * Labels that are already present on the target are ignored. * - * @return the labels + * @param names + * Names of the label + * @return the complete list of labels including the new additions + * @throws IOException + * the io exception */ - public Collection getLabels() { - if (labels == null) { - return Collections.emptyList(); - } - return Collections.unmodifiableList(labels); + public List addLabels(String... names) throws IOException { + return _addLabels(Arrays.asList(names)); } /** - * Gets closed at. + * Assign to. * - * @return the closed at + * @param user + * the user + * @throws IOException + * the io exception */ - public Date getClosedAt() { - return GitHubClient.parseDate(closed_at); + public void assignTo(GHUser user) throws IOException { + setAssignees(user); } /** - * Lock. + * Closes this issue. * * @throws IOException * the io exception */ - public void lock() throws IOException { - root().createRequest().method("PUT").withUrlPath(getApiRoute() + "/lock").send(); + public void close() throws IOException { + edit("state", "closed"); } /** - * Unlock. + * Closes this issue. * + * @param reason + * the reason the issue was closed * @throws IOException * the io exception */ - public void unlock() throws IOException { - root().createRequest().method("DELETE").withUrlPath(getApiRoute() + "/lock").send(); + public void close(GHIssueStateReason reason) throws IOException { + Map map = new HashMap<>(); + map.put("state", "closed"); + map.put("state_reason", reason.name().toLowerCase(Locale.ENGLISH)); + edit(map); } /** @@ -276,263 +298,238 @@ public GHIssueComment comment(String message) throws IOException { return r.wrapUp(this); } - private void edit(String key, Object value) throws IOException { - root().createRequest().with(key, value).method("PATCH").withUrlPath(getApiRoute()).send(); - } - - private void edit(Map map) throws IOException { - root().createRequest().with(map).method("PATCH").withUrlPath(getApiRoute()).send(); - } - /** - * Identical to edit(), but allows null for the value. - */ - private void editNullable(String key, Object value) throws IOException { - root().createRequest().withNullable(key, value).method("PATCH").withUrlPath(getApiRoute()).send(); - } - - private void editIssue(String key, Object value) throws IOException { - root().createRequest().withNullable(key, value).method("PATCH").withUrlPath(getIssuesApiRoute()).send(); + * Creates the reaction. + * + * @param content + * the content + * @return the GH reaction + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public GHReaction createReaction(ReactionContent content) throws IOException { + return root().createRequest() + .method("POST") + .with("content", content.getContent()) + .withUrlPath(getIssuesApiRoute() + "/reactions") + .fetch(GHReaction.class); } /** - * Closes this issue. + * Delete reaction. * + * @param reaction + * the reaction * @throws IOException - * the io exception + * Signals that an I/O exception has occurred. */ - public void close() throws IOException { - edit("state", "closed"); + public void deleteReaction(GHReaction reaction) throws IOException { + owner.root() + .createRequest() + .method("DELETE") + .withUrlPath(getIssuesApiRoute(), "reactions", String.valueOf(reaction.getId())) + .send(); } /** - * Closes this issue. + * Gets assignee. * - * @param reason - * the reason the issue was closed - * @throws IOException - * the io exception + * @return the assignee */ - public void close(GHIssueStateReason reason) throws IOException { - Map map = new HashMap<>(); - map.put("state", "closed"); - map.put("state_reason", reason.name().toLowerCase(Locale.ENGLISH)); - edit(map); + public GHUser getAssignee() { + return root().intern(assignee); } /** - * Reopens this issue. + * Gets assignees. * - * @throws IOException - * the io exception + * @return the assignees */ - public void reopen() throws IOException { - edit("state", "open"); + public List getAssignees() { + return Collections.unmodifiableList(Arrays.asList(assignees)); } /** - * Sets title. + * The description of this pull request. * - * @param title - * the title - * @throws IOException - * the io exception + * @return the body */ - public void setTitle(String title) throws IOException { - edit("title", title); + public String getBody() { + return body; } /** - * Sets body. + * Gets closed at. * - * @param body - * the body - * @throws IOException - * the io exception + * @return the closed at */ - public void setBody(String body) throws IOException { - edit("body", body); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getClosedAt() { + return GitHubClient.parseInstant(closedAt); } /** - * Sets the milestone for this issue. + * Reports who has closed the issue. * - * @param milestone - * The milestone to assign this issue to. Use null to remove the milestone for this issue. - * @throws IOException - * The io exception + *

+ * Note that GitHub doesn't always seem to report this information even for an issue that's already closed. See + * https://github.com/kohsuke/github-api/issues/60. + * + * @return the closed by */ - public void setMilestone(GHMilestone milestone) throws IOException { - if (milestone == null) { - editIssue("milestone", null); - } else { - editIssue("milestone", milestone.getNumber()); - } + public GHUser getClosedBy() { + if (!"closed".equals(state)) + return null; + + // TODO + /* + * if (closed_by==null) { closed_by = owner.getIssue(number).getClosed_by(); } + */ + return root().intern(closedBy); } /** - * Assign to. + * Obtains all the comments associated with this issue. * - * @param user - * the user + * @return the comments * @throws IOException * the io exception + * @see #listComments() #listComments() */ - public void assignTo(GHUser user) throws IOException { - setAssignees(user); + public List getComments() throws IOException { + return listComments().toList(); } /** - * Sets labels on the target to a specific list. + * Gets comments count. * - * @param labels - * the labels - * @throws IOException - * the io exception + * @return the comments count */ - public void setLabels(String... labels) throws IOException { - editIssue("labels", labels); + public int getCommentsCount() { + return comments; } /** - * Adds labels to the issue. - * - * Labels that are already present on the target are ignored. + * The HTML page of this issue, like https://github.com/jenkinsci/jenkins/issues/100 * - * @param names - * Names of the label - * @return the complete list of labels including the new additions - * @throws IOException - * the io exception + * @return the html url */ - public List addLabels(String... names) throws IOException { - return _addLabels(Arrays.asList(names)); + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); } /** - * Add labels. - * - * Labels that are already present on the target are ignored. + * Gets labels. * - * @param labels - * the labels - * @return the complete list of labels including the new additions - * @throws IOException - * the io exception + * @return the labels */ - public List addLabels(GHLabel... labels) throws IOException { - return addLabels(Arrays.asList(labels)); + public Collection getLabels() { + if (labels == null) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(labels); } /** - * Add labels. - * - * Labels that are already present on the target are ignored. + * Gets milestone. * - * @param labels - * the labels - * @return the complete list of labels including the new additions - * @throws IOException - * the io exception + * @return the milestone */ - public List addLabels(Collection labels) throws IOException { - return _addLabels(GHLabel.toNames(labels)); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHMilestone getMilestone() { + return milestone; } - private List _addLabels(Collection names) throws IOException { - return Arrays.asList(root().createRequest() - .with("labels", names) - .method("POST") - .withUrlPath(getIssuesApiRoute() + "/labels") - .fetch(GHLabel[].class)); + /** + * ID. + * + * @return the number + */ + public int getNumber() { + return number; } /** - * Remove a single label. - * - * Attempting to remove a label that is not present throws {@link GHFileNotFoundException}. + * Returns non-null if this issue is a shadow of a pull request. * - * @param name - * the name - * @return the remaining list of labels - * @throws IOException - * the io exception, throws {@link GHFileNotFoundException} if label was not present. + * @return the pull request */ - public List removeLabel(String name) throws IOException { - return Arrays.asList(root().createRequest() - .method("DELETE") - .withUrlPath(getIssuesApiRoute() + "/labels", name) - .fetch(GHLabel[].class)); + public PullRequest getPullRequest() { + return pullRequest; } /** - * Remove a collection of labels. - * - * Attempting to remove labels that are not present on the target are ignored. + * Repository to which the issue belongs. * - * @param names - * the names - * @return the remaining list of labels - * @throws IOException - * the io exception + * @return the repository */ - public List removeLabels(String... names) throws IOException { - return _removeLabels(Arrays.asList(names)); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHRepository getRepository() { + try { + synchronized (this) { + if (owner == null) { + String repositoryUrlPath = getRepositoryUrlPath(); + wrap(root().createRequest().withUrlPath(repositoryUrlPath).fetch(GHRepository.class)); + } + } + } catch (IOException e) { + throw new GHException("Failed to fetch repository", e); + } + return owner; } /** - * Remove a collection of labels. + * Gets state. * - * Attempting to remove labels that are not present on the target are ignored. + * @return the state + */ + public GHIssueState getState() { + return Enum.valueOf(GHIssueState.class, state.toUpperCase(Locale.ENGLISH)); + } + + /** + * Gets state reason. * - * @param labels - * the labels - * @return the remaining list of labels - * @throws IOException - * the io exception - * @see #removeLabels(String...) #removeLabels(String...) + * @return the state reason */ - public List removeLabels(GHLabel... labels) throws IOException { - return removeLabels(Arrays.asList(labels)); + public GHIssueStateReason getStateReason() { + return EnumUtils.getNullableEnumOrDefault(GHIssueStateReason.class, stateReason, GHIssueStateReason.UNKNOWN); } /** - * Remove a collection of labels. + * Gets title. * - * Attempting to remove labels that are not present on the target are ignored. + * @return the title + */ + public String getTitle() { + return title; + } + + /** + * User who submitted the issue. * - * @param labels - * the labels - * @return the remaining list of labels - * @throws IOException - * the io exception + * @return the user */ - public List removeLabels(Collection labels) throws IOException { - return _removeLabels(GHLabel.toNames(labels)); + public GHUser getUser() { + return root().intern(user); } - private List _removeLabels(Collection names) throws IOException { - List remainingLabels = Collections.emptyList(); - for (String name : names) { - try { - remainingLabels = removeLabel(name); - } catch (GHFileNotFoundException e) { - // when trying to remove multiple labels, we ignore already removed - } - } - return remainingLabels; + /** + * Is locked boolean. + * + * @return the boolean + */ + public boolean isLocked() { + return locked; } /** - * Obtains all the comments associated with this issue. + * Is pull request boolean. * - * @return the comments - * @throws IOException - * the io exception - * @see #listComments() #listComments() + * @return the boolean */ - public List getComments() throws IOException { - return listComments().toList(); + public boolean isPullRequest() { + return pullRequest != null; } /** @@ -548,6 +545,38 @@ public PagedIterable listComments() { .toIterable(GHIssueComment[].class, item -> item.wrapUp(this)); } + /** + * Lists events for this issue. See https://developer.github.com/v3/issues/events/ + * + * @return the paged iterable + */ + public PagedIterable listEvents() { + return root().createRequest() + .withUrlPath(getRepository().getApiTailUrl(String.format("/issues/%s/events", number))) + .toIterable(GHIssueEvent[].class, item -> item.wrapUp(this)); + } + + /** + * List reactions. + * + * @return the paged iterable + */ + public PagedIterable listReactions() { + return root().createRequest() + .withUrlPath(getIssuesApiRoute() + "/reactions") + .toIterable(GHReaction[].class, null); + } + + /** + * Lock. + * + * @throws IOException + * the io exception + */ + public void lock() throws IOException { + root().createRequest().method("PUT").withUrlPath(getApiRoute() + "/lock").send(); + } + /** * Search comments on this issue by specifying filters through a builder pattern. * @@ -559,87 +588,106 @@ public GHIssueCommentQueryBuilder queryComments() { } /** - * Creates the reaction. + * Remove assignees. * - * @param content - * the content - * @return the GH reaction + * @param assignees + * the assignees * @throws IOException - * Signals that an I/O exception has occurred. + * the io exception */ - public GHReaction createReaction(ReactionContent content) throws IOException { - return root().createRequest() - .method("POST") - .with("content", content.getContent()) - .withUrlPath(getIssuesApiRoute() + "/reactions") - .fetch(GHReaction.class); + public void removeAssignees(Collection assignees) throws IOException { + root().createRequest() + .method("DELETE") + .with(ASSIGNEES, getLogins(assignees)) + .inBody() + .withUrlPath(getIssuesApiRoute() + "/assignees") + .fetchInto(this); } /** - * Delete reaction. + * Remove assignees. * - * @param reaction - * the reaction + * @param assignees + * the assignees * @throws IOException - * Signals that an I/O exception has occurred. + * the io exception */ - public void deleteReaction(GHReaction reaction) throws IOException { - owner.root() - .createRequest() + public void removeAssignees(GHUser... assignees) throws IOException { + removeAssignees(Arrays.asList(assignees)); + } + + /** + * Remove a single label. + * + * Attempting to remove a label that is not present throws {@link GHFileNotFoundException}. + * + * @param name + * the name + * @return the remaining list of labels + * @throws IOException + * the io exception, throws {@link GHFileNotFoundException} if label was not present. + */ + public List removeLabel(String name) throws IOException { + return Arrays.asList(root().createRequest() .method("DELETE") - .withUrlPath(getIssuesApiRoute(), "reactions", String.valueOf(reaction.getId())) - .send(); + .withUrlPath(getIssuesApiRoute() + "/labels", name) + .fetch(GHLabel[].class)); } /** - * List reactions. + * Remove a collection of labels. * - * @return the paged iterable + * Attempting to remove labels that are not present on the target are ignored. + * + * @param labels + * the labels + * @return the remaining list of labels + * @throws IOException + * the io exception */ - public PagedIterable listReactions() { - return root().createRequest() - .withUrlPath(getIssuesApiRoute() + "/reactions") - .toIterable(GHReaction[].class, null); + public List removeLabels(Collection labels) throws IOException { + return _removeLabels(GHLabel.toNames(labels)); } /** - * Add assignees. + * Remove a collection of labels. * - * @param assignees - * the assignees + * Attempting to remove labels that are not present on the target are ignored. + * + * @param labels + * the labels + * @return the remaining list of labels * @throws IOException * the io exception + * @see #removeLabels(String...) #removeLabels(String...) */ - public void addAssignees(GHUser... assignees) throws IOException { - addAssignees(Arrays.asList(assignees)); + public List removeLabels(GHLabel... labels) throws IOException { + return removeLabels(Arrays.asList(labels)); } /** - * Add assignees. + * Remove a collection of labels. * - * @param assignees - * the assignees + * Attempting to remove labels that are not present on the target are ignored. + * + * @param names + * the names + * @return the remaining list of labels * @throws IOException * the io exception */ - public void addAssignees(Collection assignees) throws IOException { - root().createRequest() - .method("POST") - .with(ASSIGNEES, getLogins(assignees)) - .withUrlPath(getIssuesApiRoute() + "/assignees") - .fetchInto(this); + public List removeLabels(String... names) throws IOException { + return _removeLabels(Arrays.asList(names)); } /** - * Sets assignees. + * Reopens this issue. * - * @param assignees - * the assignees * @throws IOException * the io exception */ - public void setAssignees(GHUser... assignees) throws IOException { - setAssignees(Arrays.asList(assignees)); + public void reopen() throws IOException { + edit("state", "open"); } /** @@ -659,207 +707,162 @@ public void setAssignees(Collection assignees) throws IOException { } /** - * Remove assignees. + * Sets assignees. * * @param assignees * the assignees * @throws IOException * the io exception */ - public void removeAssignees(GHUser... assignees) throws IOException { - removeAssignees(Arrays.asList(assignees)); + public void setAssignees(GHUser... assignees) throws IOException { + setAssignees(Arrays.asList(assignees)); } /** - * Remove assignees. + * Sets body. * - * @param assignees - * the assignees + * @param body + * the body * @throws IOException * the io exception */ - public void removeAssignees(Collection assignees) throws IOException { - root().createRequest() - .method("DELETE") - .with(ASSIGNEES, getLogins(assignees)) - .inBody() - .withUrlPath(getIssuesApiRoute() + "/assignees") - .fetchInto(this); + public void setBody(String body) throws IOException { + edit("body", body); } /** - * Gets api route. + * Sets labels on the target to a specific list. * - * @return the api route + * @param labels + * the labels + * @throws IOException + * the io exception */ - protected String getApiRoute() { - return getIssuesApiRoute(); + public void setLabels(String... labels) throws IOException { + editIssue("labels", labels); } /** - * Gets issues api route. + * Sets the milestone for this issue. * - * @return the issues api route + * @param milestone + * The milestone to assign this issue to. Use null to remove the milestone for this issue. + * @throws IOException + * The io exception */ - protected String getIssuesApiRoute() { - if (owner == null) { - // Issues returned from search to do not have an owner. Attempt to use url. - final URL url = Objects.requireNonNull(getUrl(), "Missing instance URL!"); - return StringUtils.prependIfMissing(url.toString().replace(root().getApiUrl(), ""), "/"); + public void setMilestone(GHMilestone milestone) throws IOException { + if (milestone == null) { + editIssue("milestone", null); + } else { + editIssue("milestone", milestone.getNumber()); } - GHRepository repo = getRepository(); - return "/repos/" + repo.getOwnerName() + "/" + repo.getName() + "/issues/" + number; } /** - * Gets assignee. + * Sets title. * - * @return the assignee + * @param title + * the title + * @throws IOException + * the io exception */ - public GHUser getAssignee() { - return root().intern(assignee); + public void setTitle(String title) throws IOException { + edit("title", title); } /** - * Gets assignees. + * Unlock. * - * @return the assignees + * @throws IOException + * the io exception */ - public List getAssignees() { - return Collections.unmodifiableList(Arrays.asList(assignees)); + public void unlock() throws IOException { + root().createRequest().method("DELETE").withUrlPath(getApiRoute() + "/lock").send(); } - /** - * User who submitted the issue. - * - * @return the user - */ - public GHUser getUser() { - return root().intern(user); + private List _addLabels(Collection names) throws IOException { + return Arrays.asList(root().createRequest() + .with("labels", names) + .method("POST") + .withUrlPath(getIssuesApiRoute() + "/labels") + .fetch(GHLabel[].class)); } - /** - * Reports who has closed the issue. - * - *

- * Note that GitHub doesn't always seem to report this information even for an issue that's already closed. See - * https://github.com/kohsuke/github-api/issues/60. - * - * @return the closed by - */ - public GHUser getClosedBy() { - if (!"closed".equals(state)) - return null; + private List _removeLabels(Collection names) throws IOException { + List remainingLabels = Collections.emptyList(); + for (String name : names) { + try { + remainingLabels = removeLabel(name); + } catch (GHFileNotFoundException e) { + // when trying to remove multiple labels, we ignore already removed + } + } + return remainingLabels; + } - // TODO - /* - * if (closed_by==null) { closed_by = owner.getIssue(number).getClosed_by(); } - */ - return root().intern(closed_by); + private void edit(Map map) throws IOException { + root().createRequest().with(map).method("PATCH").withUrlPath(getApiRoute()).send(); } - /** - * Gets comments count. - * - * @return the comments count - */ - public int getCommentsCount() { - return comments; + private void edit(String key, Object value) throws IOException { + root().createRequest().with(key, value).method("PATCH").withUrlPath(getApiRoute()).send(); } - /** - * Returns non-null if this issue is a shadow of a pull request. - * - * @return the pull request - */ - public PullRequest getPullRequest() { - return pull_request; + private void editIssue(String key, Object value) throws IOException { + root().createRequest().withNullable(key, value).method("PATCH").withUrlPath(getIssuesApiRoute()).send(); } /** - * Is pull request boolean. - * - * @return the boolean + * Identical to edit(), but allows null for the value. */ - public boolean isPullRequest() { - return pull_request != null; + private void editNullable(String key, Object value) throws IOException { + root().createRequest().withNullable(key, value).method("PATCH").withUrlPath(getApiRoute()).send(); } - /** - * Gets milestone. - * - * @return the milestone - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHMilestone getMilestone() { - return milestone; + private String getRepositoryUrlPath() { + String url = getUrl().toString(); + int index = url.indexOf("/issues"); + if (index == -1) { + index = url.indexOf("/pulls"); + } + return url.substring(0, index); } /** - * The type PullRequest. + * Gets api route. + * + * @return the api route */ - @SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD" }, justification = "JSON API") - public static class PullRequest { - - /** - * Create default PullRequest instance - */ - public PullRequest() { - } - - private String diff_url, patch_url, html_url; - - /** - * Gets diff url. - * - * @return the diff url - */ - public URL getDiffUrl() { - return GitHubClient.parseURL(diff_url); - } - - /** - * Gets patch url. - * - * @return the patch url - */ - public URL getPatchUrl() { - return GitHubClient.parseURL(patch_url); - } - - /** - * Gets url. - * - * @return the url - */ - public URL getUrl() { - return GitHubClient.parseURL(html_url); - } + protected String getApiRoute() { + return getIssuesApiRoute(); } /** - * Gets the logins. + * Gets issues api route. * - * @param users - * the users - * @return the logins + * @return the issues api route */ - protected static List getLogins(Collection users) { - List names = new ArrayList(users.size()); - for (GHUser a : users) { - names.add(a.getLogin()); + protected String getIssuesApiRoute() { + if (owner == null) { + // Issues returned from search to do not have an owner. Attempt to use url. + final URL url = Objects.requireNonNull(getUrl(), "Missing instance URL!"); + return StringUtils.prependIfMissing(url.toString().replace(root().getApiUrl(), ""), "/"); } - return names; + GHRepository repo = getRepository(); + return "/repos/" + repo.getOwnerName() + "/" + repo.getName() + "/issues/" + number; } /** - * Lists events for this issue. See https://developer.github.com/v3/issues/events/ + * Wrap. * - * @return the paged iterable + * @param owner + * the owner + * @return the GH issue */ - public PagedIterable listEvents() { - return root().createRequest() - .withUrlPath(getRepository().getApiTailUrl(String.format("/issues/%s/events", number))) - .toIterable(GHIssueEvent[].class, item -> item.wrapUp(this)); + GHIssue wrap(GHRepository owner) { + this.owner = owner; + if (milestone != null) + milestone.lateBind(owner); + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHIssueBuilder.java b/src/main/java/org/kohsuke/github/GHIssueBuilder.java index 0d904c4fb2..451891f99a 100644 --- a/src/main/java/org/kohsuke/github/GHIssueBuilder.java +++ b/src/main/java/org/kohsuke/github/GHIssueBuilder.java @@ -11,10 +11,10 @@ * @author Kohsuke Kawaguchi */ public class GHIssueBuilder { - private final GHRepository repo; + private List assignees = new ArrayList(); private final Requester builder; private List labels = new ArrayList(); - private List assignees = new ArrayList(); + private final GHRepository repo; /** * Instantiates a new GH issue builder. @@ -30,18 +30,6 @@ public class GHIssueBuilder { builder.with("title", title); } - /** - * Sets the main text of an issue, which is arbitrary multi-line text. - * - * @param str - * the str - * @return the gh issue builder - */ - public GHIssueBuilder body(String str) { - builder.with("body", str); - return this; - } - /** * Assignee gh issue builder. * @@ -69,18 +57,32 @@ public GHIssueBuilder assignee(String user) { } /** - * Milestone gh issue builder. + * Sets the main text of an issue, which is arbitrary multi-line text. * - * @param milestone - * the milestone + * @param str + * the str * @return the gh issue builder */ - public GHIssueBuilder milestone(GHMilestone milestone) { - if (milestone != null) - builder.with("milestone", milestone.getNumber()); + public GHIssueBuilder body(String str) { + builder.with("body", str); return this; } + /** + * Creates a new issue. + * + * @return the gh issue + * @throws IOException + * the io exception + */ + public GHIssue create() throws IOException { + return builder.with("labels", labels) + .with("assignees", assignees) + .withUrlPath(repo.getApiTailUrl("issues")) + .fetch(GHIssue.class) + .wrap(repo); + } + /** * Label gh issue builder. * @@ -95,17 +97,15 @@ public GHIssueBuilder label(String label) { } /** - * Creates a new issue. + * Milestone gh issue builder. * - * @return the gh issue - * @throws IOException - * the io exception + * @param milestone + * the milestone + * @return the gh issue builder */ - public GHIssue create() throws IOException { - return builder.with("labels", labels) - .with("assignees", assignees) - .withUrlPath(repo.getApiTailUrl("issues")) - .fetch(GHIssue.class) - .wrap(repo); + public GHIssueBuilder milestone(GHMilestone milestone) { + if (milestone != null) + builder.with("milestone", milestone.getNumber()); + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHIssueChanges.java b/src/main/java/org/kohsuke/github/GHIssueChanges.java index d47b8cdcc0..200ff93ee8 100644 --- a/src/main/java/org/kohsuke/github/GHIssueChanges.java +++ b/src/main/java/org/kohsuke/github/GHIssueChanges.java @@ -12,21 +12,35 @@ public class GHIssueChanges { /** - * Create default GHIssueChanges instance + * Wrapper for changed values. */ - public GHIssueChanges() { + public static class GHFrom { + + private String from; + + /** + * Create default GHFrom instance + */ + public GHFrom() { + } + + /** + * Previous value that was changed. + * + * @return previous value + */ + public String getFrom() { + return from; + } } - private GHFrom title; private GHFrom body; + private GHFrom title; /** - * Old issue title. - * - * @return old issue title (or null if not changed) + * Create default GHIssueChanges instance */ - public GHFrom getTitle() { - return title; + public GHIssueChanges() { } /** @@ -39,25 +53,11 @@ public GHFrom getBody() { } /** - * Wrapper for changed values. + * Old issue title. + * + * @return old issue title (or null if not changed) */ - public static class GHFrom { - - /** - * Create default GHFrom instance - */ - public GHFrom() { - } - - private String from; - - /** - * Previous value that was changed. - * - * @return previous value - */ - public String getFrom() { - return from; - } + public GHFrom getTitle() { + return title; } } diff --git a/src/main/java/org/kohsuke/github/GHIssueComment.java b/src/main/java/org/kohsuke/github/GHIssueComment.java index 2c6d8f9561..39bfa13cba 100644 --- a/src/main/java/org/kohsuke/github/GHIssueComment.java +++ b/src/main/java/org/kohsuke/github/GHIssueComment.java @@ -24,6 +24,7 @@ package org.kohsuke.github; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.kohsuke.github.internal.EnumUtils; import java.io.IOException; import java.net.URL; @@ -39,103 +40,46 @@ public class GHIssueComment extends GHObject implements Reactable { /** - * Create default GHIssueComment instance + * Legacy field for gravatar ID (no longer returned by GitHub API). */ - public GHIssueComment() { - } - - /** The owner. */ - GHIssue owner; - - private String body, gravatar_id, html_url, author_association; - private GHUser user; // not fully populated. beware. + private String gravatarId; /** - * Wrap up. - * - * @param owner - * the owner - * @return the GH issue comment + * The author's association with the repository. */ - GHIssueComment wrapUp(GHIssue owner) { - this.owner = owner; - return this; - } + protected String authorAssociation; /** - * Gets the issue to which this comment is associated. - * - * @return the parent + * The comment body. */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHIssue getParent() { - return owner; - } + protected String body; /** - * The comment itself. - * - * @return the body + * The comment body in HTML format. */ - public String getBody() { - return body; - } + protected String bodyHtml; /** - * Gets the user who posted this comment. - * - * @return the user - * @throws IOException - * the io exception - */ - public GHUser getUser() throws IOException { - return owner == null || owner.isOffline() ? user : owner.root().getUser(user.getLogin()); - } - - /** - * Gets the html url. - * - * @return the html url + * The comment body in plain text format. */ - public URL getHtmlUrl() { - return GitHubClient.parseURL(html_url); - } + protected String bodyText; /** - * Gets author association. - * - * @return the author association + * The HTML URL of the comment. */ - public GHCommentAuthorAssociation getAuthorAssociation() { - return GHCommentAuthorAssociation.valueOf(author_association); - } + protected String htmlUrl; /** - * Updates the body of the issue comment. - * - * @param body - * the body - * @throws IOException - * the io exception + * The user who created the comment. Note: not fully populated, use getUser() for full details. */ - public void update(String body) throws IOException { - owner.root() - .createRequest() - .method("PATCH") - .with("body", body) - .withUrlPath(getApiRoute()) - .fetch(GHIssueComment.class); - this.body = body; - } + protected GHUser user; + /** The owner. */ + GHIssue owner; /** - * Deletes this issue comment. - * - * @throws IOException - * the io exception + * Create default GHIssueComment instance */ - public void delete() throws IOException { - owner.root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); + public GHIssueComment() { } /** @@ -156,6 +100,16 @@ public GHReaction createReaction(ReactionContent content) throws IOException { .fetch(GHReaction.class); } + /** + * Deletes this issue comment. + * + * @throws IOException + * the io exception + */ + public void delete() throws IOException { + owner.root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); + } + /** * Delete reaction. * @@ -172,6 +126,74 @@ public void deleteReaction(GHReaction reaction) throws IOException { .send(); } + /** + * Gets author association. + * + * @return the author association + */ + public GHCommentAuthorAssociation getAuthorAssociation() { + return EnumUtils.getEnumOrDefault(GHCommentAuthorAssociation.class, + authorAssociation, + GHCommentAuthorAssociation.UNKNOWN); + } + + /** + * The comment itself. + * + * @return the body + */ + public String getBody() { + return body; + } + + /** + * Gets the body in HTML format. + * + * @return the body in HTML format + */ + public String getBodyHtml() { + return bodyHtml; + } + + /** + * Gets the body in plain text format. + * + * @return the body in plain text format + */ + public String getBodyText() { + return bodyText; + } + + /** + * Gets the html url. + * + * @return the html url + */ + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); + } + + /** + * Gets the issue to which this comment is associated. + * + * @return the parent + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHIssue getParent() { + return owner; + } + + /** + * Gets the user who posted this comment. + * + * @return the user + * @throws IOException + * the io exception + */ + public GHUser getUser() throws IOException { + return owner == null || owner.isOffline() ? user : owner.root().getUser(user.getLogin()); + } + /** * List reactions. * @@ -184,8 +206,38 @@ public PagedIterable listReactions() { .toIterable(GHReaction[].class, item -> owner.root()); } + /** + * Updates the body of the issue comment. + * + * @param body + * the body + * @throws IOException + * the io exception + */ + public void update(String body) throws IOException { + owner.root() + .createRequest() + .method("PATCH") + .with("body", body) + .withUrlPath(getApiRoute()) + .fetch(GHIssueComment.class); + this.body = body; + } + private String getApiRoute() { return "/repos/" + owner.getRepository().getOwnerName() + "/" + owner.getRepository().getName() + "/issues/comments/" + getId(); } + + /** + * Wrap up. + * + * @param owner + * the owner + * @return the GH issue comment + */ + GHIssueComment wrapUp(GHIssue owner) { + this.owner = owner; + return this; + } } diff --git a/src/main/java/org/kohsuke/github/GHIssueCommentQueryBuilder.java b/src/main/java/org/kohsuke/github/GHIssueCommentQueryBuilder.java index d39df0d475..920644d20b 100644 --- a/src/main/java/org/kohsuke/github/GHIssueCommentQueryBuilder.java +++ b/src/main/java/org/kohsuke/github/GHIssueCommentQueryBuilder.java @@ -1,5 +1,6 @@ package org.kohsuke.github; +import java.time.Instant; import java.util.Date; // TODO: Auto-generated Javadoc @@ -20,8 +21,8 @@ * @see List issue comments */ public class GHIssueCommentQueryBuilder { - private final Requester req; private final GHIssue issue; + private final Requester req; /** * Instantiates a new GH issue comment query builder. @@ -34,35 +35,48 @@ public class GHIssueCommentQueryBuilder { this.req = issue.root().createRequest().withUrlPath(issue.getIssuesApiRoute() + "/comments"); } + /** + * Lists up the comments with the criteria added so far. + * + * @return the paged iterable + */ + public PagedIterable list() { + return req.toIterable(GHIssueComment[].class, item -> item.wrapUp(issue)); + } + /** * Only comments created/updated after this date will be returned. * * @param date * the date * @return the query builder + * @deprecated Use {@link #since(Instant)} */ + @Deprecated public GHIssueCommentQueryBuilder since(Date date) { - req.with("since", GitHubClient.printDate(date)); - return this; + return since(GitHubClient.toInstantOrNull(date)); } /** - * Only comments created/updated after this timestamp will be returned. + * Only comments created/updated after this date will be returned. * - * @param timestamp - * the timestamp + * @param date + * the date * @return the query builder */ - public GHIssueCommentQueryBuilder since(long timestamp) { - return since(new Date(timestamp)); + public GHIssueCommentQueryBuilder since(Instant date) { + req.with("since", GitHubClient.printInstant(date)); + return this; } /** - * Lists up the comments with the criteria added so far. + * Only comments created/updated after this timestamp will be returned. * - * @return the paged iterable + * @param timestamp + * the timestamp + * @return the query builder */ - public PagedIterable list() { - return req.toIterable(GHIssueComment[].class, item -> item.wrapUp(issue)); + public GHIssueCommentQueryBuilder since(long timestamp) { + return since(Instant.ofEpochMilli(timestamp)); } } diff --git a/src/main/java/org/kohsuke/github/GHIssueEvent.java b/src/main/java/org/kohsuke/github/GHIssueEvent.java index 726a590989..aff93f135e 100644 --- a/src/main/java/org/kohsuke/github/GHIssueEvent.java +++ b/src/main/java/org/kohsuke/github/GHIssueEvent.java @@ -1,7 +1,9 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.time.Instant; import java.util.Date; // TODO: Auto-generated Javadoc @@ -13,54 +15,27 @@ */ public class GHIssueEvent extends GitHubInteractiveObject { - /** - * Create default GHIssueEvent instance - */ - public GHIssueEvent() { - } - - private long id; - private String node_id; - private String url; private GHUser actor; + + private GHUser assignee; + private String commitId; + private String commitUrl; + private String createdAt; private String event; - private String commit_id; - private String commit_url; - private String created_at; - private GHMilestone milestone; + private long id; + private GHIssue issue; private GHLabel label; - private GHUser assignee; + private GHMilestone milestone; + private String nodeId; private GHIssueRename rename; - private GHUser reviewRequester; private GHUser requestedReviewer; - - private GHIssue issue; - - /** - * Gets id. - * - * @return the id - */ - public long getId() { - return id; - } - - /** - * Gets node id. - * - * @return the node id - */ - public String getNodeId() { - return node_id; - } + private GHUser reviewRequester; + private String url; /** - * Gets url. - * - * @return the url + * Create default GHIssueEvent instance */ - public String getUrl() { - return url; + public GHIssueEvent() { } /** @@ -74,12 +49,14 @@ public GHUser getActor() { } /** - * Gets event. + * Get the {@link GHUser} that was assigned or unassigned from the issue. Only present for events "assigned" and + * "unassigned", null otherwise. * - * @return the event + * @return the user */ - public String getEvent() { - return event; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHUser getAssignee() { + return assignee; } /** @@ -88,7 +65,7 @@ public String getEvent() { * @return the commit id */ public String getCommitId() { - return commit_id; + return commitId; } /** @@ -97,7 +74,7 @@ public String getCommitId() { * @return the commit url */ public String getCommitUrl() { - return commit_url; + return commitUrl; } /** @@ -105,29 +82,37 @@ public String getCommitUrl() { * * @return the created at */ - public Date getCreatedAt() { - return GitHubClient.parseDate(created_at); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getCreatedAt() { + return GitHubClient.parseInstant(createdAt); } /** - * Gets issue. + * Gets event. * - * @return the issue + * @return the event */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHIssue getIssue() { - return issue; + public String getEvent() { + return event; } /** - * Get the {@link GHMilestone} that this issue was added to or removed from. Only present for events "milestoned" - * and "demilestoned", null otherwise. + * Gets id. * - * @return the milestone + * @return the id + */ + public long getId() { + return id; + } + + /** + * Gets issue. + * + * @return the issue */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHMilestone getMilestone() { - return milestone; + public GHIssue getIssue() { + return issue; } /** @@ -142,14 +127,23 @@ public GHLabel getLabel() { } /** - * Get the {@link GHUser} that was assigned or unassigned from the issue. Only present for events "assigned" and - * "unassigned", null otherwise. + * Get the {@link GHMilestone} that this issue was added to or removed from. Only present for events "milestoned" + * and "demilestoned", null otherwise. * - * @return the user + * @return the milestone */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHUser getAssignee() { - return assignee; + public GHMilestone getMilestone() { + return milestone; + } + + /** + * Gets node id. + * + * @return the node id + */ + public String getNodeId() { + return nodeId; } /** @@ -164,7 +158,7 @@ public GHIssueRename getRename() { /** * - * Get the {@link GHUser} person who requested a review. Only present for events "review_requested", + * Get the {@link GHUser} person requested to review the pull request. Only present for events "review_requested", * "review_request_removed", null otherwise. * * @return the GHUser @@ -175,13 +169,13 @@ public GHIssueRename getRename() { * "https://docs.github.com/en/developers/webhooks-and-events/events/issue-event-types#review_request_removed">review_request_removed */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHUser getReviewRequester() { - return this.reviewRequester; + public GHUser getRequestedReviewer() { + return this.requestedReviewer; } /** * - * Get the {@link GHUser} person requested to review the pull request. Only present for events "review_requested", + * Get the {@link GHUser} person who requested a review. Only present for events "review_requested", * "review_request_removed", null otherwise. * * @return the GHUser @@ -192,20 +186,17 @@ public GHUser getReviewRequester() { * "https://docs.github.com/en/developers/webhooks-and-events/events/issue-event-types#review_request_removed">review_request_removed */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHUser getRequestedReviewer() { - return this.requestedReviewer; + public GHUser getReviewRequester() { + return this.reviewRequester; } /** - * Wrap up. + * Gets url. * - * @param parent - * the parent - * @return the GH issue event + * @return the url */ - GHIssueEvent wrapUp(GHIssue parent) { - this.issue = parent; - return this; + public String getUrl() { + return url; } /** @@ -221,4 +212,16 @@ public String toString() { getActor().getLogin(), getCreatedAt().toString()); } + + /** + * Wrap up. + * + * @param parent + * the parent + * @return the GH issue event + */ + GHIssueEvent wrapUp(GHIssue parent) { + this.issue = parent; + return this; + } } diff --git a/src/main/java/org/kohsuke/github/GHIssueQueryBuilder.java b/src/main/java/org/kohsuke/github/GHIssueQueryBuilder.java index cf43cb6391..c7ee67c690 100644 --- a/src/main/java/org/kohsuke/github/GHIssueQueryBuilder.java +++ b/src/main/java/org/kohsuke/github/GHIssueQueryBuilder.java @@ -1,5 +1,6 @@ package org.kohsuke.github; +import java.time.Instant; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -9,6 +10,108 @@ * The Class GHIssueQueryBuilder. */ public abstract class GHIssueQueryBuilder extends GHQueryBuilder { + /** + * The Class ForRepository. + */ + public static class ForRepository extends GHIssueQueryBuilder { + private final GHRepository repo; + + /** + * Instantiates a new for repository. + * + * @param repo + * the repo + */ + ForRepository(final GHRepository repo) { + super(repo.root()); + this.repo = repo; + } + + /** + * Assignee gh issue query builder. + * + * @param assignee + * the assignee + * @return the gh issue query builder + */ + public ForRepository assignee(String assignee) { + req.with("assignee", assignee); + return this; + } + + /** + * Creator gh issue query builder. + * + * @param creator + * the creator + * @return the gh issue query builder + */ + public ForRepository creator(String creator) { + req.with("creator", creator); + return this; + } + + /** + * Gets the api url. + * + * @return the api url + */ + @Override + public String getApiUrl() { + return repo.getApiTailUrl("issues"); + } + + /** + * List. + * + * @return the paged iterable + */ + @Override + public PagedIterable list() { + return req.withUrlPath(getApiUrl()).toIterable(GHIssue[].class, item -> item.wrap(repo)); + } + + /** + * Mentioned gh issue query builder. + * + * @param mentioned + * the mentioned + * @return the gh issue query builder + */ + public ForRepository mentioned(String mentioned) { + req.with("mentioned", mentioned); + return this; + } + + /** + * Milestone gh issue query builder. + *

+ * The milestone must be either an integer (the milestone number), the string * (issues with any milestone) or + * the string none (issues without milestone). + * + * @param milestone + * the milestone + * @return the gh issue request query builder + */ + public ForRepository milestone(String milestone) { + req.with("milestone", milestone); + return this; + } + } + + /** + * The enum Sort. + */ + public enum Sort { + + /** The comments. */ + COMMENTS, + /** The created. */ + CREATED, + /** The updated. */ + UPDATED + } + private final List labels = new ArrayList<>(); /** @@ -22,17 +125,24 @@ public abstract class GHIssueQueryBuilder extends GHQueryBuilder { } /** - * State gh issue query builder. + * Direction gh issue query builder. * - * @param state - * the state + * @param direction + * the direction * @return the gh issue query builder */ - public GHIssueQueryBuilder state(GHIssueState state) { - req.with("state", state); + public GHIssueQueryBuilder direction(GHDirection direction) { + req.with("direction", direction); return this; } + /** + * Gets the api url. + * + * @return the api url + */ + public abstract String getApiUrl(); + /** * Labels gh issue query builder. * @@ -49,27 +159,28 @@ public GHIssueQueryBuilder label(String label) { } /** - * Sort gh issue query builder. + * Page size gh issue query builder. * - * @param sort - * the sort + * @param pageSize + * the page size * @return the gh issue query builder */ - public GHIssueQueryBuilder sort(Sort sort) { - req.with("sort", sort); + public GHIssueQueryBuilder pageSize(int pageSize) { + req.with("per_page", pageSize); return this; } /** - * Direction gh issue query builder. + * Only issues after this date will be returned. * - * @param direction - * the direction + * @param date + * the date * @return the gh issue query builder + * @deprecated Use {@link #since(Instant)} */ - public GHIssueQueryBuilder direction(GHDirection direction) { - req.with("direction", direction); - return this; + @Deprecated + public GHIssueQueryBuilder since(Date date) { + return since(GitHubClient.toInstantOrNull(date)); } /** @@ -79,8 +190,8 @@ public GHIssueQueryBuilder direction(GHDirection direction) { * the date * @return the gh issue query builder */ - public GHIssueQueryBuilder since(Date date) { - req.with("since", GitHubClient.printDate(date)); + public GHIssueQueryBuilder since(Instant date) { + req.with("since", GitHubClient.printInstant(date)); return this; } @@ -96,123 +207,26 @@ public GHIssueQueryBuilder since(long timestamp) { } /** - * Page size gh issue query builder. + * Sort gh issue query builder. * - * @param pageSize - * the page size + * @param sort + * the sort * @return the gh issue query builder */ - public GHIssueQueryBuilder pageSize(int pageSize) { - req.with("per_page", pageSize); + public GHIssueQueryBuilder sort(Sort sort) { + req.with("sort", sort); return this; } /** - * The enum Sort. - */ - public enum Sort { - - /** The created. */ - CREATED, - /** The updated. */ - UPDATED, - /** The comments. */ - COMMENTS - } - - /** - * Gets the api url. + * State gh issue query builder. * - * @return the api url - */ - public abstract String getApiUrl(); - - /** - * The Class ForRepository. + * @param state + * the state + * @return the gh issue query builder */ - public static class ForRepository extends GHIssueQueryBuilder { - private final GHRepository repo; - - /** - * Instantiates a new for repository. - * - * @param repo - * the repo - */ - ForRepository(final GHRepository repo) { - super(repo.root()); - this.repo = repo; - } - - /** - * Milestone gh issue query builder. - *

- * The milestone must be either an integer (the milestone number), the string * (issues with any milestone) or - * the string none (issues without milestone). - * - * @param milestone - * the milestone - * @return the gh issue request query builder - */ - public ForRepository milestone(String milestone) { - req.with("milestone", milestone); - return this; - } - - /** - * Assignee gh issue query builder. - * - * @param assignee - * the assignee - * @return the gh issue query builder - */ - public ForRepository assignee(String assignee) { - req.with("assignee", assignee); - return this; - } - - /** - * Creator gh issue query builder. - * - * @param creator - * the creator - * @return the gh issue query builder - */ - public ForRepository creator(String creator) { - req.with("creator", creator); - return this; - } - - /** - * Mentioned gh issue query builder. - * - * @param mentioned - * the mentioned - * @return the gh issue query builder - */ - public ForRepository mentioned(String mentioned) { - req.with("mentioned", mentioned); - return this; - } - - /** - * Gets the api url. - * - * @return the api url - */ - @Override - public String getApiUrl() { - return repo.getApiTailUrl("issues"); - } - - /** - * List. - * - * @return the paged iterable - */ - @Override - public PagedIterable list() { - return req.withUrlPath(getApiUrl()).toIterable(GHIssue[].class, item -> item.wrap(repo)); - } + public GHIssueQueryBuilder state(GHIssueState state) { + req.with("state", state); + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHIssueRename.java b/src/main/java/org/kohsuke/github/GHIssueRename.java index cd20c86a1d..79b8042a2e 100644 --- a/src/main/java/org/kohsuke/github/GHIssueRename.java +++ b/src/main/java/org/kohsuke/github/GHIssueRename.java @@ -10,15 +10,15 @@ */ public class GHIssueRename { + private String from = ""; + + private String to = ""; /** * Create default GHIssueRename instance */ public GHIssueRename() { } - private String from = ""; - private String to = ""; - /** * Old issue name. * diff --git a/src/main/java/org/kohsuke/github/GHIssueSearchBuilder.java b/src/main/java/org/kohsuke/github/GHIssueSearchBuilder.java index 95a266847d..d62f7d91d6 100644 --- a/src/main/java/org/kohsuke/github/GHIssueSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHIssueSearchBuilder.java @@ -11,6 +11,33 @@ */ public class GHIssueSearchBuilder extends GHSearchBuilder { + /** + * The enum Sort. + */ + public enum Sort { + + /** The comments. */ + COMMENTS, + /** The created. */ + CREATED, + /** The updated. */ + UPDATED + } + + @SuppressFBWarnings( + value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, + justification = "JSON API") + private static class IssueSearchResult extends SearchResult { + private GHIssue[] items; + + @Override + GHIssue[] getItems(GitHub root) { + for (GHIssue i : items) { + } + return items; + } + } + /** * Instantiates a new GH issue search builder. * @@ -22,37 +49,31 @@ public class GHIssueSearchBuilder extends GHSearchBuilder { } /** - * Search terms. + * Is closed gh issue search builder. * - * @param term - * the term - * @return the GH issue search builder + * @return the gh issue search builder */ - public GHIssueSearchBuilder q(String term) { - super.q(term); - return this; + public GHIssueSearchBuilder isClosed() { + return q("is:closed"); } /** - * Mentions gh issue search builder. + * Filters results to only include issues (excludes pull requests). * - * @param u - * the u * @return the gh issue search builder */ - public GHIssueSearchBuilder mentions(GHUser u) { - return mentions(u.getLogin()); + public GHIssueSearchBuilder isIssue() { + terms.removeIf("is:pr"::equals); + return q("is:issue"); } /** - * Mentions gh issue search builder. + * Is merged gh issue search builder. * - * @param login - * the login * @return the gh issue search builder */ - public GHIssueSearchBuilder mentions(String login) { - return q("mentions:" + login); + public GHIssueSearchBuilder isMerged() { + return q("is:merged"); } /** @@ -65,21 +86,35 @@ public GHIssueSearchBuilder isOpen() { } /** - * Is closed gh issue search builder. + * Filters results to only include pull requests (excludes issues). * * @return the gh issue search builder */ - public GHIssueSearchBuilder isClosed() { - return q("is:closed"); + public GHIssueSearchBuilder isPullRequest() { + terms.removeIf("is:issue"::equals); + return q("is:pr"); } /** - * Is merged gh issue search builder. + * Mentions gh issue search builder. * + * @param u + * the u * @return the gh issue search builder */ - public GHIssueSearchBuilder isMerged() { - return q("is:merged"); + public GHIssueSearchBuilder mentions(GHUser u) { + return mentions(u.getLogin()); + } + + /** + * Mentions gh issue search builder. + * + * @param login + * the login + * @return the gh issue search builder + */ + public GHIssueSearchBuilder mentions(String login) { + return q("mentions:" + login); } /** @@ -95,42 +130,40 @@ public GHIssueSearchBuilder order(GHDirection v) { } /** - * Sort gh issue search builder. + * Search terms. * - * @param sort - * the sort - * @return the gh issue search builder + * @param term + * the term + * @return the GH issue search builder */ - public GHIssueSearchBuilder sort(Sort sort) { - req.with("sort", sort); + public GHIssueSearchBuilder q(String term) { + super.q(term); return this; } /** - * The enum Sort. + * Filters results to a specific repository. + * + * @param owner + * the repository owner + * @param name + * the repository name + * @return the gh issue search builder */ - public enum Sort { - - /** The comments. */ - COMMENTS, - /** The created. */ - CREATED, - /** The updated. */ - UPDATED + public GHIssueSearchBuilder repo(String owner, String name) { + return q("repo:" + owner + "/" + name); } - @SuppressFBWarnings( - value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, - justification = "JSON API") - private static class IssueSearchResult extends SearchResult { - private GHIssue[] items; - - @Override - GHIssue[] getItems(GitHub root) { - for (GHIssue i : items) { - } - return items; - } + /** + * Sort gh issue search builder. + * + * @param sort + * the sort + * @return the gh issue search builder + */ + public GHIssueSearchBuilder sort(Sort sort) { + req.with("sort", sort); + return this; } /** diff --git a/src/main/java/org/kohsuke/github/GHIssueState.java b/src/main/java/org/kohsuke/github/GHIssueState.java index 17a69251e2..9eabf43ef4 100644 --- a/src/main/java/org/kohsuke/github/GHIssueState.java +++ b/src/main/java/org/kohsuke/github/GHIssueState.java @@ -32,10 +32,10 @@ */ public enum GHIssueState { - /** The open. */ - OPEN, + /** The all. */ + ALL, /** The closed. */ CLOSED, - /** The all. */ - ALL + /** The open. */ + OPEN } diff --git a/src/main/java/org/kohsuke/github/GHKey.java b/src/main/java/org/kohsuke/github/GHKey.java index d0e90a2032..812183eff9 100644 --- a/src/main/java/org/kohsuke/github/GHKey.java +++ b/src/main/java/org/kohsuke/github/GHKey.java @@ -14,11 +14,8 @@ @SuppressFBWarnings(value = "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", justification = "JSON API") public class GHKey extends GitHubInteractiveObject { - /** - * Create default GHKey instance - */ - public GHKey() { - } + /** The id. */ + protected int id; /** The title. */ protected String url, key, title; @@ -26,8 +23,21 @@ public GHKey() { /** The verified. */ protected boolean verified; - /** The id. */ - protected int id; + /** + * Create default GHKey instance + */ + public GHKey() { + } + + /** + * Delete the GHKey + * + * @throws IOException + * the io exception + */ + public void delete() throws IOException { + root().createRequest().method("DELETE").withUrlPath(String.format("/user/keys/%d", id)).send(); + } /** * Gets id. @@ -82,14 +92,4 @@ public boolean isVerified() { public String toString() { return new ToStringBuilder(this).append("title", title).append("id", id).append("key", key).toString(); } - - /** - * Delete the GHKey - * - * @throws IOException - * the io exception - */ - public void delete() throws IOException { - root().createRequest().method("DELETE").withUrlPath(String.format("/user/keys/%d", id)).send(); - } } diff --git a/src/main/java/org/kohsuke/github/GHLabel.java b/src/main/java/org/kohsuke/github/GHLabel.java index 83f1dfa735..dd3c44523e 100644 --- a/src/main/java/org/kohsuke/github/GHLabel.java +++ b/src/main/java/org/kohsuke/github/GHLabel.java @@ -24,115 +24,41 @@ */ public class GHLabel extends GitHubInteractiveObject { - private long id; - private String nodeId; - @JsonProperty("default") - private boolean default_; - - @Nonnull - private String url, name, color; - - @CheckForNull - private String description; - - @JsonCreator - private GHLabel(@JacksonInject @Nonnull GitHub root) { - url = ""; - name = ""; - color = ""; - description = null; - } - - /** - * Gets the api root. - * - * @return the api root - */ - @Nonnull - GitHub getApiRoot() { - return Objects.requireNonNull(root()); - } - - /** - * Gets id. - * - * @return the id - */ - public long getId() { - return id; - } - - /** - * Gets node id. - * - * @return the node id. - */ - public String getNodeId() { - return nodeId; - } - - /** - * Gets url. - * - * @return the url - */ - @Nonnull - public String getUrl() { - return url; - } - - /** - * Gets name. - * - * @return the name - */ - @Nonnull - public String getName() { - return name; - } - /** - * Color code without leading '#', such as 'f29513'. - * - * @return the color - */ - @Nonnull - public String getColor() { - return color; - } - - /** - * Purpose of Label. + * A {@link GHLabelBuilder} that creates a new {@link GHLabel} * - * @return the description + * Consumer must call {@link Creator#done()} to create the new instance. */ - @CheckForNull - public String getDescription() { - return description; + @BetaApi + public static class Creator extends GHLabelBuilder { + private Creator(@Nonnull GHRepository repository) { + super(Creator.class, repository.root(), null); + requester.method("POST").withUrlPath(repository.getApiTailUrl("labels")); + } } - /** - * If the label is one of the default labels created by GitHub automatically. + * A {@link GHLabelBuilder} that updates a single property per request * - * @return true if the label is a default one + * {@link Setter#done()} is called automatically after the property is set. */ - public boolean isDefault() { - return default_; + @BetaApi + public static class Setter extends GHLabelBuilder { + private Setter(@Nonnull GHLabel base) { + super(GHLabel.class, base.getApiRoot(), base); + requester.method("PATCH").setRawUrlPath(base.getUrl()); + } } - /** - * To names. + * A {@link GHLabelBuilder} that allows multiple properties to be updated per request. * - * @param labels - * the labels - * @return the collection + * Consumer must call {@link Updater#done()} to commit changes. */ - static Collection toNames(Collection labels) { - List r = new ArrayList<>(); - for (GHLabel l : labels) { - r.add(l.getName()); + @BetaApi + public static class Updater extends GHLabelBuilder { + private Updater(@Nonnull GHLabel base) { + super(Updater.class, base.getApiRoot(), base); + requester.method("PATCH").setRawUrlPath(base.getUrl()); } - return r; } /** @@ -184,25 +110,39 @@ static PagedIterable readAll(@Nonnull final GHRepository repository) { } /** - * Begins a batch update - * - * Consumer must call {@link Updater#done()} to commit changes. + * To names. * - * @return a {@link Updater} + * @param labels + * the labels + * @return the collection */ - @BetaApi - public Updater update() { - return new Updater(this); + static Collection toNames(Collection labels) { + List r = new ArrayList<>(); + for (GHLabel l : labels) { + r.add(l.getName()); + } + return r; } - /** - * Begins a single property update. - * - * @return a {@link Setter} - */ - @BetaApi - public Setter set() { - return new Setter(this); + @CheckForNull + private String description; + + private long id; + + @JsonProperty("default") + private boolean isDefault; + + private String nodeId; + + @Nonnull + private String url, name, color; + + @JsonCreator + private GHLabel(@JacksonInject @Nonnull GitHub root) { + url = ""; + name = ""; + color = ""; + description = null; } /** @@ -233,6 +173,64 @@ public boolean equals(final Object o) { && Objects.equals(color, ghLabel.color) && Objects.equals(description, ghLabel.description); } + /** + * Color code without leading '#', such as 'f29513'. + * + * @return the color + */ + @Nonnull + public String getColor() { + return color; + } + + /** + * Purpose of Label. + * + * @return the description + */ + @CheckForNull + public String getDescription() { + return description; + } + + /** + * Gets id. + * + * @return the id + */ + public long getId() { + return id; + } + + /** + * Gets name. + * + * @return the name + */ + @Nonnull + public String getName() { + return name; + } + + /** + * Gets node id. + * + * @return the node id. + */ + public String getNodeId() { + return nodeId; + } + + /** + * Gets url. + * + * @return the url + */ + @Nonnull + public String getUrl() { + return url; + } + /** * Hash code. * @@ -244,42 +242,44 @@ public int hashCode() { } /** - * A {@link GHLabelBuilder} that updates a single property per request + * If the label is one of the default labels created by GitHub automatically. * - * {@link Setter#done()} is called automatically after the property is set. + * @return true if the label is a default one + */ + public boolean isDefault() { + return isDefault; + } + + /** + * Begins a single property update. + * + * @return a {@link Setter} */ @BetaApi - public static class Setter extends GHLabelBuilder { - private Setter(@Nonnull GHLabel base) { - super(GHLabel.class, base.getApiRoot(), base); - requester.method("PATCH").setRawUrlPath(base.getUrl()); - } + public Setter set() { + return new Setter(this); } /** - * A {@link GHLabelBuilder} that allows multiple properties to be updated per request. + * Begins a batch update * * Consumer must call {@link Updater#done()} to commit changes. + * + * @return a {@link Updater} */ @BetaApi - public static class Updater extends GHLabelBuilder { - private Updater(@Nonnull GHLabel base) { - super(Updater.class, base.getApiRoot(), base); - requester.method("PATCH").setRawUrlPath(base.getUrl()); - } + public Updater update() { + return new Updater(this); } /** - * A {@link GHLabelBuilder} that creates a new {@link GHLabel} + * Gets the api root. * - * Consumer must call {@link Creator#done()} to create the new instance. + * @return the api root */ - @BetaApi - public static class Creator extends GHLabelBuilder { - private Creator(@Nonnull GHRepository repository) { - super(Creator.class, repository.root(), null); - requester.method("POST").withUrlPath(repository.getApiTailUrl("labels")); - } + @Nonnull + GitHub getApiRoot() { + return Objects.requireNonNull(root()); } } diff --git a/src/main/java/org/kohsuke/github/GHLabelBuilder.java b/src/main/java/org/kohsuke/github/GHLabelBuilder.java index 62a5c2ce7e..ffbcb3fbf5 100644 --- a/src/main/java/org/kohsuke/github/GHLabelBuilder.java +++ b/src/main/java/org/kohsuke/github/GHLabelBuilder.java @@ -41,7 +41,7 @@ protected GHLabelBuilder(@Nonnull Class intermediateReturnType, } /** - * Name. + * Color. * * @param value * the value @@ -51,12 +51,12 @@ protected GHLabelBuilder(@Nonnull Class intermediateReturnType, */ @Nonnull @BetaApi - public S name(String value) throws IOException { - return with("name", value); + public S color(String value) throws IOException { + return with("color", value); } /** - * Color. + * Description. * * @param value * the value @@ -66,12 +66,12 @@ public S name(String value) throws IOException { */ @Nonnull @BetaApi - public S color(String value) throws IOException { - return with("color", value); + public S description(String value) throws IOException { + return with("description", value); } /** - * Description. + * Name. * * @param value * the value @@ -81,7 +81,7 @@ public S color(String value) throws IOException { */ @Nonnull @BetaApi - public S description(String value) throws IOException { - return with("description", value); + public S name(String value) throws IOException { + return with("name", value); } } diff --git a/src/main/java/org/kohsuke/github/GHLabelChanges.java b/src/main/java/org/kohsuke/github/GHLabelChanges.java index a3880dcbf6..c76058e3dc 100644 --- a/src/main/java/org/kohsuke/github/GHLabelChanges.java +++ b/src/main/java/org/kohsuke/github/GHLabelChanges.java @@ -12,21 +12,35 @@ public class GHLabelChanges { /** - * Create default GHLabelChanges instance + * Wrapper for changed values. */ - public GHLabelChanges() { + public static class GHFrom { + + private String from; + + /** + * Create default GHFrom instance + */ + public GHFrom() { + } + + /** + * Previous value that was changed. + * + * @return previous value + */ + public String getFrom() { + return from; + } } - private GHFrom name; private GHFrom color; + private GHFrom name; /** - * Old label name. - * - * @return old label name (or null if not changed) + * Create default GHLabelChanges instance */ - public GHFrom getName() { - return name; + public GHLabelChanges() { } /** @@ -39,25 +53,11 @@ public GHFrom getColor() { } /** - * Wrapper for changed values. + * Old label name. + * + * @return old label name (or null if not changed) */ - public static class GHFrom { - - /** - * Create default GHFrom instance - */ - public GHFrom() { - } - - private String from; - - /** - * Previous value that was changed. - * - * @return previous value - */ - public String getFrom() { - return from; - } + public GHFrom getName() { + return name; } } diff --git a/src/main/java/org/kohsuke/github/GHLicense.java b/src/main/java/org/kohsuke/github/GHLicense.java index 9a66ad3a98..71aff84609 100644 --- a/src/main/java/org/kohsuke/github/GHLicense.java +++ b/src/main/java/org/kohsuke/github/GHLicense.java @@ -47,81 +47,72 @@ justification = "JSON API") public class GHLicense extends GHObject { - /** - * Create default GHLicense instance - */ - public GHLicense() { - } - - /** The name. */ - // these fields are always present, even in the short form - protected String key, name, spdxId; - /** The featured. */ // the rest is only after populated protected Boolean featured; + /** The forbidden. */ + protected List forbidden = new ArrayList(); + /** The body. */ - protected String html_url, description, category, implementation, body; + protected String htmlUrl, description, category, implementation, body; - /** The required. */ - protected List required = new ArrayList(); + /** The name. */ + // these fields are always present, even in the short form + protected String key, name, spdxId; /** The permitted. */ protected List permitted = new ArrayList(); - /** The forbidden. */ - protected List forbidden = new ArrayList(); + /** The required. */ + protected List required = new ArrayList(); /** - * Gets key. - * - * @return a mnemonic for the license + * Create default GHLicense instance */ - public String getKey() { - return key; + public GHLicense() { } /** - * Gets name. + * Equals. * - * @return the license name + * @param o + * the o + * @return true, if successful */ - public String getName() { - return name; - } + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof GHLicense)) + return false; - /** - * Gets SPDX ID. - * - * @return the spdx id - */ - public String getSpdxId() { - return spdxId; + GHLicense that = (GHLicense) o; + return Objects.equals(getUrl(), that.getUrl()); } /** - * Featured licenses are bold in the new repository drop-down. + * Gets body. * - * @return True if the license is featured, false otherwise + * @return the body * @throws IOException * the io exception */ - public Boolean isFeatured() throws IOException { + public String getBody() throws IOException { populate(); - return featured; + return body; } /** - * Gets the html url. + * Gets category. * - * @return the html url + * @return the category * @throws IOException - * Signals that an I/O exception has occurred. + * the io exception */ - public URL getHtmlUrl() throws IOException { + public String getCategory() throws IOException { populate(); - return GitHubClient.parseURL(html_url); + return category; } /** @@ -137,15 +128,27 @@ public String getDescription() throws IOException { } /** - * Gets category. + * Gets forbidden. * - * @return the category + * @return the forbidden * @throws IOException * the io exception */ - public String getCategory() throws IOException { + public List getForbidden() throws IOException { populate(); - return category; + return Collections.unmodifiableList(forbidden); + } + + /** + * Gets the html url. + * + * @return the html url + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public URL getHtmlUrl() throws IOException { + populate(); + return GitHubClient.parseURL(htmlUrl); } /** @@ -161,15 +164,21 @@ public String getImplementation() throws IOException { } /** - * Gets required. + * Gets key. * - * @return the required - * @throws IOException - * the io exception + * @return a mnemonic for the license */ - public List getRequired() throws IOException { - populate(); - return Collections.unmodifiableList(required); + public String getKey() { + return key; + } + + /** + * Gets name. + * + * @return the license name + */ + public String getName() { + return name; } /** @@ -185,27 +194,46 @@ public List getPermitted() throws IOException { } /** - * Gets forbidden. + * Gets required. * - * @return the forbidden + * @return the required * @throws IOException * the io exception */ - public List getForbidden() throws IOException { + public List getRequired() throws IOException { populate(); - return Collections.unmodifiableList(forbidden); + return Collections.unmodifiableList(required); } /** - * Gets body. + * Gets SPDX ID. * - * @return the body + * @return the spdx id + */ + public String getSpdxId() { + return spdxId; + } + + /** + * Hash code. + * + * @return the int + */ + @Override + public int hashCode() { + return Objects.hashCode(getUrl()); + } + + /** + * Featured licenses are bold in the new repository drop-down. + * + * @return True if the license is featured, false otherwise * @throws IOException * the io exception */ - public String getBody() throws IOException { + public Boolean isFeatured() throws IOException { populate(); - return body; + return featured; } /** @@ -229,32 +257,4 @@ protected synchronized void populate() throws IOException { root().createRequest().setRawUrlPath(url.toString()).fetchInto(this); } } - - /** - * Equals. - * - * @param o - * the o - * @return true, if successful - */ - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof GHLicense)) - return false; - - GHLicense that = (GHLicense) o; - return Objects.equals(getUrl(), that.getUrl()); - } - - /** - * Hash code. - * - * @return the int - */ - @Override - public int hashCode() { - return Objects.hashCode(getUrl()); - } } diff --git a/src/main/java/org/kohsuke/github/GHMarketplaceAccount.java b/src/main/java/org/kohsuke/github/GHMarketplaceAccount.java index df0261e880..ac872ee8ba 100644 --- a/src/main/java/org/kohsuke/github/GHMarketplaceAccount.java +++ b/src/main/java/org/kohsuke/github/GHMarketplaceAccount.java @@ -13,26 +13,26 @@ */ public class GHMarketplaceAccount extends GitHubInteractiveObject { - /** - * Create default GHMarketplaceAccount instance - */ - public GHMarketplaceAccount() { - } + private String email; - private String url; private long id; private String login; - private String email; private String organizationBillingEmail; private GHMarketplaceAccountType type; + private String url; + /** + * Create default GHMarketplaceAccount instance + */ + public GHMarketplaceAccount() { + } /** - * Gets url. + * Gets email. * - * @return the url + * @return the email */ - public URL getUrl() { - return GitHubClient.parseURL(url); + public String getEmail() { + return email; } /** @@ -53,15 +53,6 @@ public String getLogin() { return login; } - /** - * Gets email. - * - * @return the email - */ - public String getEmail() { - return email; - } - /** * Gets organization billing email. * @@ -71,15 +62,6 @@ public String getOrganizationBillingEmail() { return organizationBillingEmail; } - /** - * Gets type. - * - * @return the type - */ - public GHMarketplaceAccountType getType() { - return type; - } - /** * Shows whether the user or organization account actively subscribes to a plan listed by the authenticated GitHub * App. When someone submits a plan change that won't be processed until the end of their billing cycle, you will @@ -101,4 +83,22 @@ public GHMarketplaceAccountPlan getPlan() throws IOException { return new GHMarketplacePlanForAccountBuilder(root(), this.id).createRequest(); } + /** + * Gets type. + * + * @return the type + */ + public GHMarketplaceAccountType getType() { + return type; + } + + /** + * Gets url. + * + * @return the url + */ + public URL getUrl() { + return GitHubClient.parseURL(url); + } + } diff --git a/src/main/java/org/kohsuke/github/GHMarketplaceAccountPlan.java b/src/main/java/org/kohsuke/github/GHMarketplaceAccountPlan.java index c780aa4dde..c53a1f8a5a 100644 --- a/src/main/java/org/kohsuke/github/GHMarketplaceAccountPlan.java +++ b/src/main/java/org/kohsuke/github/GHMarketplaceAccountPlan.java @@ -11,17 +11,17 @@ */ public class GHMarketplaceAccountPlan extends GHMarketplaceAccount { + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") + private GHMarketplacePendingChange marketplacePendingChange; + + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") + private GHMarketplacePurchase marketplacePurchase; /** * Create default GHMarketplaceAccountPlan instance */ public GHMarketplaceAccountPlan() { } - @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") - private GHMarketplacePendingChange marketplacePendingChange; - @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") - private GHMarketplacePurchase marketplacePurchase; - /** * Gets marketplace pending change. * diff --git a/src/main/java/org/kohsuke/github/GHMarketplaceListAccountBuilder.java b/src/main/java/org/kohsuke/github/GHMarketplaceListAccountBuilder.java index 745034a8e4..63e53d2e3b 100644 --- a/src/main/java/org/kohsuke/github/GHMarketplaceListAccountBuilder.java +++ b/src/main/java/org/kohsuke/github/GHMarketplaceListAccountBuilder.java @@ -8,7 +8,18 @@ * @see GHMarketplacePlan#listAccounts() */ public class GHMarketplaceListAccountBuilder extends GitHubInteractiveObject { + /** + * The enum Sort. + */ + public enum Sort { + + /** The created. */ + CREATED, + /** The updated. */ + UPDATED + } private final Requester builder; + private final long planId; /** @@ -26,17 +37,17 @@ public class GHMarketplaceListAccountBuilder extends GitHubInteractiveObject { } /** - * Sorts the GitHub accounts by the date they were created or last updated. Can be one of created or updated. + * List any accounts associated with the plan specified on construction with all the order/sort parameters set. *

- * If omitted, the default sorting strategy will be "CREATED" + * GitHub Apps must use a JWT to access this endpoint. + *

+ * OAuth Apps must use basic authentication with their client ID and client secret to access this endpoint. * - * @param sort - * the sort strategy - * @return a GHMarketplaceListAccountBuilder + * @return a paged iterable instance of GHMarketplaceAccountPlan */ - public GHMarketplaceListAccountBuilder sort(Sort sort) { - this.builder.with("sort", sort); - return this; + public PagedIterable createRequest() { + return builder.withUrlPath(String.format("/marketplace_listing/plans/%d/accounts", this.planId)) + .toIterable(GHMarketplaceAccountPlan[].class, null); } /** @@ -52,28 +63,17 @@ public GHMarketplaceListAccountBuilder direction(GHDirection direction) { } /** - * The enum Sort. - */ - public enum Sort { - - /** The created. */ - CREATED, - /** The updated. */ - UPDATED - } - - /** - * List any accounts associated with the plan specified on construction with all the order/sort parameters set. - *

- * GitHub Apps must use a JWT to access this endpoint. + * Sorts the GitHub accounts by the date they were created or last updated. Can be one of created or updated. *

- * OAuth Apps must use basic authentication with their client ID and client secret to access this endpoint. + * If omitted, the default sorting strategy will be "CREATED" * - * @return a paged iterable instance of GHMarketplaceAccountPlan + * @param sort + * the sort strategy + * @return a GHMarketplaceListAccountBuilder */ - public PagedIterable createRequest() { - return builder.withUrlPath(String.format("/marketplace_listing/plans/%d/accounts", this.planId)) - .toIterable(GHMarketplaceAccountPlan[].class, null); + public GHMarketplaceListAccountBuilder sort(Sort sort) { + this.builder.with("sort", sort); + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHMarketplacePendingChange.java b/src/main/java/org/kohsuke/github/GHMarketplacePendingChange.java index d91e6e5417..39fcdf4f8b 100644 --- a/src/main/java/org/kohsuke/github/GHMarketplacePendingChange.java +++ b/src/main/java/org/kohsuke/github/GHMarketplacePendingChange.java @@ -1,7 +1,9 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.time.Instant; import java.util.Date; // TODO: Auto-generated Javadoc @@ -13,36 +15,37 @@ */ public class GHMarketplacePendingChange extends GitHubInteractiveObject { - /** - * Create default GHMarketplacePendingChange instance - */ - public GHMarketplacePendingChange() { - } + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") + private String effectiveDate; private long id; @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") - private Long unitCount; - @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") private GHMarketplacePlan plan; @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") - private String effectiveDate; + private Long unitCount; + /** + * Create default GHMarketplacePendingChange instance + */ + public GHMarketplacePendingChange() { + } /** - * Gets id. + * Gets effective date. * - * @return the id + * @return the effective date */ - public long getId() { - return id; + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getEffectiveDate() { + return GitHubClient.parseInstant(effectiveDate); } /** - * Gets unit count. + * Gets id. * - * @return the unit count + * @return the id */ - public Long getUnitCount() { - return unitCount; + public long getId() { + return id; } /** @@ -55,12 +58,12 @@ public GHMarketplacePlan getPlan() { } /** - * Gets effective date. + * Gets unit count. * - * @return the effective date + * @return the unit count */ - public Date getEffectiveDate() { - return GitHubClient.parseDate(effectiveDate); + public Long getUnitCount() { + return unitCount; } } diff --git a/src/main/java/org/kohsuke/github/GHMarketplacePlan.java b/src/main/java/org/kohsuke/github/GHMarketplacePlan.java index 43bcfe3be1..03cc87c662 100644 --- a/src/main/java/org/kohsuke/github/GHMarketplacePlan.java +++ b/src/main/java/org/kohsuke/github/GHMarketplacePlan.java @@ -15,34 +15,25 @@ */ public class GHMarketplacePlan extends GitHubInteractiveObject { - /** - * Create default GHMarketplacePlan instance - */ - public GHMarketplacePlan() { - } - - private String url; private String accountsUrl; - private long id; - private long number; - private String name; + + private List bullets; private String description; - private long monthlyPriceInCents; - private long yearlyPriceInCents; - private GHMarketplacePriceModel priceModel; @JsonProperty("has_free_trial") private boolean freeTrial; // JavaBeans Spec 1.01 section 8.3.2 forces us to have is - private String unitName; + private long id; + private long monthlyPriceInCents; + private String name; + private long number; + private GHMarketplacePriceModel priceModel; private String state; - private List bullets; - + private String unitName; + private String url; + private long yearlyPriceInCents; /** - * Gets url. - * - * @return the url + * Create default GHMarketplacePlan instance */ - public URL getUrl() { - return GitHubClient.parseURL(url); + public GHMarketplacePlan() { } /** @@ -55,57 +46,57 @@ public String getAccountsUrl() { } /** - * Gets id. + * Gets bullets. * - * @return the id + * @return the bullets */ - public long getId() { - return id; + public List getBullets() { + return Collections.unmodifiableList(bullets); } /** - * Gets number. + * Gets description. * - * @return the number + * @return the description */ - public long getNumber() { - return number; + public String getDescription() { + return description; } /** - * Gets name. + * Gets id. * - * @return the name + * @return the id */ - public String getName() { - return name; + public long getId() { + return id; } /** - * Gets description. + * Gets monthly price in cents. * - * @return the description + * @return the monthly price in cents */ - public String getDescription() { - return description; + public long getMonthlyPriceInCents() { + return monthlyPriceInCents; } /** - * Gets monthly price in cents. + * Gets name. * - * @return the monthly price in cents + * @return the name */ - public long getMonthlyPriceInCents() { - return monthlyPriceInCents; + public String getName() { + return name; } /** - * Gets yearly price in cents. + * Gets number. * - * @return the yearly price in cents + * @return the number */ - public long getYearlyPriceInCents() { - return yearlyPriceInCents; + public long getNumber() { + return number; } /** @@ -118,12 +109,12 @@ public GHMarketplacePriceModel getPriceModel() { } /** - * Is free trial boolean. + * Gets state. * - * @return the boolean + * @return the state */ - public boolean isFreeTrial() { - return freeTrial; + public String getState() { + return state; } /** @@ -136,21 +127,30 @@ public String getUnitName() { } /** - * Gets state. + * Gets url. * - * @return the state + * @return the url */ - public String getState() { - return state; + public URL getUrl() { + return GitHubClient.parseURL(url); } /** - * Gets bullets. + * Gets yearly price in cents. * - * @return the bullets + * @return the yearly price in cents */ - public List getBullets() { - return Collections.unmodifiableList(bullets); + public long getYearlyPriceInCents() { + return yearlyPriceInCents; + } + + /** + * Is free trial boolean. + * + * @return the boolean + */ + public boolean isFreeTrial() { + return freeTrial; } /** diff --git a/src/main/java/org/kohsuke/github/GHMarketplacePlanForAccountBuilder.java b/src/main/java/org/kohsuke/github/GHMarketplacePlanForAccountBuilder.java index e5167f31ac..55715a85ef 100644 --- a/src/main/java/org/kohsuke/github/GHMarketplacePlanForAccountBuilder.java +++ b/src/main/java/org/kohsuke/github/GHMarketplacePlanForAccountBuilder.java @@ -11,8 +11,8 @@ * @see GitHub#listMarketplacePlans() */ public class GHMarketplacePlanForAccountBuilder extends GitHubInteractiveObject { - private final Requester builder; private final long accountId; + private final Requester builder; /** * Instantiates a new GH marketplace list account builder. diff --git a/src/main/java/org/kohsuke/github/GHMarketplacePriceModel.java b/src/main/java/org/kohsuke/github/GHMarketplacePriceModel.java index a41cfc651c..57cdcc9b7f 100644 --- a/src/main/java/org/kohsuke/github/GHMarketplacePriceModel.java +++ b/src/main/java/org/kohsuke/github/GHMarketplacePriceModel.java @@ -11,12 +11,12 @@ */ public enum GHMarketplacePriceModel { + /** The flat rate. */ + FLAT_RATE("FLAT_RATE"), /** The free. */ FREE("FREE"), /** The per unit. */ - PER_UNIT("PER_UNIT"), - /** The flat rate. */ - FLAT_RATE("FLAT_RATE"); + PER_UNIT("PER_UNIT"); @JsonValue private final String internalName; diff --git a/src/main/java/org/kohsuke/github/GHMarketplacePurchase.java b/src/main/java/org/kohsuke/github/GHMarketplacePurchase.java index a0c149e159..9b6b8f1e27 100644 --- a/src/main/java/org/kohsuke/github/GHMarketplacePurchase.java +++ b/src/main/java/org/kohsuke/github/GHMarketplacePurchase.java @@ -1,7 +1,9 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.time.Instant; import java.util.Date; // TODO: Auto-generated Javadoc @@ -13,20 +15,20 @@ */ public class GHMarketplacePurchase extends GitHubInteractiveObject { - /** - * Create default GHMarketplacePurchase instance - */ - public GHMarketplacePurchase() { - } - private String billingCycle; + + private String freeTrialEndsOn; private String nextBillingDate; private boolean onFreeTrial; - private String freeTrialEndsOn; - private Long unitCount; - private String updatedAt; @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") private GHMarketplacePlan plan; + private Long unitCount; + private String updatedAt; + /** + * Create default GHMarketplacePurchase instance + */ + public GHMarketplacePurchase() { + } /** * Gets billing cycle. @@ -38,30 +40,32 @@ public String getBillingCycle() { } /** - * Gets next billing date. + * Gets free trial ends on. * - * @return the next billing date + * @return the free trial ends on */ - public Date getNextBillingDate() { - return GitHubClient.parseDate(nextBillingDate); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getFreeTrialEndsOn() { + return GitHubClient.parseInstant(freeTrialEndsOn); } /** - * Is on free trial boolean. + * Gets next billing date. * - * @return the boolean + * @return the next billing date */ - public boolean isOnFreeTrial() { - return onFreeTrial; + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getNextBillingDate() { + return GitHubClient.parseInstant(nextBillingDate); } /** - * Gets free trial ends on. + * Gets plan. * - * @return the free trial ends on + * @return the plan */ - public Date getFreeTrialEndsOn() { - return GitHubClient.parseDate(freeTrialEndsOn); + public GHMarketplacePlan getPlan() { + return plan; } /** @@ -78,16 +82,17 @@ public Long getUnitCount() { * * @return the updated at */ - public Date getUpdatedAt() { - return GitHubClient.parseDate(updatedAt); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getUpdatedAt() { + return GitHubClient.parseInstant(updatedAt); } /** - * Gets plan. + * Is on free trial boolean. * - * @return the plan + * @return the boolean */ - public GHMarketplacePlan getPlan() { - return plan; + public boolean isOnFreeTrial() { + return onFreeTrial; } } diff --git a/src/main/java/org/kohsuke/github/GHMarketplaceUserPurchase.java b/src/main/java/org/kohsuke/github/GHMarketplaceUserPurchase.java index fb2b210512..1ad622da99 100644 --- a/src/main/java/org/kohsuke/github/GHMarketplaceUserPurchase.java +++ b/src/main/java/org/kohsuke/github/GHMarketplaceUserPurchase.java @@ -1,7 +1,9 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.time.Instant; import java.util.Date; // TODO: Auto-generated Javadoc @@ -13,22 +15,31 @@ */ public class GHMarketplaceUserPurchase extends GitHubInteractiveObject { - /** - * Create default GHMarketplaceUserPurchase instance - */ - public GHMarketplaceUserPurchase() { - } + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") + private GHMarketplaceAccount account; private String billingCycle; + private String freeTrialEndsOn; private String nextBillingDate; private boolean onFreeTrial; - private String freeTrialEndsOn; - private Long unitCount; - private String updatedAt; - @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") - private GHMarketplaceAccount account; @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") private GHMarketplacePlan plan; + private Long unitCount; + private String updatedAt; + /** + * Create default GHMarketplaceUserPurchase instance + */ + public GHMarketplaceUserPurchase() { + } + + /** + * Gets account. + * + * @return the account + */ + public GHMarketplaceAccount getAccount() { + return account; + } /** * Gets billing cycle. @@ -40,30 +51,32 @@ public String getBillingCycle() { } /** - * Gets next billing date. + * Gets free trial ends on. * - * @return the next billing date + * @return the free trial ends on */ - public Date getNextBillingDate() { - return GitHubClient.parseDate(nextBillingDate); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getFreeTrialEndsOn() { + return GitHubClient.parseInstant(freeTrialEndsOn); } /** - * Is on free trial boolean. + * Gets next billing date. * - * @return the boolean + * @return the next billing date */ - public boolean isOnFreeTrial() { - return onFreeTrial; + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getNextBillingDate() { + return GitHubClient.parseInstant(nextBillingDate); } /** - * Gets free trial ends on. + * Gets plan. * - * @return the free trial ends on + * @return the plan */ - public Date getFreeTrialEndsOn() { - return GitHubClient.parseDate(freeTrialEndsOn); + public GHMarketplacePlan getPlan() { + return plan; } /** @@ -80,25 +93,17 @@ public Long getUnitCount() { * * @return the updated at */ - public Date getUpdatedAt() { - return GitHubClient.parseDate(updatedAt); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getUpdatedAt() { + return GitHubClient.parseInstant(updatedAt); } /** - * Gets account. - * - * @return the account - */ - public GHMarketplaceAccount getAccount() { - return account; - } - - /** - * Gets plan. + * Is on free trial boolean. * - * @return the plan + * @return the boolean */ - public GHMarketplacePlan getPlan() { - return plan; + public boolean isOnFreeTrial() { + return onFreeTrial; } } diff --git a/src/main/java/org/kohsuke/github/GHMemberChanges.java b/src/main/java/org/kohsuke/github/GHMemberChanges.java index 781753eaec..93f5043861 100644 --- a/src/main/java/org/kohsuke/github/GHMemberChanges.java +++ b/src/main/java/org/kohsuke/github/GHMemberChanges.java @@ -9,34 +9,26 @@ public class GHMemberChanges { /** - * Create default GHMemberChanges instance + * Changes to role name. */ - public GHMemberChanges() { - } - - private FromToPermission permission; + public static class FromRoleName { - private FromRoleName roleName; + private String to; - /** - * Get changes to permission. - * - * @return changes to permission - */ - public FromToPermission getPermission() { - return permission; - } + /** + * Create default FromRoleName instance + */ + public FromRoleName() { + } - /** - * Get changes to the role name. - *

- * Apparently, it is recommended to use this rather than permission if defined. But it will only be defined when - * adding and not when editing. - * - * @return changes to role name - */ - public FromRoleName getRoleName() { - return roleName; + /** + * Gets the to. + * + * @return the to + */ + public String getTo() { + return to; + } } /** @@ -44,16 +36,16 @@ public FromRoleName getRoleName() { */ public static class FromToPermission { + private String from; + + private String to; + /** * Create default FromToPermission instance */ public FromToPermission() { } - private String from; - - private String to; - /** * Gets the from. * @@ -77,26 +69,34 @@ public String getTo() { } } + private FromToPermission permission; + + private FromRoleName roleName; + /** - * Changes to role name. + * Create default GHMemberChanges instance */ - public static class FromRoleName { - - /** - * Create default FromRoleName instance - */ - public FromRoleName() { - } + public GHMemberChanges() { + } - private String to; + /** + * Get changes to permission. + * + * @return changes to permission + */ + public FromToPermission getPermission() { + return permission; + } - /** - * Gets the to. - * - * @return the to - */ - public String getTo() { - return to; - } + /** + * Get changes to the role name. + *

+ * Apparently, it is recommended to use this rather than permission if defined. But it will only be defined when + * adding and not when editing. + * + * @return changes to role name + */ + public FromRoleName getRoleName() { + return roleName; } } diff --git a/src/main/java/org/kohsuke/github/GHMembership.java b/src/main/java/org/kohsuke/github/GHMembership.java index e6b7e2e09c..d3b39282f9 100644 --- a/src/main/java/org/kohsuke/github/GHMembership.java +++ b/src/main/java/org/kohsuke/github/GHMembership.java @@ -18,42 +18,70 @@ public class GHMembership extends GitHubInteractiveObject { /** - * Create default GHMembership instance + * Role of a user in an organization. */ - public GHMembership() { + public enum Role { + /** + * Organization owner. + */ + ADMIN, + /** + * Non-owner organization member. + */ + MEMBER; } - /** The url. */ - String url; + /** + * Whether a role is currently active or waiting for acceptance (pending). + */ + public enum State { - /** The state. */ - String state; + /** The active. */ + ACTIVE, + /** The pending. */ + PENDING; + } + + /** The organization. */ + GHOrganization organization; /** The role. */ String role; + /** The state. */ + String state; + + /** The url. */ + String url; + /** The user. */ GHUser user; - /** The organization. */ - GHOrganization organization; + /** + * Create default GHMembership instance + */ + public GHMembership() { + } /** - * Gets url. + * Accepts a pending invitation to an organization. * - * @return the url + * @throws IOException + * the io exception + * @see GHMyself#getMembership(GHOrganization) GHMyself#getMembership(GHOrganization) */ - public URL getUrl() { - return GitHubClient.parseURL(url); + public void activate() throws IOException { + root().createRequest().method("PATCH").with("state", State.ACTIVE).withUrlPath(url).fetchInto(this); } /** - * Gets state. + * Gets organization. * - * @return the state + * @return the organization */ - public State getState() { - return Enum.valueOf(State.class, state.toUpperCase(Locale.ENGLISH)); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHOrganization getOrganization() { + return organization; } /** @@ -66,34 +94,31 @@ public Role getRole() { } /** - * Gets user. + * Gets state. * - * @return the user + * @return the state */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHUser getUser() { - return user; + public State getState() { + return Enum.valueOf(State.class, state.toUpperCase(Locale.ENGLISH)); } /** - * Gets organization. + * Gets url. * - * @return the organization + * @return the url */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHOrganization getOrganization() { - return organization; + public URL getUrl() { + return GitHubClient.parseURL(url); } /** - * Accepts a pending invitation to an organization. + * Gets user. * - * @throws IOException - * the io exception - * @see GHMyself#getMembership(GHOrganization) GHMyself#getMembership(GHOrganization) + * @return the user */ - public void activate() throws IOException { - root().createRequest().method("PATCH").with("state", State.ACTIVE).withUrlPath(url).fetchInto(this); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHUser getUser() { + return user; } /** @@ -108,29 +133,4 @@ GHMembership wrap(GitHub root) { user = root.getUser(user); return this; } - - /** - * Role of a user in an organization. - */ - public enum Role { - /** - * Organization owner. - */ - ADMIN, - /** - * Non-owner organization member. - */ - MEMBER; - } - - /** - * Whether a role is currently active or waiting for acceptance (pending). - */ - public enum State { - - /** The active. */ - ACTIVE, - /** The pending. */ - PENDING; - } } diff --git a/src/main/java/org/kohsuke/github/GHMeta.java b/src/main/java/org/kohsuke/github/GHMeta.java index 25cbcb0c3c..7978efe697 100644 --- a/src/main/java/org/kohsuke/github/GHMeta.java +++ b/src/main/java/org/kohsuke/github/GHMeta.java @@ -18,62 +18,53 @@ */ public class GHMeta { - /** - * Create default GHMeta instance - */ - public GHMeta() { - } + private List actions; - @JsonProperty("verifiable_password_authentication") - private boolean verifiablePasswordAuthentication; + private List api; + private List dependabot; + private List git; + private List hooks; + private List importer = new ArrayList<>(); + private List packages; + private List pages; @JsonProperty("ssh_key_fingerprints") private Map sshKeyFingerprints; @JsonProperty("ssh_keys") private List sshKeys; - private List hooks; - private List git; + @JsonProperty("verifiable_password_authentication") + private boolean verifiablePasswordAuthentication; private List web; - private List api; - private List pages; - private List importer = new ArrayList<>(); - private List packages; - private List actions; - private List dependabot; - /** - * Is verifiable password authentication boolean. - * - * @return the boolean + * Create default GHMeta instance */ - public boolean isVerifiablePasswordAuthentication() { - return verifiablePasswordAuthentication; + public GHMeta() { } /** - * Gets ssh key fingerprints. + * Gets actions. * - * @return the ssh key fingerprints + * @return the actions */ - public Map getSshKeyFingerprints() { - return Collections.unmodifiableMap(sshKeyFingerprints); + public List getActions() { + return Collections.unmodifiableList(actions); } /** - * Gets ssh keys. + * Gets api. * - * @return the ssh keys + * @return the api */ - public List getSshKeys() { - return Collections.unmodifiableList(sshKeys); + public List getApi() { + return Collections.unmodifiableList(api); } /** - * Gets hooks. + * Gets dependabot. * - * @return the hooks + * @return the dependabot */ - public List getHooks() { - return Collections.unmodifiableList(hooks); + public List getDependabot() { + return Collections.unmodifiableList(dependabot); } /** @@ -86,21 +77,30 @@ public List getGit() { } /** - * Gets web. + * Gets hooks. * - * @return the web + * @return the hooks */ - public List getWeb() { - return Collections.unmodifiableList(web); + public List getHooks() { + return Collections.unmodifiableList(hooks); } /** - * Gets api. + * Gets importer. * - * @return the api + * @return the importer */ - public List getApi() { - return Collections.unmodifiableList(api); + public List getImporter() { + return Collections.unmodifiableList(importer); + } + + /** + * Gets package. + * + * @return the package + */ + public List getPackages() { + return Collections.unmodifiableList(packages); } /** @@ -113,38 +113,38 @@ public List getPages() { } /** - * Gets importer. + * Gets ssh key fingerprints. * - * @return the importer + * @return the ssh key fingerprints */ - public List getImporter() { - return Collections.unmodifiableList(importer); + public Map getSshKeyFingerprints() { + return Collections.unmodifiableMap(sshKeyFingerprints); } /** - * Gets package. + * Gets ssh keys. * - * @return the package + * @return the ssh keys */ - public List getPackages() { - return Collections.unmodifiableList(packages); + public List getSshKeys() { + return Collections.unmodifiableList(sshKeys); } /** - * Gets actions. + * Gets web. * - * @return the actions + * @return the web */ - public List getActions() { - return Collections.unmodifiableList(actions); + public List getWeb() { + return Collections.unmodifiableList(web); } /** - * Gets dependabot. + * Is verifiable password authentication boolean. * - * @return the dependabot + * @return the boolean */ - public List getDependabot() { - return Collections.unmodifiableList(dependabot); + public boolean isVerifiablePasswordAuthentication() { + return verifiablePasswordAuthentication; } } diff --git a/src/main/java/org/kohsuke/github/GHMilestone.java b/src/main/java/org/kohsuke/github/GHMilestone.java index 3df476bb74..7cd556c8ee 100644 --- a/src/main/java/org/kohsuke/github/GHMilestone.java +++ b/src/main/java/org/kohsuke/github/GHMilestone.java @@ -1,9 +1,11 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.net.URL; +import java.time.Instant; import java.util.Date; import java.util.Locale; @@ -15,51 +17,41 @@ */ public class GHMilestone extends GHObject { - /** - * Create default GHMilestone instance - */ - public GHMilestone() { - } + private int closedIssues, openIssues, number; - /** The owner. */ - GHRepository owner; + private String state, dueOn, title, description, htmlUrl; + /** The closed at. */ + protected String closedAt; /** The creator. */ GHUser creator; - private String state, due_on, title, description, html_url; - private int closed_issues, open_issues, number; - - /** The closed at. */ - protected String closed_at; + /** The owner. */ + GHRepository owner; /** - * Gets owner. - * - * @return the owner + * Create default GHMilestone instance */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHRepository getOwner() { - return owner; + public GHMilestone() { } /** - * Gets creator. + * Closes this milestone. * - * @return the creator + * @throws IOException + * the io exception */ - public GHUser getCreator() { - return root().intern(creator); + public void close() throws IOException { + edit("state", "closed"); } /** - * Gets due on. + * Deletes this milestone. * - * @return the due on + * @throws IOException + * the io exception */ - public Date getDueOn() { - if (due_on == null) - return null; - return GitHubClient.parseDate(due_on); + public void delete() throws IOException { + root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); } /** @@ -67,17 +59,27 @@ public Date getDueOn() { * * @return the closed at */ - public Date getClosedAt() { - return GitHubClient.parseDate(closed_at); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getClosedAt() { + return GitHubClient.parseInstant(closedAt); } /** - * Gets title. + * Gets closed issues. * - * @return the title + * @return the closed issues */ - public String getTitle() { - return title; + public int getClosedIssues() { + return closedIssues; + } + + /** + * Gets creator. + * + * @return the creator + */ + public GHUser getCreator() { + return root().intern(creator); } /** @@ -90,21 +92,22 @@ public String getDescription() { } /** - * Gets closed issues. + * Gets due on. * - * @return the closed issues + * @return the due on */ - public int getClosedIssues() { - return closed_issues; + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getDueOn() { + return GitHubClient.parseInstant(dueOn); } /** - * Gets open issues. + * Gets the html url. * - * @return the open issues + * @return the html url */ - public int getOpenIssues() { - return open_issues; + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); } /** @@ -117,12 +120,22 @@ public int getNumber() { } /** - * Gets the html url. + * Gets open issues. * - * @return the html url + * @return the open issues */ - public URL getHtmlUrl() { - return GitHubClient.parseURL(html_url); + public int getOpenIssues() { + return openIssues; + } + + /** + * Gets owner. + * + * @return the owner + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHRepository getOwner() { + return owner; } /** @@ -135,13 +148,12 @@ public GHMilestoneState getState() { } /** - * Closes this milestone. + * Gets title. * - * @throws IOException - * the io exception + * @return the title */ - public void close() throws IOException { - edit("state", "closed"); + public String getTitle() { + return title; } /** @@ -155,53 +167,57 @@ public void reopen() throws IOException { } /** - * Deletes this milestone. + * Sets description. * + * @param description + * the description * @throws IOException * the io exception */ - public void delete() throws IOException { - root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); - } - - private void edit(String key, Object value) throws IOException { - root().createRequest().with(key, value).method("PATCH").withUrlPath(getApiRoute()).send(); + public void setDescription(String description) throws IOException { + edit("description", description); } /** - * Sets title. + * Sets due on. * - * @param title - * the title + * @param dueOn + * the due on * @throws IOException * the io exception + * @deprecated Use {@link #setDueOn(Instant)} */ - public void setTitle(String title) throws IOException { - edit("title", title); + @Deprecated + public void setDueOn(Date dueOn) throws IOException { + setDueOn(GitHubClient.toInstantOrNull(dueOn)); } /** - * Sets description. + * Sets due on. * - * @param description - * the description + * @param dueOn + * the due on * @throws IOException * the io exception */ - public void setDescription(String description) throws IOException { - edit("description", description); + public void setDueOn(Instant dueOn) throws IOException { + edit("due_on", GitHubClient.printInstant(dueOn)); } /** - * Sets due on. + * Sets title. * - * @param dueOn - * the due on + * @param title + * the title * @throws IOException * the io exception */ - public void setDueOn(Date dueOn) throws IOException { - edit("due_on", GitHubClient.printDate(dueOn)); + public void setTitle(String title) throws IOException { + edit("title", title); + } + + private void edit(String key, Object value) throws IOException { + root().createRequest().with(key, value).method("PATCH").withUrlPath(getApiRoute()).send(); } /** diff --git a/src/main/java/org/kohsuke/github/GHMilestoneState.java b/src/main/java/org/kohsuke/github/GHMilestoneState.java index 85dcb9d5f4..ab2030239d 100644 --- a/src/main/java/org/kohsuke/github/GHMilestoneState.java +++ b/src/main/java/org/kohsuke/github/GHMilestoneState.java @@ -8,8 +8,8 @@ */ public enum GHMilestoneState { - /** The open. */ - OPEN, /** The closed. */ - CLOSED + CLOSED, + /** The open. */ + OPEN } diff --git a/src/main/java/org/kohsuke/github/GHMyself.java b/src/main/java/org/kohsuke/github/GHMyself.java index 9d692b65c9..05e52cd5ac 100644 --- a/src/main/java/org/kohsuke/github/GHMyself.java +++ b/src/main/java/org/kohsuke/github/GHMyself.java @@ -17,12 +17,6 @@ */ public class GHMyself extends GHUser { - /** - * Create default GHMyself instance - */ - public GHMyself() { - } - /** * Type of repositories returned during listing. */ @@ -31,17 +25,92 @@ public enum RepositoryListFilter { /** All public and private repositories that current user has access or collaborates to. */ ALL, + /** Public and private repositories that current user is a member. */ + MEMBER, + /** Public and private repositories owned by current user. */ OWNER, - /** Public repositories that current user has access or collaborates to. */ - PUBLIC, - /** Private repositories that current user has access or collaborates to. */ PRIVATE, - /** Public and private repositories that current user is a member. */ - MEMBER; + /** Public repositories that current user has access or collaborates to. */ + PUBLIC; + } + + /** + * Create default GHMyself instance + */ + public GHMyself() { + } + + /** + * Add public SSH key for the user. + *

+ * https://docs.github.com/en/rest/users/keys?apiVersion=2022-11-28#create-a-public-ssh-key-for-the-authenticated-user + * + * @param title + * Title of the SSH key + * @param key + * the public key + * @return the newly created Github key + * @throws IOException + * the io exception + */ + public GHKey addPublicKey(String title, String key) throws IOException { + return root().createRequest() + .withUrlPath("/user/keys") + .method("POST") + .with("title", title) + .with("key", key) + .fetch(GHKey.class); + } + + /** + * Gets the organization that this user belongs to. + * + * @return the all organizations + * @throws IOException + * the io exception + */ + public GHPersonSet getAllOrganizations() throws IOException { + GHPersonSet orgs = new GHPersonSet(); + Set names = new HashSet(); + for (GHOrganization o : root().createRequest() + .withUrlPath("/user/orgs") + .toIterable(GHOrganization[].class, null) + .toArray()) { + if (names.add(o.getLogin())) // in case of rumoured duplicates in the data + orgs.add(root().getOrganization(o.getLogin())); + } + return orgs; + } + + /** + * Gets the all repositories this user owns (public and private). + * + * @return the all repositories + */ + public synchronized Map getAllRepositories() { + Map repositories = new TreeMap(); + for (GHRepository r : listRepositories()) { + repositories.put(r.getName(), r); + } + return Collections.unmodifiableMap(repositories); + } + + /** + * Lists installations of your GitHub App that the authenticated user has explicit permission to access. You must + * use a user-to-server OAuth access token, created for a user who has authorized your GitHub App, to access this + * endpoint. + * + * @return the paged iterable + * @see List + * app installations accessible to the user access token + */ + public PagedIterable getAppInstallations() { + return new GHAppInstallationsIterable(root()); } /** @@ -74,15 +143,19 @@ public List getEmails2() throws IOException { } /** - * Returns the read-only list of e-mail addresses configured for you. - *

- * This corresponds to the stuff you configure in https://github.com/settings/emails, and not to be confused with - * {@link #getEmail()} that shows your public e-mail address set in https://github.com/settings/profile + * Gets your membership in a specific organization. * - * @return Always non-null. + * @param o + * the o + * @return the membership + * @throws IOException + * the io exception */ - public PagedIterable listEmails() { - return root().createRequest().withUrlPath("/user/emails").toIterable(GHEmail[].class, null); + public GHMembership getMembership(GHOrganization o) throws IOException { + return root().createRequest() + .withUrlPath("/user/memberships/orgs/" + o.getLogin()) + .fetch(GHMembership.class) + .wrap(root()); } /** @@ -99,28 +172,6 @@ public List getPublicKeys() throws IOException { return root().createRequest().withUrlPath("/user/keys").toIterable(GHKey[].class, null).toList(); } - /** - * Add public SSH key for the user. - *

- * https://docs.github.com/en/rest/users/keys?apiVersion=2022-11-28#create-a-public-ssh-key-for-the-authenticated-user - * - * @param title - * Title of the SSH key - * @param key - * the public key - * @return the newly created Github key - * @throws IOException - * the io exception - */ - public GHKey addPublicKey(String title, String key) throws IOException { - return root().createRequest() - .withUrlPath("/user/keys") - .method("POST") - .with("title", title) - .with("key", key) - .fetch(GHKey.class); - } - /** * Returns the read-only list of all the public verified keys of the current user. *

@@ -139,36 +190,38 @@ public List getPublicVerifiedKeys() throws IOException { } /** - * Gets the organization that this user belongs to. + * Returns the read-only list of e-mail addresses configured for you. + *

+ * This corresponds to the stuff you configure in https://github.com/settings/emails, and not to be confused with + * {@link #getEmail()} that shows your public e-mail address set in https://github.com/settings/profile * - * @return the all organizations - * @throws IOException - * the io exception + * @return Always non-null. */ - public GHPersonSet getAllOrganizations() throws IOException { - GHPersonSet orgs = new GHPersonSet(); - Set names = new HashSet(); - for (GHOrganization o : root().createRequest() - .withUrlPath("/user/orgs") - .toIterable(GHOrganization[].class, null) - .toArray()) { - if (names.add(o.getLogin())) // in case of rumoured duplicates in the data - orgs.add(root().getOrganization(o.getLogin())); - } - return orgs; + public PagedIterable listEmails() { + return root().createRequest().withUrlPath("/user/emails").toIterable(GHEmail[].class, null); } /** - * Gets the all repositories this user owns (public and private). + * List your organization memberships. * - * @return the all repositories + * @return the paged iterable */ - public synchronized Map getAllRepositories() { - Map repositories = new TreeMap(); - for (GHRepository r : listRepositories()) { - repositories.put(r.getName(), r); - } - return Collections.unmodifiableMap(repositories); + public PagedIterable listOrgMemberships() { + return listOrgMemberships(null); + } + + /** + * List your organization memberships. + * + * @param state + * Filter by a specific state + * @return the paged iterable + */ + public PagedIterable listOrgMemberships(final GHMembership.State state) { + return root().createRequest() + .with("state", state) + .withUrlPath("/user/memberships/orgs") + .toIterable(GHMembership[].class, item -> item.wrap(root())); } /** @@ -202,6 +255,11 @@ public PagedIterable listRepositories(final int pageSize) { return listRepositories(pageSize, RepositoryListFilter.ALL); } + // public void addEmails(Collection emails) throws IOException { + //// new Requester(root,ApiVersion.V3).withCredential().to("/user/emails"); + // root.retrieveWithAuth3() + // } + /** * List repositories of a certain type that are accessible by current authenticated user using the specified page * size. @@ -219,62 +277,4 @@ public PagedIterable listRepositories(final int pageSize, final Re .toIterable(GHRepository[].class, null) .withPageSize(pageSize); } - - /** - * List your organization memberships. - * - * @return the paged iterable - */ - public PagedIterable listOrgMemberships() { - return listOrgMemberships(null); - } - - /** - * List your organization memberships. - * - * @param state - * Filter by a specific state - * @return the paged iterable - */ - public PagedIterable listOrgMemberships(final GHMembership.State state) { - return root().createRequest() - .with("state", state) - .withUrlPath("/user/memberships/orgs") - .toIterable(GHMembership[].class, item -> item.wrap(root())); - } - - /** - * Gets your membership in a specific organization. - * - * @param o - * the o - * @return the membership - * @throws IOException - * the io exception - */ - public GHMembership getMembership(GHOrganization o) throws IOException { - return root().createRequest() - .withUrlPath("/user/memberships/orgs/" + o.getLogin()) - .fetch(GHMembership.class) - .wrap(root()); - } - - // public void addEmails(Collection emails) throws IOException { - //// new Requester(root,ApiVersion.V3).withCredential().to("/user/emails"); - // root.retrieveWithAuth3() - // } - - /** - * Lists installations of your GitHub App that the authenticated user has explicit permission to access. You must - * use a user-to-server OAuth access token, created for a user who has authorized your GitHub App, to access this - * endpoint. - * - * @return the paged iterable - * @see List - * app installations accessible to the user access token - */ - public PagedIterable getAppInstallations() { - return new GHAppInstallationsIterable(root()); - } } diff --git a/src/main/java/org/kohsuke/github/GHNotificationStream.java b/src/main/java/org/kohsuke/github/GHNotificationStream.java index f769907125..269ddf972f 100644 --- a/src/main/java/org/kohsuke/github/GHNotificationStream.java +++ b/src/main/java/org/kohsuke/github/GHNotificationStream.java @@ -1,6 +1,7 @@ package org.kohsuke.github; import java.io.IOException; +import java.time.Instant; import java.util.Date; import java.util.Iterator; import java.util.NoSuchElementException; @@ -25,11 +26,13 @@ * @see GHRepository#listNotifications() GHRepository#listNotifications() */ public class GHNotificationStream extends GitHubInteractiveObject implements Iterable { + private static final GHThread[] EMPTY_ARRAY = new GHThread[0]; private Boolean all, participating; - private String since; private String apiUrl; private boolean nonBlocking = false; + private String since; + /** * Instantiates a new GH notification stream. * @@ -43,66 +46,6 @@ public class GHNotificationStream extends GitHubInteractiveObject implements Ite this.apiUrl = apiUrl; } - /** - * Should the stream include notifications that are already read?. - * - * @param v - * the v - * @return the gh notification stream - */ - public GHNotificationStream read(boolean v) { - all = v; - return this; - } - - /** - * Should the stream be restricted to notifications in which the user is directly participating or mentioned?. - * - * @param v - * the v - * @return the gh notification stream - */ - public GHNotificationStream participating(boolean v) { - participating = v; - return this; - } - - /** - * Since gh notification stream. - * - * @param timestamp - * the timestamp - * @return the gh notification stream - */ - public GHNotificationStream since(long timestamp) { - return since(new Date(timestamp)); - } - - /** - * Since gh notification stream. - * - * @param dt - * the dt - * @return the gh notification stream - */ - public GHNotificationStream since(Date dt) { - since = GitHubClient.printDate(dt); - return this; - } - - /** - * If set to true, {@link #iterator()} will stop iterating instead of blocking and waiting for the updates to - * arrive. - * - * @param v - * the v - * @return the gh notification stream - */ - public GHNotificationStream nonBlocking(boolean v) { - this.nonBlocking = v; - return this; - } - /** * Returns an infinite blocking {@link Iterator} that returns {@link GHThread} as notifications arrive. * @@ -117,31 +60,37 @@ public Iterator iterator() { return new Iterator() { /** - * Stuff we've fetched but haven't returned to the caller. Newer ones first. + * Next element in {@link #threads} to return. This counts down. */ - private GHThread[] threads = EMPTY_ARRAY; + private int idx = -1; /** - * Next element in {@link #threads} to return. This counts down. + * Next request should have "If-Modified-Since" header with this value. */ - private int idx = -1; + private String lastModified; /** * threads whose updated_at is older than this should be ignored. */ private long lastUpdated = -1; - /** - * Next request should have "If-Modified-Since" header with this value. - */ - private String lastModified; + private GHThread next; /** * When is the next polling allowed? */ private long nextCheckTime = -1; - private GHThread next; + /** + * Stuff we've fetched but haven't returned to the caller. Newer ones first. + */ + private GHThread[] threads = EMPTY_ARRAY; + + public boolean hasNext() { + if (next == null) + next = fetch(); + return next != null; + } public GHThread next() { if (next == null) { @@ -155,10 +104,12 @@ public GHThread next() { return r; } - public boolean hasNext() { - if (next == null) - next = fetch(); - return next != null; + private long calcNextCheckTime(GitHubResponse response) { + String v = response.header("X-Poll-Interval"); + if (v == null) + v = "60"; + long seconds = Integer.parseInt(v); + return System.currentTimeMillis() + seconds * 1000; } GHThread fetch() { @@ -168,7 +119,7 @@ GHThread fetch() { // if we have fetched un-returned threads, use them first while (idx >= 0) { GHThread n = threads[idx--]; - long nt = n.getUpdatedAt().getTime(); + long nt = n.getUpdatedAt().toEpochMilli(); if (nt >= lastUpdated) { lastUpdated = nt; return n; @@ -211,14 +162,6 @@ GHThread fetch() { throw new RuntimeException(e); } } - - private long calcNextCheckTime(GitHubResponse response) { - String v = response.header("X-Poll-Interval"); - if (v == null) - v = "60"; - long seconds = Integer.parseInt(v); - return System.currentTimeMillis() + seconds * 1000; - } }; } @@ -243,9 +186,80 @@ public void markAsRead() throws IOException { public void markAsRead(long timestamp) throws IOException { final Requester req = root().createRequest(); if (timestamp >= 0) - req.with("last_read_at", GitHubClient.printDate(new Date(timestamp))); + req.with("last_read_at", GitHubClient.printInstant(Instant.ofEpochMilli(timestamp))); req.withUrlPath(apiUrl).fetchHttpStatusCode(); } - private static final GHThread[] EMPTY_ARRAY = new GHThread[0]; + /** + * If set to true, {@link #iterator()} will stop iterating instead of blocking and waiting for the updates to + * arrive. + * + * @param v + * the v + * @return the gh notification stream + */ + public GHNotificationStream nonBlocking(boolean v) { + this.nonBlocking = v; + return this; + } + + /** + * Should the stream be restricted to notifications in which the user is directly participating or mentioned?. + * + * @param v + * the v + * @return the gh notification stream + */ + public GHNotificationStream participating(boolean v) { + participating = v; + return this; + } + + /** + * Should the stream include notifications that are already read?. + * + * @param v + * the v + * @return the gh notification stream + */ + public GHNotificationStream read(boolean v) { + all = v; + return this; + } + + /** + * Since gh notification stream. + * + * @param dt + * the dt + * @return the gh notification stream + * @deprecated {@link #since(Instant)} + */ + @Deprecated + public GHNotificationStream since(Date dt) { + return since(GitHubClient.toInstantOrNull(dt)); + } + + /** + * Since gh notification stream. + * + * @param dt + * the dt + * @return the gh notification stream + */ + public GHNotificationStream since(Instant dt) { + since = GitHubClient.printInstant(dt); + return this; + } + + /** + * Since gh notification stream. + * + * @param timestamp + * the timestamp + * @return the gh notification stream + */ + public GHNotificationStream since(long timestamp) { + return since(new Date(timestamp)); + } } diff --git a/src/main/java/org/kohsuke/github/GHObject.java b/src/main/java/org/kohsuke/github/GHObject.java index 9d9b2f4fe0..0545758887 100644 --- a/src/main/java/org/kohsuke/github/GHObject.java +++ b/src/main/java/org/kohsuke/github/GHObject.java @@ -1,6 +1,7 @@ package org.kohsuke.github; import com.fasterxml.jackson.annotation.JacksonInject; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -9,6 +10,7 @@ import java.io.IOException; import java.lang.reflect.Field; import java.net.URL; +import java.time.Instant; import java.util.Date; import java.util.List; import java.util.Map; @@ -22,17 +24,38 @@ @SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, justification = "JSON API") public abstract class GHObject extends GitHubInteractiveObject { - /** - * Capture response HTTP headers on the state object. - */ - protected transient Map> responseHeaderFields; + private static final ToStringStyle TOSTRING_STYLE = new ToStringStyle() { + { + this.setUseShortClassName(true); + } - private String url; + @Override + public void append(StringBuffer buffer, String fieldName, Object value, Boolean fullDetail) { + // skip unimportant properties. '_' is a heuristics as important properties tend to have short names + if (fieldName.contains("_")) + return; + // avoid recursing other GHObject + if (value instanceof GHObject) + return; + // likewise no point in showing root + if (value instanceof GitHub) + return; + + super.append(buffer, fieldName, value, fullDetail); + } + }; + + private String createdAt; private long id; private String nodeId; - private String createdAt; private String updatedAt; + private String url; + + /** + * Capture response HTTP headers on the state object. + */ + protected transient Map> responseHeaderFields; /** * Instantiates a new GH object. @@ -41,16 +64,34 @@ public abstract class GHObject extends GitHubInteractiveObject { } /** - * Called by Jackson. + * When was this resource created?. * - * @param connectorResponse - * the {@link GitHubConnectorResponse} to get headers from. + * @return date created + * @throws IOException + * on error */ - @JacksonInject - protected void setResponseHeaderFields(@CheckForNull GitHubConnectorResponse connectorResponse) { - if (connectorResponse != null) { - responseHeaderFields = connectorResponse.allHeaders(); - } + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getCreatedAt() throws IOException { + return GitHubClient.parseInstant(createdAt); + } + + /** + * Gets id. + * + * @return Unique ID number of this resource. + */ + public long getId() { + return id; + } + + /** + * Get Global node_id from Github object. + * + * @return Global Node ID. + * @see Using Global Node IDs + */ + public String getNodeId() { + return nodeId; } /** @@ -72,14 +113,15 @@ public Map> getResponseHeaderFields() { } /** - * When was this resource created?. + * When was this resource last updated?. * - * @return date created + * @return updated date * @throws IOException * on error */ - public Date getCreatedAt() throws IOException { - return GitHubClient.parseDate(createdAt); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getUpdatedAt() throws IOException { + return GitHubClient.parseInstant(updatedAt); } /** @@ -91,36 +133,6 @@ public URL getUrl() { return GitHubClient.parseURL(url); } - /** - * When was this resource last updated?. - * - * @return updated date - * @throws IOException - * on error - */ - public Date getUpdatedAt() throws IOException { - return GitHubClient.parseDate(updatedAt); - } - - /** - * Get Global node_id from Github object. - * - * @return Global Node ID. - * @see Using Global Node IDs - */ - public String getNodeId() { - return nodeId; - } - - /** - * Gets id. - * - * @return Unique ID number of this resource. - */ - public long getId() { - return id; - } - /** * String representation to assist debugging and inspection. The output format of this string is not a committed * part of the API and is subject to change. @@ -137,24 +149,16 @@ protected boolean accept(Field field) { }.toString(); } - private static final ToStringStyle TOSTRING_STYLE = new ToStringStyle() { - { - this.setUseShortClassName(true); - } - - @Override - public void append(StringBuffer buffer, String fieldName, Object value, Boolean fullDetail) { - // skip unimportant properties. '_' is a heuristics as important properties tend to have short names - if (fieldName.contains("_")) - return; - // avoid recursing other GHObject - if (value instanceof GHObject) - return; - // likewise no point in showing root - if (value instanceof GitHub) - return; - - super.append(buffer, fieldName, value, fullDetail); + /** + * Called by Jackson. + * + * @param connectorResponse + * the {@link GitHubConnectorResponse} to get headers from. + */ + @JacksonInject + protected void setResponseHeaderFields(@CheckForNull GitHubConnectorResponse connectorResponse) { + if (connectorResponse != null) { + responseHeaderFields = connectorResponse.allHeaders(); } - }; + } } diff --git a/src/main/java/org/kohsuke/github/GHOrgHook.java b/src/main/java/org/kohsuke/github/GHOrgHook.java index 485e1c47a4..a483ec51ca 100644 --- a/src/main/java/org/kohsuke/github/GHOrgHook.java +++ b/src/main/java/org/kohsuke/github/GHOrgHook.java @@ -15,15 +15,13 @@ class GHOrgHook extends GHHook { transient GHOrganization organization; /** - * Wrap. + * Gets the api route. * - * @param owner - * the owner - * @return the GH org hook + * @return the api route */ - GHOrgHook wrap(GHOrganization owner) { - this.organization = owner; - return this; + @Override + String getApiRoute() { + return String.format("/orgs/%s/hooks/%d", organization.getLogin(), getId()); } /** @@ -37,12 +35,14 @@ GitHub root() { } /** - * Gets the api route. + * Wrap. * - * @return the api route + * @param owner + * the owner + * @return the GH org hook */ - @Override - String getApiRoute() { - return String.format("/orgs/%s/hooks/%d", organization.getLogin(), getId()); + GHOrgHook wrap(GHOrganization owner) { + this.organization = owner; + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHOrganization.java b/src/main/java/org/kohsuke/github/GHOrganization.java index ec434595aa..f86b990084 100644 --- a/src/main/java/org/kohsuke/github/GHOrganization.java +++ b/src/main/java/org/kohsuke/github/GHOrganization.java @@ -18,128 +18,252 @@ */ public class GHOrganization extends GHPerson { + /** + * The enum Permission. + * + * @see RepositoryRole + */ + public enum Permission { + + /** The admin. */ + ADMIN, + /** The maintain. */ + MAINTAIN, + /** The pull. */ + PULL, + /** The push. */ + PUSH, + /** The triage. */ + TRIAGE, + /** Unknown, before we add the new permission to the enum */ + UNKNOWN + } + + /** + * Repository permissions (roles) for teams and collaborators. + */ + public static class RepositoryRole { + /** + * Custom. + * + * @param permission + * the permission + * @return the repository role + */ + public static RepositoryRole custom(String permission) { + return new RepositoryRole(permission); + } + + /** + * From. + * + * @param permission + * the permission + * @return the repository role + */ + public static RepositoryRole from(Permission permission) { + return custom(permission.toString().toLowerCase()); + } + + private final String permission; + + private RepositoryRole(String permission) { + this.permission = permission; + } + + /** + * To string. + * + * @return the string + */ + @Override + public String toString() { + return permission; + } + } + + /** + * Member's role in an organization. + */ + public enum Role { + + /** The admin. */ + ADMIN, + /** The user is an owner of the organization. */ + MEMBER /** The user is a non-owner member of the organization. */ + } + + private boolean hasOrganizationProjects; + /** * Create default GHOrganization instance */ public GHOrganization() { } - private boolean has_organization_projects; + /** + * Adds (invites) a user to the organization. + * + * @param user + * the user + * @param role + * the role + * @throws IOException + * the io exception + * @see documentation + */ + public void add(GHUser user, Role role) throws IOException { + root().createRequest() + .method("PUT") + .with("role", role.name().toLowerCase()) + .withUrlPath("/orgs/" + login + "/memberships/" + user.getLogin()) + .send(); + } /** - * Starts a builder that creates a new repository. - *

- * You use the returned builder to set various properties, then call {@link GHCreateRepositoryBuilder#create()} to - * finally create a repository. + * Are projects enabled for organization boolean. * - * @param name - * the name - * @return the gh create repository builder + * @return the boolean */ - public GHCreateRepositoryBuilder createRepository(String name) { - return new GHCreateRepositoryBuilder(name, root(), "/orgs/" + login + "/repos"); + public boolean areOrganizationProjectsEnabled() { + return hasOrganizationProjects; } /** - * Teams by their names. + * Conceals the membership. * - * @return the teams + * @param u + * the u + * @throws IOException + * the io exception */ - public Map getTeams() { - Map r = new TreeMap(); - for (GHTeam t : listTeams()) { - r.put(t.getName(), t); - } - return r; + public void conceal(GHUser u) throws IOException { + root().createRequest() + .method("DELETE") + .withUrlPath("/orgs/" + login + "/public_members/" + u.getLogin()) + .send(); } /** - * List up all the teams. + * See https://api.github.com/hooks for possible names and their configuration scheme. TODO: produce type-safe + * binding * - * @return the paged iterable + * @param name + * Type of the hook to be created. See https://api.github.com/hooks for possible names. + * @param config + * The configuration hash. + * @param events + * Can be null. Types of events to hook into. + * @param active + * the active + * @return the gh hook + * @throws IOException + * the io exception */ - public PagedIterable listTeams() { - return root().createRequest() - .withUrlPath(String.format("/orgs/%s/teams", login)) - .toIterable(GHTeam[].class, item -> item.wrapUp(this)); + public GHHook createHook(String name, Map config, Collection events, boolean active) + throws IOException { + return GHHooks.orgContext(this).createHook(name, config, events, active); } /** - * Gets a single team by ID. + * Creates a project for the organization. * - * @param teamId - * id of the team that we want to query for - * @return the team + * @param name + * the name + * @param body + * the body + * @return the gh project * @throws IOException * the io exception - * @see documentation */ - public GHTeam getTeam(long teamId) throws IOException { + public GHProject createProject(String name, String body) throws IOException { return root().createRequest() - .withUrlPath(String.format("/organizations/%d/team/%d", getId(), teamId)) - .fetch(GHTeam.class) - .wrapUp(this); + .method("POST") + .with("name", name) + .with("body", body) + .withUrlPath(String.format("/orgs/%s/projects", login)) + .fetch(GHProject.class); } /** - * Finds a team that has the given name in its {@link GHTeam#getName()}. + * Starts a builder that creates a new repository. + *

+ * You use the returned builder to set various properties, then call {@link GHCreateRepositoryBuilder#create()} to + * finally create a repository. * * @param name * the name - * @return the team by name + * @return the gh create repository builder */ - public GHTeam getTeamByName(String name) { - for (GHTeam t : listTeams()) { - if (t.getName().equals(name)) - return t; - } - return null; + public GHCreateRepositoryBuilder createRepository(String name) { + return new GHCreateRepositoryBuilder(name, root(), "/orgs/" + login + "/repos"); } /** - * Finds a team that has the given slug in its {@link GHTeam#getSlug()}. + * Starts a builder that creates a new team. + *

+ * You use the returned builder to set various properties, then call {@link GHTeamBuilder#create()} to finally + * create a team. * - * @param slug - * the slug - * @return the team by slug + * @param name + * the name + * @return the gh create repository builder + */ + public GHTeamBuilder createTeam(String name) { + return new GHTeamBuilder(root(), login, name); + } + + /** + * Create web hook gh hook. + * + * @param url + * the url + * @return the gh hook * @throws IOException * the io exception - * @see documentation */ - public GHTeam getTeamBySlug(String slug) throws IOException { - return root().createRequest() - .withUrlPath(String.format("/orgs/%s/teams/%s", login, slug)) - .fetch(GHTeam.class) - .wrapUp(this); + public GHHook createWebHook(URL url) throws IOException { + return createWebHook(url, null); } /** - * List up all the external groups. + * Create web hook gh hook. * - * @return the paged iterable - * @see documentation + * @param url + * the url + * @param events + * the events + * @return the gh hook + * @throws IOException + * the io exception */ - public PagedIterable listExternalGroups() { - return listExternalGroups(null); + public GHHook createWebHook(URL url, Collection events) throws IOException { + return createHook("web", Collections.singletonMap("url", url.toExternalForm()), events, true); } /** - * List up all the external groups with a given text in their name + * Deletes hook. * - * @param displayName - * the text that must be part of the returned groups name - * @return the paged iterable - * @see documentation + * @param id + * the id + * @throws IOException + * the io exception */ - public PagedIterable listExternalGroups(final String displayName) { - final Requester requester = root().createRequest() - .withUrlPath(String.format("/orgs/%s/external-groups", login)); - if (displayName != null) { - requester.with("display_name", displayName); - } - return new GHExternalGroupIterable(this, requester); + public void deleteHook(int id) throws IOException { + GHHooks.orgContext(this).deleteHook(id); + } + + /** + * Sets organization projects enabled status boolean. + * + * @param newStatus + * enable status + * @throws IOException + * the io exception + */ + public void enableOrganizationProjects(boolean newStatus) throws IOException { + edit("has_organization_projects", newStatus); } /** @@ -167,50 +291,27 @@ public GHExternalGroup getExternalGroup(final long groupId) throws IOException { } /** - * Member's role in an organization. - */ - public enum Role { - - /** The admin. */ - ADMIN, - /** The user is an owner of the organization. */ - MEMBER /** The user is a non-owner member of the organization. */ - } - - /** - * Adds (invites) a user to the organization. + * Gets hook. * - * @param user - * the user - * @param role - * the role + * @param id + * the id + * @return the hook * @throws IOException * the io exception - * @see documentation */ - public void add(GHUser user, Role role) throws IOException { - root().createRequest() - .method("PUT") - .with("role", role.name().toLowerCase()) - .withUrlPath("/orgs/" + login + "/memberships/" + user.getLogin()) - .send(); + public GHHook getHook(int id) throws IOException { + return GHHooks.orgContext(this).getHook(id); } /** - * Checks if this organization has the specified user as a member. + * Retrieves the currently configured hooks. * - * @param user - * the user - * @return the boolean + * @return the hooks + * @throws IOException + * the io exception */ - public boolean hasMember(GHUser user) { - try { - root().createRequest().withUrlPath("/orgs/" + login + "/members/" + user.getLogin()).send(); - return true; - } catch (IOException ignore) { - return false; - } + public List getHooks() throws IOException { + return GHHooks.orgContext(this).getHooks(); } /** @@ -234,341 +335,258 @@ public GHMembership getMembership(String username) throws IOException { } /** - * Remove a member of the organisation - which will remove them from all teams, and remove their access to the - * organization’s repositories. + * Gets all the open pull requests in this organization. * - * @param user - * the user + * @return the pull requests * @throws IOException * the io exception */ - public void remove(GHUser user) throws IOException { - root().createRequest().method("DELETE").withUrlPath("/orgs/" + login + "/members/" + user.getLogin()).send(); + public List getPullRequests() throws IOException { + List all = new ArrayList(); + for (GHRepository r : getRepositoriesWithOpenPullRequests()) { + all.addAll(r.queryPullRequests().state(GHIssueState.OPEN).list().toList()); + } + return all; } /** - * Checks if this organization has the specified user as a public member. + * List repositories that has some open pull requests. + *

+ * This used to be an efficient method that didn't involve traversing every repository, but now it doesn't do any + * optimization. * - * @param user - * the user - * @return the boolean + * @return the repositories with open pull requests + * @throws IOException + * the io exception */ - public boolean hasPublicMember(GHUser user) { - try { - root().createRequest().withUrlPath("/orgs/" + login + "/public_members/" + user.getLogin()).send(); - return true; - } catch (IOException ignore) { - return false; + public List getRepositoriesWithOpenPullRequests() throws IOException { + List r = new ArrayList(); + for (GHRepository repository : listRepositories().withPageSize(100)) { + List pullRequests = repository.queryPullRequests().state(GHIssueState.OPEN).list().toList(); + if (pullRequests.size() > 0) { + r.add(repository); + } } + return r; } /** - * Publicizes the membership. + * Gets a single team by ID. * - * @param u - * the u + * @param teamId + * id of the team that we want to query for + * @return the team * @throws IOException * the io exception + * @see documentation */ - public void publicize(GHUser u) throws IOException { - root().createRequest().method("PUT").withUrlPath("/orgs/" + login + "/public_members/" + u.getLogin()).send(); + public GHTeam getTeam(long teamId) throws IOException { + return root().createRequest() + .withUrlPath(String.format("/organizations/%d/team/%d", getId(), teamId)) + .fetch(GHTeam.class) + .wrapUp(this); } /** - * All the members of this organization. + * Finds a team that has the given name in its {@link GHTeam#getName()}. * - * @return the paged iterable + * @param name + * the name + * @return the team by name */ - public PagedIterable listMembers() { - return listMembers("members"); + public GHTeam getTeamByName(String name) { + for (GHTeam t : listTeams()) { + if (t.getName().equals(name)) + return t; + } + return null; } /** - * All the public members of this organization. + * Finds a team that has the given slug in its {@link GHTeam#getSlug()}. * - * @return the paged iterable + * @param slug + * the slug + * @return the team by slug + * @throws IOException + * the io exception + * @see documentation */ - public PagedIterable listPublicMembers() { - return listMembers("public_members"); + public GHTeam getTeamBySlug(String slug) throws IOException { + return root().createRequest() + .withUrlPath(String.format("/orgs/%s/teams/%s", login, slug)) + .fetch(GHTeam.class) + .wrapUp(this); } /** - * All the outside collaborators of this organization. + * Teams by their names. * - * @return the paged iterable + * @return the teams */ - public PagedIterable listOutsideCollaborators() { - return listMembers("outside_collaborators"); - } - - private PagedIterable listMembers(String suffix) { - return listMembers(suffix, null, null); + public Map getTeams() { + Map r = new TreeMap(); + for (GHTeam t : listTeams()) { + r.put(t.getName(), t); + } + return r; } /** - * List members with filter paged iterable. + * Checks if this organization has the specified user as a member. * - * @param filter - * the filter - * @return the paged iterable + * @param user + * the user + * @return the boolean */ - public PagedIterable listMembersWithFilter(String filter) { - return listMembers("members", filter, null); + public boolean hasMember(GHUser user) { + try { + root().createRequest().withUrlPath("/orgs/" + login + "/members/" + user.getLogin()).send(); + return true; + } catch (IOException ignore) { + return false; + } } /** - * List outside collaborators with filter paged iterable. + * Checks if this organization has the specified user as a public member. * - * @param filter - * the filter - * @return the paged iterable + * @param user + * the user + * @return the boolean */ - public PagedIterable listOutsideCollaboratorsWithFilter(String filter) { - return listMembers("outside_collaborators", filter, null); + public boolean hasPublicMember(GHUser user) { + try { + root().createRequest().withUrlPath("/orgs/" + login + "/public_members/" + user.getLogin()).send(); + return true; + } catch (IOException ignore) { + return false; + } } /** - * List members with specified role paged iterable. + * Lists events performed by a user (this includes private events if the caller is authenticated. * - * @param role - * the role * @return the paged iterable + * @throws IOException + * Signals that an I/O exception has occurred. */ - public PagedIterable listMembersWithRole(String role) { - return listMembers("members", null, role); - } - - private PagedIterable listMembers(final String suffix, final String filter, String role) { + public PagedIterable listEvents() throws IOException { return root().createRequest() - .withUrlPath(String.format("/orgs/%s/%s", login, suffix)) - .with("filter", filter) - .with("role", role) - .toIterable(GHUser[].class, null); + .withUrlPath(String.format("/orgs/%s/events", login)) + .toIterable(GHEventInfo[].class, null); } /** - * List up all the security managers. + * List up all the external groups. * * @return the paged iterable + * @see documentation */ - public PagedIterable listSecurityManagers() { - return root().createRequest() - .withUrlPath(String.format("/orgs/%s/security-managers", login)) - .toIterable(GHTeam[].class, item -> item.wrapUp(this)); - } - - /** - * Conceals the membership. - * - * @param u - * the u - * @throws IOException - * the io exception - */ - public void conceal(GHUser u) throws IOException { - root().createRequest() - .method("DELETE") - .withUrlPath("/orgs/" + login + "/public_members/" + u.getLogin()) - .send(); - } - - /** - * Are projects enabled for organization boolean. - * - * @return the boolean - */ - public boolean areOrganizationProjectsEnabled() { - return has_organization_projects; - } - - /** - * Sets organization projects enabled status boolean. - * - * @param newStatus - * enable status - * @throws IOException - * the io exception - */ - public void enableOrganizationProjects(boolean newStatus) throws IOException { - edit("has_organization_projects", newStatus); - } - - private void edit(String key, Object value) throws IOException { - root().createRequest() - .withUrlPath(String.format("/orgs/%s", login)) - .method("PATCH") - .with(key, value) - .fetchInto(this); + public PagedIterable listExternalGroups() { + return listExternalGroups(null); } /** - * Returns the projects for this organization. + * List up all the external groups with a given text in their name * - * @param status - * The status filter (all, open or closed). + * @param displayName + * the text that must be part of the returned groups name * @return the paged iterable + * @see documentation */ - public PagedIterable listProjects(final GHProject.ProjectStateFilter status) { - return root().createRequest() - .with("state", status) - .withUrlPath(String.format("/orgs/%s/projects", login)) - .toIterable(GHProject[].class, null); + public PagedIterable listExternalGroups(final String displayName) { + final Requester requester = root().createRequest() + .withUrlPath(String.format("/orgs/%s/external-groups", login)); + if (displayName != null) { + requester.with("display_name", displayName); + } + return new GHExternalGroupIterable(this, requester); } /** - * Returns all open projects for the organization. + * All the members of this organization. * * @return the paged iterable */ - public PagedIterable listProjects() { - return listProjects(GHProject.ProjectStateFilter.OPEN); + public PagedIterable listMembers() { + return listMembers("members"); } /** - * Creates a project for the organization. + * List members with filter paged iterable. * - * @param name - * the name - * @param body - * the body - * @return the gh project - * @throws IOException - * the io exception + * @param filter + * the filter + * @return the paged iterable */ - public GHProject createProject(String name, String body) throws IOException { - return root().createRequest() - .method("POST") - .with("name", name) - .with("body", body) - .withUrlPath(String.format("/orgs/%s/projects", login)) - .fetch(GHProject.class); + public PagedIterable listMembersWithFilter(String filter) { + return listMembers("members", filter, null); } /** - * The enum Permission. + * List members with specified role paged iterable. * - * @see RepositoryRole + * @param role + * the role + * @return the paged iterable */ - public enum Permission { - - /** The admin. */ - ADMIN, - /** The maintain. */ - MAINTAIN, - /** The push. */ - PUSH, - /** The triage. */ - TRIAGE, - /** The pull. */ - PULL, - /** Unknown, before we add the new permission to the enum */ - UNKNOWN + public PagedIterable listMembersWithRole(String role) { + return listMembers("members", null, role); } /** - * Repository permissions (roles) for teams and collaborators. + * All the outside collaborators of this organization. + * + * @return the paged iterable */ - public static class RepositoryRole { - private final String permission; - - private RepositoryRole(String permission) { - this.permission = permission; - } - - /** - * Custom. - * - * @param permission - * the permission - * @return the repository role - */ - public static RepositoryRole custom(String permission) { - return new RepositoryRole(permission); - } - - /** - * From. - * - * @param permission - * the permission - * @return the repository role - */ - public static RepositoryRole from(Permission permission) { - return custom(permission.toString().toLowerCase()); - } - - /** - * To string. - * - * @return the string - */ - @Override - public String toString() { - return permission; - } + public PagedIterable listOutsideCollaborators() { + return listMembers("outside_collaborators"); } /** - * Starts a builder that creates a new team. - *

- * You use the returned builder to set various properties, then call {@link GHTeamBuilder#create()} to finally - * create a team. + * List outside collaborators with filter paged iterable. * - * @param name - * the name - * @return the gh create repository builder + * @param filter + * the filter + * @return the paged iterable */ - public GHTeamBuilder createTeam(String name) { - return new GHTeamBuilder(root(), login, name); + public PagedIterable listOutsideCollaboratorsWithFilter(String filter) { + return listMembers("outside_collaborators", filter, null); } /** - * List repositories that has some open pull requests. - *

- * This used to be an efficient method that didn't involve traversing every repository, but now it doesn't do any - * optimization. + * Returns all open projects for the organization. * - * @return the repositories with open pull requests - * @throws IOException - * the io exception + * @return the paged iterable */ - public List getRepositoriesWithOpenPullRequests() throws IOException { - List r = new ArrayList(); - for (GHRepository repository : listRepositories().withPageSize(100)) { - List pullRequests = repository.queryPullRequests().state(GHIssueState.OPEN).list().toList(); - if (pullRequests.size() > 0) { - r.add(repository); - } - } - return r; + public PagedIterable listProjects() { + return listProjects(GHProject.ProjectStateFilter.OPEN); } /** - * Gets all the open pull requests in this organization. + * Returns the projects for this organization. * - * @return the pull requests - * @throws IOException - * the io exception + * @param status + * The status filter (all, open or closed). + * @return the paged iterable */ - public List getPullRequests() throws IOException { - List all = new ArrayList(); - for (GHRepository r : getRepositoriesWithOpenPullRequests()) { - all.addAll(r.queryPullRequests().state(GHIssueState.OPEN).list().toList()); - } - return all; + public PagedIterable listProjects(final GHProject.ProjectStateFilter status) { + return root().createRequest() + .with("state", status) + .withUrlPath(String.format("/orgs/%s/projects", login)) + .toIterable(GHProject[].class, null); } /** - * Lists events performed by a user (this includes private events if the caller is authenticated. + * All the public members of this organization. * * @return the paged iterable - * @throws IOException - * Signals that an I/O exception has occurred. */ - public PagedIterable listEvents() throws IOException { - return root().createRequest() - .withUrlPath(String.format("/orgs/%s/events", login)) - .toIterable(GHEventInfo[].class, null); + public PagedIterable listPublicMembers() { + return listMembers("public_members"); } /** @@ -585,87 +603,69 @@ public PagedIterable listRepositories() { } /** - * Retrieves the currently configured hooks. + * List up all the security managers. * - * @return the hooks - * @throws IOException - * the io exception + * @return the paged iterable */ - public List getHooks() throws IOException { - return GHHooks.orgContext(this).getHooks(); + public PagedIterable listSecurityManagers() { + return root().createRequest() + .withUrlPath(String.format("/orgs/%s/security-managers", login)) + .toIterable(GHTeam[].class, item -> item.wrapUp(this)); } /** - * Gets hook. + * List up all the teams. * - * @param id - * the id - * @return the hook - * @throws IOException - * the io exception + * @return the paged iterable */ - public GHHook getHook(int id) throws IOException { - return GHHooks.orgContext(this).getHook(id); + public PagedIterable listTeams() { + return root().createRequest() + .withUrlPath(String.format("/orgs/%s/teams", login)) + .toIterable(GHTeam[].class, item -> item.wrapUp(this)); } /** - * Deletes hook. + * Publicizes the membership. * - * @param id - * the id + * @param u + * the u * @throws IOException * the io exception */ - public void deleteHook(int id) throws IOException { - GHHooks.orgContext(this).deleteHook(id); + public void publicize(GHUser u) throws IOException { + root().createRequest().method("PUT").withUrlPath("/orgs/" + login + "/public_members/" + u.getLogin()).send(); } /** - * See https://api.github.com/hooks for possible names and their configuration scheme. TODO: produce type-safe - * binding + * Remove a member of the organisation - which will remove them from all teams, and remove their access to the + * organization’s repositories. * - * @param name - * Type of the hook to be created. See https://api.github.com/hooks for possible names. - * @param config - * The configuration hash. - * @param events - * Can be null. Types of events to hook into. - * @param active - * the active - * @return the gh hook + * @param user + * the user * @throws IOException * the io exception */ - public GHHook createHook(String name, Map config, Collection events, boolean active) - throws IOException { - return GHHooks.orgContext(this).createHook(name, config, events, active); + public void remove(GHUser user) throws IOException { + root().createRequest().method("DELETE").withUrlPath("/orgs/" + login + "/members/" + user.getLogin()).send(); } - /** - * Create web hook gh hook. - * - * @param url - * the url - * @param events - * the events - * @return the gh hook - * @throws IOException - * the io exception - */ - public GHHook createWebHook(URL url, Collection events) throws IOException { - return createHook("web", Collections.singletonMap("url", url.toExternalForm()), events, true); + private void edit(String key, Object value) throws IOException { + root().createRequest() + .withUrlPath(String.format("/orgs/%s", login)) + .method("PATCH") + .with(key, value) + .fetchInto(this); } - /** - * Create web hook gh hook. - * - * @param url - * the url - * @return the gh hook - * @throws IOException - * the io exception - */ - public GHHook createWebHook(URL url) throws IOException { - return createWebHook(url, null); + private PagedIterable listMembers(String suffix) { + return listMembers(suffix, null, null); + } + + private PagedIterable listMembers(final String suffix, final String filter, String role) { + return root().createRequest() + .withUrlPath(String.format("/orgs/%s/%s", login, suffix)) + .with("filter", filter) + .with("role", role) + .toIterable(GHUser[].class, null); } } diff --git a/src/main/java/org/kohsuke/github/GHPermissionType.java b/src/main/java/org/kohsuke/github/GHPermissionType.java index 8dc9ca1a14..2efd179a18 100644 --- a/src/main/java/org/kohsuke/github/GHPermissionType.java +++ b/src/main/java/org/kohsuke/github/GHPermissionType.java @@ -10,14 +10,14 @@ public enum GHPermissionType { /** The admin. */ ADMIN(30), - /** The write. */ - WRITE(20), - /** The read. */ - READ(10), /** The none. */ NONE(0), + /** The read. */ + READ(10), /** The unknown permission type returned when an unrecognized permission type is returned. */ - UNKNOWN(-5); + UNKNOWN(-5), + /** The write. */ + WRITE(20); private final int level; diff --git a/src/main/java/org/kohsuke/github/GHPerson.java b/src/main/java/org/kohsuke/github/GHPerson.java index 1b87779a6b..52c0557e4e 100644 --- a/src/main/java/org/kohsuke/github/GHPerson.java +++ b/src/main/java/org/kohsuke/github/GHPerson.java @@ -1,8 +1,11 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; + import java.io.FileNotFoundException; import java.io.IOException; import java.net.URL; +import java.time.Instant; import java.util.Collections; import java.util.Date; import java.util.Map; @@ -17,164 +20,122 @@ */ public abstract class GHPerson extends GHObject { - /** - * Create default GHPerson instance - */ - public GHPerson() { - } + /** The public gists. */ + protected int followers, following, publicRepos, publicGists; - /** The avatar url. */ - // core data fields that exist even for "small" user data (such as the user info in pull request) - protected String login, avatar_url; + /** The html url. */ + protected String htmlUrl; /** The twitter username. */ // other fields (that only show up in full data) - protected String location, blog, email, bio, name, company, type, twitter_username; + protected String location, blog, email, bio, name, company, type, twitterUsername; - /** The html url. */ - protected String html_url; - - /** The public gists. */ - protected int followers, following, public_repos, public_gists; + /** The avatar url. */ + // core data fields that exist even for "small" user data (such as the user info in pull request) + protected String login, avatarUrl; /** The hireable. */ - protected boolean site_admin, hireable; + protected boolean siteAdmin, hireable; /** The total private repos. */ // other fields (that only show up in full data) that require privileged scope - protected Integer total_private_repos; + protected Integer totalPrivateRepos; /** - * Fully populate the data by retrieving missing data. - *

- * Depending on the original API call where this object is created, it may not contain everything. - * - * @throws IOException - * the io exception + * Create default GHPerson instance */ - protected synchronized void populate() throws IOException { - if (super.getCreatedAt() != null) { - return; // already populated - } - if (isOffline()) { - return; // cannot populate, will have to live with what we have - } - URL url = getUrl(); - if (url != null) { - root().createRequest().setRawUrlPath(url.toString()).fetchInto(this); - } + public GHPerson() { } /** - * Gets the public repositories this user owns. - * - *

- * To list your own repositories, including private repositories, use {@link GHMyself#listRepositories()} + * Returns a string of the avatar image URL. * - * @return the repositories + * @return the avatar url */ - public synchronized Map getRepositories() { - Map repositories = new TreeMap(); - for (GHRepository r : listRepositories().withPageSize(100)) { - repositories.put(r.getName(), r); - } - return Collections.unmodifiableMap(repositories); + public String getAvatarUrl() { + return avatarUrl; } /** - * List all the repositories using a default of 30 items page size. - *

- * Unlike {@link #getRepositories()}, this does not wait until all the repositories are returned. + * Gets the blog URL of this user. * - * @return the paged iterable + * @return the blog + * @throws IOException + * the io exception */ - public PagedIterable listRepositories() { - return root().createRequest() - .withUrlPath("/users/" + login + "/repos") - .toIterable(GHRepository[].class, null) - .withPageSize(30); + public String getBlog() throws IOException { + populate(); + return blog; } /** - * Lists up all the repositories using the specified page size. + * Gets the company name of this user, like "Sun Microsystems, Inc." * - * @param pageSize - * size for each page of items returned by GitHub. Maximum page size is 100. Unlike - * {@link #getRepositories()}, this does not wait until all the repositories are returned. - * @return the paged iterable - * @deprecated Use #listRepositories().withPageSize() instead. + * @return the company + * @throws IOException + * the io exception */ - @Deprecated - public PagedIterable listRepositories(final int pageSize) { - return listRepositories().withPageSize(pageSize); + public String getCompany() throws IOException { + populate(); + return company; } /** - * Gets repository. + * Gets the created at. * - * @param name - * the name - * @return null if the repository was not found + * @return the created at * @throws IOException - * the io exception + * Signals that an I/O exception has occurred. */ - public GHRepository getRepository(String name) throws IOException { - try { - return GHRepository.read(root(), login, name); - } catch (FileNotFoundException e) { - return null; - } + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getCreatedAt() throws IOException { + populate(); + return super.getCreatedAt(); } /** - * Lists events for an organization or an user. + * Gets the e-mail address of the user. * - * @return the paged iterable + * @return the email * @throws IOException * the io exception */ - public abstract PagedIterable listEvents() throws IOException; - - /** - * Returns a string of the avatar image URL. - * - * @return the avatar url - */ - public String getAvatarUrl() { - return avatar_url; + public String getEmail() throws IOException { + populate(); + return email; } /** - * Gets the login ID of this user, like 'kohsuke'. + * Gets followers count. * - * @return the login + * @return the followers count + * @throws IOException + * the io exception */ - public String getLogin() { - return login; + public int getFollowersCount() throws IOException { + populate(); + return followers; } /** - * Gets the human-readable name of the user, like "Kohsuke Kawaguchi". + * Gets following count. * - * @return the name + * @return the following count * @throws IOException * the io exception */ - public String getName() throws IOException { + public int getFollowingCount() throws IOException { populate(); - return name; + return following; } /** - * Gets the company name of this user, like "Sun Microsystems, Inc." + * Gets the html url. * - * @return the company - * @throws IOException - * the io exception + * @return the html url */ - public String getCompany() throws IOException { - populate(); - return company; + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); } /** @@ -190,157 +151,201 @@ public String getLocation() throws IOException { } /** - * Gets the Twitter Username of this user, like "GitHub". + * Gets the login ID of this user, like 'kohsuke'. * - * @return the Twitter username - * @throws IOException - * the io exception + * @return the login */ - public String getTwitterUsername() throws IOException { - populate(); - return twitter_username; + public String getLogin() { + return login; } /** - * Gets the created at. + * Gets the human-readable name of the user, like "Kohsuke Kawaguchi". * - * @return the created at + * @return the name * @throws IOException - * Signals that an I/O exception has occurred. + * the io exception */ - public Date getCreatedAt() throws IOException { + public String getName() throws IOException { populate(); - return super.getCreatedAt(); + return name; } /** - * Gets the updated at. + * Gets public gist count. * - * @return the updated at + * @return the public gist count * @throws IOException - * Signals that an I/O exception has occurred. + * the io exception */ - public Date getUpdatedAt() throws IOException { + public int getPublicGistCount() throws IOException { populate(); - return super.getUpdatedAt(); + return publicGists; } /** - * Gets the blog URL of this user. + * Gets public repo count. * - * @return the blog + * @return the public repo count * @throws IOException * the io exception */ - public String getBlog() throws IOException { + public int getPublicRepoCount() throws IOException { populate(); - return blog; + return publicRepos; } /** - * Gets the html url. + * Gets the public repositories this user owns. * - * @return the html url + *

+ * To list your own repositories, including private repositories, use {@link GHMyself#listRepositories()} + * + * @return the repositories */ - public URL getHtmlUrl() { - return GitHubClient.parseURL(html_url); + public synchronized Map getRepositories() { + Map repositories = new TreeMap(); + for (GHRepository r : listRepositories().withPageSize(100)) { + repositories.put(r.getName(), r); + } + return Collections.unmodifiableMap(repositories); } /** - * Gets the e-mail address of the user. + * Gets repository. * - * @return the email + * @param name + * the name + * @return null if the repository was not found * @throws IOException * the io exception */ - public String getEmail() throws IOException { - populate(); - return email; + public GHRepository getRepository(String name) throws IOException { + try { + return GHRepository.read(root(), login, name); + } catch (FileNotFoundException e) { + return null; + } } /** - * Gets public gist count. + * Gets total private repo count. * - * @return the public gist count + * @return the total private repo count * @throws IOException * the io exception */ - public int getPublicGistCount() throws IOException { + public Optional getTotalPrivateRepoCount() throws IOException { populate(); - return public_gists; + return Optional.ofNullable(totalPrivateRepos); } /** - * Gets public repo count. + * Gets the Twitter Username of this user, like "GitHub". * - * @return the public repo count + * @return the Twitter username * @throws IOException * the io exception */ - public int getPublicRepoCount() throws IOException { + public String getTwitterUsername() throws IOException { populate(); - return public_repos; + return twitterUsername; } /** - * Gets following count. + * Gets the type. This is either "User" or "Organization". * - * @return the following count + * @return the type * @throws IOException * the io exception */ - public int getFollowingCount() throws IOException { - populate(); - return following; + public String getType() throws IOException { + if (type == null) { + populate(); + } + return type; } /** - * Gets followers count. + * Gets the updated at. * - * @return the followers count + * @return the updated at * @throws IOException - * the io exception + * Signals that an I/O exception has occurred. */ - public int getFollowersCount() throws IOException { + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getUpdatedAt() throws IOException { populate(); - return followers; + return super.getUpdatedAt(); } /** - * Gets the type. This is either "User" or "Organization". + * Gets the siteAdmin field. * - * @return the type + * @return the siteAdmin field * @throws IOException * the io exception */ - public String getType() throws IOException { - if (type == null) { - populate(); - } - return type; + public boolean isSiteAdmin() throws IOException { + populate(); + return siteAdmin; } /** - * Gets the site_admin field. + * Lists events for an organization or an user. * - * @return the site_admin field + * @return the paged iterable * @throws IOException * the io exception */ - public boolean isSiteAdmin() throws IOException { - populate(); - return site_admin; + public abstract PagedIterable listEvents() throws IOException; + + /** + * List all the repositories using a default of 30 items page size. + *

+ * Unlike {@link #getRepositories()}, this does not wait until all the repositories are returned. + * + * @return the paged iterable + */ + public PagedIterable listRepositories() { + return root().createRequest() + .withUrlPath("/users/" + login + "/repos") + .toIterable(GHRepository[].class, null) + .withPageSize(30); } /** - * Gets total private repo count. + * Lists up all the repositories using the specified page size. + * + * @param pageSize + * size for each page of items returned by GitHub. Maximum page size is 100. Unlike + * {@link #getRepositories()}, this does not wait until all the repositories are returned. + * @return the paged iterable + * @deprecated Use #listRepositories().withPageSize() instead. + */ + @Deprecated + public PagedIterable listRepositories(final int pageSize) { + return listRepositories().withPageSize(pageSize); + } + + /** + * Fully populate the data by retrieving missing data. + *

+ * Depending on the original API call where this object is created, it may not contain everything. * - * @return the total private repo count * @throws IOException * the io exception */ - public Optional getTotalPrivateRepoCount() throws IOException { - populate(); - return Optional.ofNullable(total_private_repos); + protected synchronized void populate() throws IOException { + if (super.getCreatedAt() != null) { + return; // already populated + } + if (isOffline()) { + return; // cannot populate, will have to live with what we have + } + URL url = getUrl(); + if (url != null) { + root().createRequest().setRawUrlPath(url.toString()).fetchInto(this); + } } } diff --git a/src/main/java/org/kohsuke/github/GHPersonSet.java b/src/main/java/org/kohsuke/github/GHPersonSet.java index c737249b5e..cd1bf9788e 100644 --- a/src/main/java/org/kohsuke/github/GHPersonSet.java +++ b/src/main/java/org/kohsuke/github/GHPersonSet.java @@ -46,11 +46,9 @@ public GHPersonSet(T... c) { * * @param initialCapacity * the initial capacity - * @param loadFactor - * the load factor */ - public GHPersonSet(int initialCapacity, float loadFactor) { - super(initialCapacity, loadFactor); + public GHPersonSet(int initialCapacity) { + super(initialCapacity); } /** @@ -58,9 +56,11 @@ public GHPersonSet(int initialCapacity, float loadFactor) { * * @param initialCapacity * the initial capacity + * @param loadFactor + * the load factor */ - public GHPersonSet(int initialCapacity) { - super(initialCapacity); + public GHPersonSet(int initialCapacity, float loadFactor) { + super(initialCapacity, loadFactor); } /** diff --git a/src/main/java/org/kohsuke/github/GHProject.java b/src/main/java/org/kohsuke/github/GHProject.java index 38fcf51e90..7243833ec5 100644 --- a/src/main/java/org/kohsuke/github/GHProject.java +++ b/src/main/java/org/kohsuke/github/GHProject.java @@ -40,147 +40,174 @@ public class GHProject extends GHObject { /** - * Create default GHProject instance + * The enum ProjectState. */ - public GHProject() { + public enum ProjectState { + + /** The closed. */ + CLOSED, + /** The open. */ + OPEN } - /** The owner. */ - protected GHObject owner; + /** + * The enum ProjectStateFilter. + */ + public static enum ProjectStateFilter { + + /** The all. */ + ALL, + /** The closed. */ + CLOSED, + /** The open. */ + OPEN + } - private String owner_url; - private String html_url; - private String name; private String body; + private GHUser creator; + private String htmlUrl; + private String name; private int number; + private String ownerUrl; private String state; - private GHUser creator; + + /** The owner. */ + protected GHObject owner; /** - * Gets the html url. - * - * @return the html url + * Create default GHProject instance */ - public URL getHtmlUrl() { - return GitHubClient.parseURL(html_url); + public GHProject() { } /** - * Gets owner. + * Create column gh project column. * - * @return the owner + * @param name + * the name + * @return the gh project column * @throws IOException * the io exception */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHObject getOwner() throws IOException { - if (owner == null) { - try { - if (owner_url.contains("/orgs/")) { - owner = root().createRequest().withUrlPath(getOwnerUrl().getPath()).fetch(GHOrganization.class); - } else if (owner_url.contains("/users/")) { - owner = root().createRequest().withUrlPath(getOwnerUrl().getPath()).fetch(GHUser.class); - } else if (owner_url.contains("/repos/")) { - String[] pathElements = getOwnerUrl().getPath().split("/"); - owner = GHRepository.read(root(), pathElements[1], pathElements[2]); - } - } catch (FileNotFoundException e) { - return null; - } - } - return owner; + public GHProjectColumn createColumn(String name) throws IOException { + return root().createRequest() + .method("POST") + .with("name", name) + .withUrlPath(String.format("/projects/%d/columns", getId())) + .fetch(GHProjectColumn.class) + .lateBind(this); } /** - * Gets owner url. + * Delete. * - * @return the owner url + * @throws IOException + * the io exception */ - public URL getOwnerUrl() { - return GitHubClient.parseURL(owner_url); + public void delete() throws IOException { + root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); } /** - * Gets name. + * Gets body. * - * @return the name + * @return the body */ - public String getName() { - return name; + public String getBody() { + return body; } /** - * Gets body. + * Gets creator. * - * @return the body + * @return the creator */ - public String getBody() { - return body; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHUser getCreator() { + return creator; } /** - * Gets number. + * Gets the html url. * - * @return the number + * @return the html url */ - public int getNumber() { - return number; + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); } /** - * Gets state. + * Gets name. * - * @return the state + * @return the name */ - public ProjectState getState() { - return Enum.valueOf(ProjectState.class, state.toUpperCase(Locale.ENGLISH)); + public String getName() { + return name; } /** - * Gets creator. + * Gets number. * - * @return the creator + * @return the number */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHUser getCreator() { - return creator; + public int getNumber() { + return number; } /** - * Wrap gh project. + * Gets owner. * - * @param repo - * the repo - * @return the gh project + * @return the owner + * @throws IOException + * the io exception */ - GHProject lateBind(GHRepository repo) { - this.owner = repo; - return this; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHObject getOwner() throws IOException { + if (owner == null) { + try { + if (ownerUrl.contains("/orgs/")) { + owner = root().createRequest().withUrlPath(getOwnerUrl().getPath()).fetch(GHOrganization.class); + } else if (ownerUrl.contains("/users/")) { + owner = root().createRequest().withUrlPath(getOwnerUrl().getPath()).fetch(GHUser.class); + } else if (ownerUrl.contains("/repos/")) { + String[] pathElements = getOwnerUrl().getPath().split("/"); + owner = GHRepository.read(root(), pathElements[1], pathElements[2]); + } + } catch (FileNotFoundException e) { + return null; + } + } + return owner; } - private void edit(String key, Object value) throws IOException { - root().createRequest().method("PATCH").with(key, value).withUrlPath(getApiRoute()).send(); + /** + * Gets owner url. + * + * @return the owner url + */ + public URL getOwnerUrl() { + return GitHubClient.parseURL(ownerUrl); } /** - * Gets api route. + * Gets state. * - * @return the api route + * @return the state */ - protected String getApiRoute() { - return "/projects/" + getId(); + public ProjectState getState() { + return Enum.valueOf(ProjectState.class, state.toUpperCase(Locale.ENGLISH)); } /** - * Sets name. + * List columns paged iterable. * - * @param name - * the name - * @throws IOException - * the io exception + * @return the paged iterable */ - public void setName(String name) throws IOException { - edit("name", name); + public PagedIterable listColumns() { + final GHProject project = this; + return root().createRequest() + .withUrlPath(String.format("/projects/%d/columns", getId())) + .toIterable(GHProjectColumn[].class, item -> item.lateBind(project)); } /** @@ -196,39 +223,15 @@ public void setBody(String body) throws IOException { } /** - * The enum ProjectState. - */ - public enum ProjectState { - - /** The open. */ - OPEN, - /** The closed. */ - CLOSED - } - - /** - * Sets state. + * Sets name. * - * @param state - * the state + * @param name + * the name * @throws IOException * the io exception */ - public void setState(ProjectState state) throws IOException { - edit("state", state.toString().toLowerCase()); - } - - /** - * The enum ProjectStateFilter. - */ - public static enum ProjectStateFilter { - - /** The all. */ - ALL, - /** The open. */ - OPEN, - /** The closed. */ - CLOSED + public void setName(String name) throws IOException { + edit("name", name); } /** @@ -257,42 +260,39 @@ public void setPublic(boolean isPublic) throws IOException { } /** - * Delete. + * Sets state. * + * @param state + * the state * @throws IOException * the io exception */ - public void delete() throws IOException { - root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); + public void setState(ProjectState state) throws IOException { + edit("state", state.toString().toLowerCase()); + } + + private void edit(String key, Object value) throws IOException { + root().createRequest().method("PATCH").with(key, value).withUrlPath(getApiRoute()).send(); } /** - * List columns paged iterable. + * Gets api route. * - * @return the paged iterable + * @return the api route */ - public PagedIterable listColumns() { - final GHProject project = this; - return root().createRequest() - .withUrlPath(String.format("/projects/%d/columns", getId())) - .toIterable(GHProjectColumn[].class, item -> item.lateBind(project)); + protected String getApiRoute() { + return "/projects/" + getId(); } /** - * Create column gh project column. + * Wrap gh project. * - * @param name - * the name - * @return the gh project column - * @throws IOException - * the io exception + * @param repo + * the repo + * @return the gh project */ - public GHProjectColumn createColumn(String name) throws IOException { - return root().createRequest() - .method("POST") - .with("name", name) - .withUrlPath(String.format("/projects/%d/columns", getId())) - .fetch(GHProjectColumn.class) - .lateBind(this); + GHProject lateBind(GHRepository repo) { + this.owner = repo; + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHProjectCard.java b/src/main/java/org/kohsuke/github/GHProjectCard.java index 20a95294a6..6b3d7321b8 100644 --- a/src/main/java/org/kohsuke/github/GHProjectCard.java +++ b/src/main/java/org/kohsuke/github/GHProjectCard.java @@ -15,69 +15,28 @@ */ public class GHProjectCard extends GHObject { - /** - * Create default GHProjectCard instance - */ - public GHProjectCard() { - } + private boolean archived; - private GHProject project; private GHProjectColumn column; + private String contentUrl, projectUrl, columnUrl; - private String note; private GHUser creator; - private String content_url, project_url, column_url; - private boolean archived; - - /** - * Gets the html url. - * - * @return the html url - */ - public URL getHtmlUrl() { - return null; - } - - /** - * Wrap gh project card. - * - * @param root - * the root - * @return the gh project card - */ - GHProjectCard lateBind(GitHub root) { - return this; - } - + private String note; + private GHProject project; /** - * Wrap gh project card. - * - * @param column - * the column - * @return the gh project card + * Create default GHProjectCard instance */ - GHProjectCard lateBind(GHProjectColumn column) { - this.column = column; - this.project = column.project; - return lateBind(column.root()); + public GHProjectCard() { } /** - * Gets project. + * Delete. * - * @return the project * @throws IOException * the io exception */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHProject getProject() throws IOException { - if (project == null) { - try { - project = root().createRequest().withUrlPath(getProjectUrl().getPath()).fetch(GHProject.class); - } catch (FileNotFoundException e) { - } - } - return project; + public void delete() throws IOException { + root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); } /** @@ -101,6 +60,15 @@ public GHProjectColumn getColumn() throws IOException { return column; } + /** + * Gets column url. + * + * @return the column url + */ + public URL getColumnUrl() { + return GitHubClient.parseURL(columnUrl); + } + /** * Gets content if present. Might be a {@link GHPullRequest} or a {@link GHIssue}. * @@ -109,10 +77,10 @@ public GHProjectColumn getColumn() throws IOException { * the io exception */ public GHIssue getContent() throws IOException { - if (StringUtils.isEmpty(content_url)) + if (StringUtils.isEmpty(contentUrl)) return null; try { - if (content_url.contains("/pulls")) { + if (contentUrl.contains("/pulls")) { return root().createRequest().withUrlPath(getContentUrl().getPath()).fetch(GHPullRequest.class); } else { return root().createRequest().withUrlPath(getContentUrl().getPath()).fetch(GHIssue.class); @@ -123,12 +91,12 @@ public GHIssue getContent() throws IOException { } /** - * Gets note. + * Gets content url. * - * @return the note + * @return the content url */ - public String getNote() { - return note; + public URL getContentUrl() { + return GitHubClient.parseURL(contentUrl); } /** @@ -142,30 +110,48 @@ public GHUser getCreator() { } /** - * Gets content url. + * Gets the html url. * - * @return the content url + * @return the html url */ - public URL getContentUrl() { - return GitHubClient.parseURL(content_url); + public URL getHtmlUrl() { + return null; } /** - * Gets project url. + * Gets note. * - * @return the project url + * @return the note */ - public URL getProjectUrl() { - return GitHubClient.parseURL(project_url); + public String getNote() { + return note; } /** - * Gets column url. + * Gets project. * - * @return the column url + * @return the project + * @throws IOException + * the io exception */ - public URL getColumnUrl() { - return GitHubClient.parseURL(column_url); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHProject getProject() throws IOException { + if (project == null) { + try { + project = root().createRequest().withUrlPath(getProjectUrl().getPath()).fetch(GHProject.class); + } catch (FileNotFoundException e) { + } + } + return project; + } + + /** + * Gets project url. + * + * @return the project url + */ + public URL getProjectUrl() { + return GitHubClient.parseURL(projectUrl); } /** @@ -178,27 +164,27 @@ public boolean isArchived() { } /** - * Sets note. + * Sets archived. * - * @param note - * the note + * @param archived + * the archived * @throws IOException * the io exception */ - public void setNote(String note) throws IOException { - edit("note", note); + public void setArchived(boolean archived) throws IOException { + edit("archived", archived); } /** - * Sets archived. + * Sets note. * - * @param archived - * the archived + * @param note + * the note * @throws IOException * the io exception */ - public void setArchived(boolean archived) throws IOException { - edit("archived", archived); + public void setNote(String note) throws IOException { + edit("note", note); } private void edit(String key, Object value) throws IOException { @@ -215,12 +201,26 @@ protected String getApiRoute() { } /** - * Delete. + * Wrap gh project card. * - * @throws IOException - * the io exception + * @param column + * the column + * @return the gh project card */ - public void delete() throws IOException { - root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); + GHProjectCard lateBind(GHProjectColumn column) { + this.column = column; + this.project = column.project; + return lateBind(column.root()); + } + + /** + * Wrap gh project card. + * + * @param root + * the root + * @return the gh project card + */ + GHProjectCard lateBind(GitHub root) { + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHProjectColumn.java b/src/main/java/org/kohsuke/github/GHProjectColumn.java index e4ca1f5f90..8b7b0f272c 100644 --- a/src/main/java/org/kohsuke/github/GHProjectColumn.java +++ b/src/main/java/org/kohsuke/github/GHProjectColumn.java @@ -14,39 +14,73 @@ */ public class GHProjectColumn extends GHObject { + private String name; + + private String projectUrl; + + /** The project. */ + protected GHProject project; /** * Create default GHProjectColumn instance */ public GHProjectColumn() { } - /** The project. */ - protected GHProject project; + /** + * Create card gh project card. + * + * @param issue + * the issue + * @return the gh project card + * @throws IOException + * the io exception + */ + public GHProjectCard createCard(GHIssue issue) throws IOException { + String contentType = issue instanceof GHPullRequest ? "PullRequest" : "Issue"; + return root().createRequest() + .method("POST") + .with("content_type", contentType) + .with("content_id", issue.getId()) + .withUrlPath(String.format("/projects/columns/%d/cards", getId())) + .fetch(GHProjectCard.class) + .lateBind(this); + } - private String name; - private String project_url; + /** + * Create card gh project card. + * + * @param note + * the note + * @return the gh project card + * @throws IOException + * the io exception + */ + public GHProjectCard createCard(String note) throws IOException { + return root().createRequest() + .method("POST") + .with("note", note) + .withUrlPath(String.format("/projects/columns/%d/cards", getId())) + .fetch(GHProjectCard.class) + .lateBind(this); + } /** - * Wrap gh project column. + * Delete. * - * @param root - * the root - * @return the gh project column + * @throws IOException + * the io exception */ - GHProjectColumn lateBind(GitHub root) { - return this; + public void delete() throws IOException { + root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); } /** - * Wrap gh project column. + * Gets name. * - * @param project - * the project - * @return the gh project column + * @return the name */ - GHProjectColumn lateBind(GHProject project) { - this.project = project; - return lateBind(project.root()); + public String getName() { + return name; } /** @@ -68,21 +102,24 @@ public GHProject getProject() throws IOException { } /** - * Gets name. + * Gets project url. * - * @return the name + * @return the project url */ - public String getName() { - return name; + public URL getProjectUrl() { + return GitHubClient.parseURL(projectUrl); } /** - * Gets project url. + * List cards paged iterable. * - * @return the project url + * @return the paged iterable */ - public URL getProjectUrl() { - return GitHubClient.parseURL(project_url); + public PagedIterable listCards() { + final GHProjectColumn column = this; + return root().createRequest() + .withUrlPath(String.format("/projects/columns/%d/cards", getId())) + .toIterable(GHProjectCard[].class, item -> item.lateBind(column)); } /** @@ -111,62 +148,25 @@ protected String getApiRoute() { } /** - * Delete. - * - * @throws IOException - * the io exception - */ - public void delete() throws IOException { - root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); - } - - /** - * List cards paged iterable. - * - * @return the paged iterable - */ - public PagedIterable listCards() { - final GHProjectColumn column = this; - return root().createRequest() - .withUrlPath(String.format("/projects/columns/%d/cards", getId())) - .toIterable(GHProjectCard[].class, item -> item.lateBind(column)); - } - - /** - * Create card gh project card. + * Wrap gh project column. * - * @param note - * the note - * @return the gh project card - * @throws IOException - * the io exception + * @param project + * the project + * @return the gh project column */ - public GHProjectCard createCard(String note) throws IOException { - return root().createRequest() - .method("POST") - .with("note", note) - .withUrlPath(String.format("/projects/columns/%d/cards", getId())) - .fetch(GHProjectCard.class) - .lateBind(this); + GHProjectColumn lateBind(GHProject project) { + this.project = project; + return lateBind(project.root()); } /** - * Create card gh project card. + * Wrap gh project column. * - * @param issue - * the issue - * @return the gh project card - * @throws IOException - * the io exception + * @param root + * the root + * @return the gh project column */ - public GHProjectCard createCard(GHIssue issue) throws IOException { - String contentType = issue instanceof GHPullRequest ? "PullRequest" : "Issue"; - return root().createRequest() - .method("POST") - .with("content_type", contentType) - .with("content_id", issue.getId()) - .withUrlPath(String.format("/projects/columns/%d/cards", getId())) - .fetch(GHProjectCard.class) - .lateBind(this); + GHProjectColumn lateBind(GitHub root) { + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHProjectsV2Item.java b/src/main/java/org/kohsuke/github/GHProjectsV2Item.java index 30e0424d1c..10f581bf80 100644 --- a/src/main/java/org/kohsuke/github/GHProjectsV2Item.java +++ b/src/main/java/org/kohsuke/github/GHProjectsV2Item.java @@ -1,8 +1,10 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import org.kohsuke.github.internal.EnumUtils; import java.net.URL; +import java.time.Instant; import java.util.Date; // TODO: Auto-generated Javadoc @@ -23,25 +25,41 @@ public class GHProjectsV2Item extends GHObject { /** - * Create default GHProjectsV2Item instance + * The Enum ContentType. */ - public GHProjectsV2Item() { + public enum ContentType { + + /** The draftissue. */ + DRAFTISSUE, + /** The issue. */ + ISSUE, + /** The pullrequest. */ + PULLREQUEST, + /** The unknown. */ + UNKNOWN; } - private String projectNodeId; + private String archivedAt; private String contentNodeId; private String contentType; private GHUser creator; - private String archivedAt; + private String projectNodeId; /** - * Gets the project node id. + * Create default GHProjectsV2Item instance + */ + public GHProjectsV2Item() { + } + + /** + * Gets the archived at. * - * @return the project node id + * @return the archived at */ - public String getProjectNodeId() { - return projectNodeId; + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getArchivedAt() { + return GitHubClient.parseInstant(archivedAt); } /** @@ -71,15 +89,6 @@ public GHUser getCreator() { return root().intern(creator); } - /** - * Gets the archived at. - * - * @return the archived at - */ - public Date getArchivedAt() { - return GitHubClient.parseDate(archivedAt); - } - /** * Gets the html url. * @@ -90,17 +99,11 @@ public URL getHtmlUrl() { } /** - * The Enum ContentType. + * Gets the project node id. + * + * @return the project node id */ - public enum ContentType { - - /** The issue. */ - ISSUE, - /** The draftissue. */ - DRAFTISSUE, - /** The pullrequest. */ - PULLREQUEST, - /** The unknown. */ - UNKNOWN; + public String getProjectNodeId() { + return projectNodeId; } } diff --git a/src/main/java/org/kohsuke/github/GHProjectsV2ItemChanges.java b/src/main/java/org/kohsuke/github/GHProjectsV2ItemChanges.java index d9636537ef..9ab551c3ad 100644 --- a/src/main/java/org/kohsuke/github/GHProjectsV2ItemChanges.java +++ b/src/main/java/org/kohsuke/github/GHProjectsV2ItemChanges.java @@ -1,8 +1,10 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.kohsuke.github.internal.EnumUtils; +import java.time.Instant; import java.util.Date; // TODO: Auto-generated Javadoc @@ -12,45 +14,25 @@ * Note that this is best effort only as nothing is documented in the GitHub documentation. */ @SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD" }, justification = "JSON API") -public class GHProjectsV2ItemChanges { +public class GHProjectsV2ItemChanges extends GitHubBridgeAdapterObject { /** - * Create default GHProjectsV2ItemChanges instance - */ - public GHProjectsV2ItemChanges() { - } - - private FieldValue fieldValue; - - private FromToDate archivedAt; - - private FromTo previousProjectsV2ItemNodeId; - - /** - * Gets the field value. - * - * @return the field value - */ - public FieldValue getFieldValue() { - return fieldValue; - } - - /** - * Gets the archived at. - * - * @return the archived at + * The Enum FieldType. */ - public FromToDate getArchivedAt() { - return archivedAt; - } + public enum FieldType { - /** - * Gets the previous projects V 2 item node id. - * - * @return the previous projects V 2 item node id - */ - public FromTo getPreviousProjectsV2ItemNodeId() { - return previousProjectsV2ItemNodeId; + /** The date. */ + DATE, + /** The iteration. */ + ITERATION, + /** The number. */ + NUMBER, + /** The single select. */ + SINGLE_SELECT, + /** The text. */ + TEXT, + /** The unknown. */ + UNKNOWN; } /** @@ -58,15 +40,15 @@ public FromTo getPreviousProjectsV2ItemNodeId() { */ public static class FieldValue { + private String fieldNodeId; + + private String fieldType; /** * Create default FieldValue instance */ public FieldValue() { } - private String fieldNodeId; - private String fieldType; - /** * Gets the field node id. * @@ -91,15 +73,15 @@ public FieldType getFieldType() { */ public static class FromTo { + private String from; + + private String to; /** * Create default FromTo instance */ public FromTo() { } - private String from; - private String to; - /** * Gets the from. * @@ -124,22 +106,23 @@ public String getTo() { */ public static class FromToDate { + private String from; + + private String to; /** * Create default FromToDate instance */ public FromToDate() { } - private String from; - private String to; - /** * Gets the from. * * @return the from */ - public Date getFrom() { - return GitHubClient.parseDate(from); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getFrom() { + return GitHubClient.parseInstant(from); } /** @@ -147,27 +130,48 @@ public Date getFrom() { * * @return the to */ - public Date getTo() { - return GitHubClient.parseDate(to); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getTo() { + return GitHubClient.parseInstant(to); } } + private FromToDate archivedAt; + + private FieldValue fieldValue; + + private FromTo previousProjectsV2ItemNodeId; + /** - * The Enum FieldType. + * Create default GHProjectsV2ItemChanges instance */ - public enum FieldType { + public GHProjectsV2ItemChanges() { + } - /** The text. */ - TEXT, - /** The number. */ - NUMBER, - /** The date. */ - DATE, - /** The single select. */ - SINGLE_SELECT, - /** The iteration. */ - ITERATION, - /** The unknown. */ - UNKNOWN; + /** + * Gets the archived at. + * + * @return the archived at + */ + public FromToDate getArchivedAt() { + return archivedAt; + } + + /** + * Gets the field value. + * + * @return the field value + */ + public FieldValue getFieldValue() { + return fieldValue; + } + + /** + * Gets the previous projects V 2 item node id. + * + * @return the previous projects V 2 item node id + */ + public FromTo getPreviousProjectsV2ItemNodeId() { + return previousProjectsV2ItemNodeId; } } diff --git a/src/main/java/org/kohsuke/github/GHPullRequest.java b/src/main/java/org/kohsuke/github/GHPullRequest.java index 128f819d5b..47636ce108 100644 --- a/src/main/java/org/kohsuke/github/GHPullRequest.java +++ b/src/main/java/org/kohsuke/github/GHPullRequest.java @@ -23,11 +23,13 @@ */ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.commons.lang3.StringUtils; import java.io.IOException; import java.net.URL; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -46,152 +48,243 @@ public class GHPullRequest extends GHIssue implements Refreshable { /** - * Create default GHPullRequest instance + * The status of auto merging a {@linkplain GHPullRequest}. + * */ - public GHPullRequest() { + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") + public static class AutoMerge { + + private String commitMessage; + + private String commitTitle; + private GHUser enabledBy; + private MergeMethod mergeMethod; + /** + * Create default AutoMerge instance + */ + public AutoMerge() { + } + + /** + * the message of the commit, if e.g. {@linkplain MergeMethod#SQUASH} is used for the auto merge. + * + * @return the message of the commit + */ + public String getCommitMessage() { + return commitMessage; + } + + /** + * the title of the commit, if e.g. {@linkplain MergeMethod#SQUASH} is used for the auto merge. + * + * @return the title of the commit + */ + public String getCommitTitle() { + return commitTitle; + } + + /** + * The user who enabled the auto merge of the pull request. + * + * @return the {@linkplain GHUser} + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHUser getEnabledBy() { + return enabledBy; + } + + /** + * The merge method of the auto merge. + * + * @return the {@linkplain MergeMethod} + */ + public MergeMethod getMergeMethod() { + return mergeMethod; + } } + /** The enum MergeMethod. */ + public enum MergeMethod { + + /** The merge. */ + MERGE, + /** The rebase. */ + REBASE, + /** The squash. */ + SQUASH + } private static final String COMMENTS_ACTION = "/comments"; - private static final String REQUEST_REVIEWERS = "/requested_reviewers"; - private String patch_url, diff_url, issue_url; + private static final String REQUEST_REVIEWERS = "/requested_reviewers"; + private AutoMerge autoMerge; private GHCommitPointer base; - private String merged_at; + private int changedFiles; + + private int deletions; private GHCommitPointer head; + private String mergeCommitSha; + private Boolean mergeable; + private String mergeableState; + private boolean merged, maintainerCanModify; + private String mergedAt; // details that are only available when obtained from ID - private GHUser merged_by; - private int review_comments, additions, commits; - private boolean merged, maintainer_can_modify; + private GHUser mergedBy; + private String patchUrl, diffUrl, issueUrl; + private GHUser[] requestedReviewers; + + // pull request reviewers + + private GHTeam[] requestedTeams; + private int reviewComments, additions, commits; /** The draft. */ // making these package private to all for testing boolean draft; - private Boolean mergeable; - private int deletions; - private String mergeable_state; - private int changed_files; - private String merge_commit_sha; - private AutoMerge auto_merge; - - // pull request reviewers - - private GHUser[] requested_reviewers; - private GHTeam[] requested_teams; /** - * Wrap up. - * - * @param owner - * the owner - * @return the GH pull request + * Create default GHPullRequest instance */ - GHPullRequest wrapUp(GHRepository owner) { - this.wrap(owner); - return this; + public GHPullRequest() { } /** - * Gets the api route. + * Can maintainer modify boolean. * - * @return the api route + * @return the boolean + * @throws IOException + * the io exception */ - @Override - protected String getApiRoute() { - if (owner == null) { - // Issues returned from search to do not have an owner. Attempt to use url. - final URL url = Objects.requireNonNull(getUrl(), "Missing instance URL!"); - // The url sourced above is of the form '/repos///issues/', which - // subsequently issues requests against the `/issues/` handler, causing a 404 when - // asking for, say, a list of commits associated with a PR. Replace the `/issues/` - // with `/pulls/` to avoid that. - return StringUtils.prependIfMissing(url.toString().replace(root().getApiUrl(), ""), "/") - .replace("/issues/", "/pulls/"); - } - return "/repos/" + owner.getOwnerName() + "/" + owner.getName() + "/pulls/" + number; + public boolean canMaintainerModify() throws IOException { + populate(); + return maintainerCanModify; } /** - * The status of auto merging a pull request. + * Create review gh pull request review builder. * - * @return the {@linkplain AutoMerge} or {@code null} if no auto merge is set. + * @return the gh pull request review builder */ - public AutoMerge getAutoMerge() { - return auto_merge; + public GHPullRequestReviewBuilder createReview() { + return new GHPullRequestReviewBuilder(this); } /** - * The URL of the patch file. like https://github.com/jenkinsci/jenkins/pull/100.patch + * Create gh pull request review comment builder. * - * @return the patch url + * @return the gh pull request review comment builder. */ - public URL getPatchUrl() { - return GitHubClient.parseURL(patch_url); + public GHPullRequestReviewCommentBuilder createReviewComment() { + return new GHPullRequestReviewCommentBuilder(this); } /** - * The URL of the patch file. like https://github.com/jenkinsci/jenkins/pull/100.patch + * Create review comment gh pull request review comment. * - * @return the issue url + * @param body + * the body + * @param sha + * the sha + * @param path + * the path + * @param position + * the position + * @return the gh pull request review comment + * @throws IOException + * the io exception + * @deprecated use {@link #createReviewComment()} */ - public URL getIssueUrl() { - return GitHubClient.parseURL(issue_url); + @Deprecated + public GHPullRequestReviewComment createReviewComment(String body, String sha, String path, int position) + throws IOException { + return createReviewComment().body(body).commitId(sha).path(path).position(position).create(); } /** - * This points to where the change should be pulled into, but I'm not really sure what exactly it means. - * - * @return the base + * Request to enable auto merge for a pull request. + * + * @param authorEmail + * The email address to associate with this merge. + * @param clientMutationId + * A unique identifier for the client performing the mutation. + * @param commitBody + * Commit body to use for the commit when the PR is mergable; if omitted, a default message will be used. + * NOTE: when merging with a merge queue any input value for commit message is ignored. + * @param commitHeadline + * Commit headline to use for the commit when the PR is mergable; if omitted, a default message will be + * used. NOTE: when merging with a merge queue any input value for commit headline is ignored. + * @param expectedHeadOid + * The expected head OID of the pull request. + * @param mergeMethod + * The merge method to use. If omitted, defaults to `MERGE`. NOTE: when merging with a merge queue any + * input value for merge method is ignored. + * @throws IOException + * the io exception */ - public GHCommitPointer getBase() { - return base; - } + public void enablePullRequestAutoMerge(String authorEmail, + String clientMutationId, + String commitBody, + String commitHeadline, + String expectedHeadOid, + MergeMethod mergeMethod) throws IOException { - /** - * The change that should be pulled. The tip of the commits to merge. - * - * @return the head - */ - public GHCommitPointer getHead() { - return head; + StringBuilder inputBuilder = new StringBuilder(); + addParameter(inputBuilder, "pullRequestId", this.getNodeId()); + addOptionalParameter(inputBuilder, "authorEmail", authorEmail); + addOptionalParameter(inputBuilder, "clientMutationId", clientMutationId); + addOptionalParameter(inputBuilder, "commitBody", commitBody); + addOptionalParameter(inputBuilder, "commitHeadline", commitHeadline); + addOptionalParameter(inputBuilder, "expectedHeadOid", expectedHeadOid); + addOptionalParameter(inputBuilder, "mergeMethod", mergeMethod); + + String graphqlBody = "mutation EnableAutoMerge { enablePullRequestAutoMerge(input: {" + inputBuilder + "}) { " + + "pullRequest { id } } }"; + + root().createGraphQLRequest(graphqlBody).sendGraphQL(); + + refresh(); } /** - * The diff file, like https://github.com/jenkinsci/jenkins/pull/100.diff + * Gets additions. * - * @return the diff url + * @return the additions + * @throws IOException + * the io exception */ - public URL getDiffUrl() { - return GitHubClient.parseURL(diff_url); + public int getAdditions() throws IOException { + populate(); + return additions; } /** - * Gets merged at. + * The status of auto merging a pull request. * - * @return the merged at + * @return the {@linkplain AutoMerge} or {@code null} if no auto merge is set. */ - public Date getMergedAt() { - return GitHubClient.parseDate(merged_at); + public AutoMerge getAutoMerge() { + return autoMerge; } /** - * Gets the closed by. + * This points to where the change should be pulled into, but I'm not really sure what exactly it means. * - * @return the closed by + * @return the base */ - @Override - public GHUser getClosedBy() { - return null; + public GHCommitPointer getBase() { + return base; } /** - * Gets the pull request. + * Gets changed files. * - * @return the pull request + * @return the changed files + * @throws IOException + * the io exception */ - @Override - public PullRequest getPullRequest() { - return null; + public int getChangedFiles() throws IOException { + populate(); + return changedFiles; } // @@ -199,88 +292,76 @@ public PullRequest getPullRequest() { // /** - * Gets merged by. + * Gets the closed by. * - * @return the merged by - * @throws IOException - * the io exception + * @return the closed by */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHUser getMergedBy() throws IOException { - populate(); - return merged_by; + @Override + public GHUser getClosedBy() { + return null; } /** - * Gets review comments. + * Gets the number of commits. * - * @return the review comments + * @return the number of commits * @throws IOException * the io exception */ - public int getReviewComments() throws IOException { + public int getCommits() throws IOException { populate(); - return review_comments; + return commits; } /** - * Gets additions. + * Gets deletions. * - * @return the additions + * @return the deletions * @throws IOException * the io exception */ - public int getAdditions() throws IOException { + public int getDeletions() throws IOException { populate(); - return additions; + return deletions; } /** - * Gets the number of commits. + * The diff file, like https://github.com/jenkinsci/jenkins/pull/100.diff * - * @return the number of commits - * @throws IOException - * the io exception + * @return the diff url */ - public int getCommits() throws IOException { - populate(); - return commits; + public URL getDiffUrl() { + return GitHubClient.parseURL(diffUrl); } /** - * Is merged boolean. + * The change that should be pulled. The tip of the commits to merge. * - * @return the boolean - * @throws IOException - * the io exception + * @return the head */ - public boolean isMerged() throws IOException { - populate(); - return merged; + public GHCommitPointer getHead() { + return head; } /** - * Can maintainer modify boolean. + * The URL of the patch file. like https://github.com/jenkinsci/jenkins/pull/100.patch * - * @return the boolean - * @throws IOException - * the io exception + * @return the issue url */ - public boolean canMaintainerModify() throws IOException { - populate(); - return maintainer_can_modify; + public URL getIssueUrl() { + return GitHubClient.parseURL(issueUrl); } /** - * Is draft boolean. + * See GitHub blog post * - * @return the boolean + * @return the merge commit sha * @throws IOException * the io exception */ - public boolean isDraft() throws IOException { + public String getMergeCommitSha() throws IOException { populate(); - return draft; + return mergeCommitSha; } /** @@ -298,113 +379,138 @@ public Boolean getMergeable() throws IOException { } /** - * for test purposes only. + * Gets mergeable state. * - * @return the mergeable no refresh + * @return the mergeable state + * @throws IOException + * the io exception */ - Boolean getMergeableNoRefresh() { - return mergeable; + public String getMergeableState() throws IOException { + populate(); + return mergeableState; } /** - * Gets deletions. + * Gets merged at. * - * @return the deletions + * @return the merged at + */ + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getMergedAt() { + return GitHubClient.parseInstant(mergedAt); + } + + /** + * Gets merged by. + * + * @return the merged by * @throws IOException * the io exception */ - public int getDeletions() throws IOException { + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHUser getMergedBy() throws IOException { populate(); - return deletions; + return mergedBy; } /** - * Gets mergeable state. + * The URL of the patch file. like https://github.com/jenkinsci/jenkins/pull/100.patch * - * @return the mergeable state + * @return the patch url + */ + public URL getPatchUrl() { + return GitHubClient.parseURL(patchUrl); + } + + /** + * Gets the pull request. + * + * @return the pull request + */ + @Override + public PullRequest getPullRequest() { + return null; + } + + /** + * Gets requested reviewers. + * + * @return the requested reviewers * @throws IOException * the io exception */ - public String getMergeableState() throws IOException { - populate(); - return mergeable_state; + public List getRequestedReviewers() throws IOException { + refresh(requestedReviewers); + return Collections.unmodifiableList(Arrays.asList(requestedReviewers)); } /** - * Gets changed files. + * Gets requested teams. * - * @return the changed files + * @return the requested teams * @throws IOException * the io exception */ - public int getChangedFiles() throws IOException { - populate(); - return changed_files; + public List getRequestedTeams() throws IOException { + refresh(requestedTeams); + return Collections.unmodifiableList(Arrays.asList(requestedTeams)); } /** - * See GitHub blog post + * Gets review comments. * - * @return the merge commit sha + * @return the review comments * @throws IOException * the io exception */ - public String getMergeCommitSha() throws IOException { + public int getReviewComments() throws IOException { populate(); - return merge_commit_sha; + return reviewComments; } /** - * Gets requested reviewers. + * Is draft boolean. * - * @return the requested reviewers + * @return the boolean * @throws IOException * the io exception */ - public List getRequestedReviewers() throws IOException { - refresh(requested_reviewers); - return Collections.unmodifiableList(Arrays.asList(requested_reviewers)); + public boolean isDraft() throws IOException { + populate(); + return draft; } /** - * Gets requested teams. + * Is merged boolean. * - * @return the requested teams + * @return the boolean * @throws IOException * the io exception */ - public List getRequestedTeams() throws IOException { - refresh(requested_teams); - return Collections.unmodifiableList(Arrays.asList(requested_teams)); + public boolean isMerged() throws IOException { + populate(); + return merged; } /** - * Fully populate the data by retrieving missing data. + * Since a GHPullRequest is always a pull request, this method always returns true. * - *

- * Depending on the original API call where this object is created, it may not contain everything. + * @return true */ - private void populate() throws IOException { - if (mergeable_state != null) - return; // already populated - refresh(); + @Override + public boolean isPullRequest() { + return true; } /** - * Repopulates this object. + * Retrieves all the commits associated to this pull request. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @return the paged iterable */ - public void refresh() throws IOException { - if (isOffline()) { - return; // cannot populate, will have to live with what we have - } - - // we do not want to use getUrl() here as it points to the issues API - // and not the pull request one - URL absoluteUrl = GitHubRequest.getApiURL(root().getApiUrl(), getApiRoute()); - root().createRequest().setRawUrlPath(absoluteUrl.toString()).fetchInto(this).wrapUp(owner); + public PagedIterable listCommits() { + return root().createRequest() + .withUrlPath(String.format("%s/commits", getApiRoute())) + .toIterable(GHPullRequestCommitDetail[].class, item -> item.wrapUp(this)); } /** @@ -422,76 +528,129 @@ public PagedIterable listFiles() { } /** - * Retrieves all the reviews associated to this pull request. + * Obtains all the review comments associated with this pull request. + * + *

+ * Unlike {@link GHPullRequestReview#listReviewComments()}, this method returns full + * {@link GHPullRequestReviewComment} objects including line-related fields such as + * {@link GHPullRequestReviewComment#getLine() line}, {@link GHPullRequestReviewComment#getSide() side}, etc. * * @return the paged iterable + * @see GHPullRequestReview#listReviewComments() */ - public PagedIterable listReviews() { + public PagedIterable listReviewComments() { return root().createRequest() - .withUrlPath(String.format("%s/reviews", getApiRoute())) - .toIterable(GHPullRequestReview[].class, item -> item.wrapUp(this)); + .withUrlPath(getApiRoute() + COMMENTS_ACTION) + .toIterable(GHPullRequestReviewComment[].class, item -> item.wrapUp(this)); } /** - * Obtains all the review comments associated with this pull request. + * Retrieves all the reviews associated to this pull request. * * @return the paged iterable */ - public PagedIterable listReviewComments() { + public PagedIterable listReviews() { return root().createRequest() - .withUrlPath(getApiRoute() + COMMENTS_ACTION) - .toIterable(GHPullRequestReviewComment[].class, item -> item.wrapUp(this)); + .withUrlPath(String.format("%s/reviews", getApiRoute())) + .toIterable(GHPullRequestReview[].class, item -> item.wrapUp(this)); } /** - * Retrieves all the commits associated to this pull request. + * Converts a draft pull request to ready for review. * - * @return the paged iterable + * @throws IOException + * the io exception + * @throws IllegalStateException + * if the pull request is not a draft */ - public PagedIterable listCommits() { - return root().createRequest() - .withUrlPath(String.format("%s/commits", getApiRoute())) - .toIterable(GHPullRequestCommitDetail[].class, item -> item.wrapUp(this)); + public void markReadyForReview() throws IOException { + if (!draft) { + throw new IllegalStateException("Pull request is not a draft"); + } + + StringBuilder inputBuilder = new StringBuilder(); + addParameter(inputBuilder, "pullRequestId", this.getNodeId()); + + String graphqlBody = "mutation MarkReadyForReview { markPullRequestReadyForReview(input: {" + inputBuilder + + "}) { pullRequest { id } } }"; + + root().createGraphQLRequest(graphqlBody).sendGraphQL(); + + refresh(); } /** - * Create review gh pull request review builder. + * Merge this pull request. * - * @return the gh pull request review builder + *

+ * The equivalent of the big green "Merge pull request" button. + * + * @param msg + * Commit message. If null, the default one will be used. + * @throws IOException + * the io exception */ - public GHPullRequestReviewBuilder createReview() { - return new GHPullRequestReviewBuilder(this); + public void merge(String msg) throws IOException { + merge(msg, null); } /** - * Create gh pull request review comment builder. + * Merge this pull request. * - * @return the gh pull request review comment builder. + *

+ * The equivalent of the big green "Merge pull request" button. + * + * @param msg + * Commit message. If null, the default one will be used. + * @param sha + * SHA that pull request head must match to allow merge. + * @throws IOException + * the io exception */ - public GHPullRequestReviewCommentBuilder createReviewComment() { - return new GHPullRequestReviewCommentBuilder(this); + public void merge(String msg, String sha) throws IOException { + merge(msg, sha, null); } /** - * Create review comment gh pull request review comment. + * Merge this pull request, using the specified merge method. * - * @param body - * the body + *

+ * The equivalent of the big green "Merge pull request" button. + * + * @param msg + * Commit message. If null, the default one will be used. * @param sha * the sha - * @param path - * the path - * @param position - * the position - * @return the gh pull request review comment + * @param method + * SHA that pull request head must match to allow merge. * @throws IOException * the io exception - * @deprecated use {@link #createReviewComment()} */ - @Deprecated - public GHPullRequestReviewComment createReviewComment(String body, String sha, String path, int position) - throws IOException { - return createReviewComment().body(body).commitId(sha).path(path).position(position).create(); + public void merge(String msg, String sha, MergeMethod method) throws IOException { + root().createRequest() + .method("PUT") + .with("commit_message", msg) + .with("sha", sha) + .with("merge_method", method) + .withUrlPath(getApiRoute() + "/merge") + .send(); + } + + /** + * Repopulates this object. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public void refresh() throws IOException { + if (isOffline()) { + return; // cannot populate, will have to live with what we have + } + + // we do not want to use getUrl() here as it points to the issues API + // and not the pull request one + URL absoluteUrl = GitHubRequest.getApiURL(root().getApiUrl(), getApiRoute()); + root().createRequest().setRawUrlPath(absoluteUrl.toString()).fetchInto(this).wrapUp(owner); } /** @@ -561,127 +720,71 @@ public void updateBranch() throws IOException { .send(); } + private void addOptionalParameter(StringBuilder inputBuilder, String name, Object value) { + if (value != null) { + addParameter(inputBuilder, name, value); + } + } + + private void addParameter(StringBuilder inputBuilder, String name, Object value) { + Objects.requireNonNull(value); + String formatString = " %s: \"%s\""; + if (value instanceof Enum) { + formatString = " %s: %s"; + } + + inputBuilder.append(String.format(formatString, name, value)); + } + /** - * Merge this pull request. + * Fully populate the data by retrieving missing data. * *

- * The equivalent of the big green "Merge pull request" button. - * - * @param msg - * Commit message. If null, the default one will be used. - * @throws IOException - * the io exception + * Depending on the original API call where this object is created, it may not contain everything. */ - public void merge(String msg) throws IOException { - merge(msg, null); + private void populate() throws IOException { + if (mergeableState != null) + return; // already populated + refresh(); } /** - * Merge this pull request. - * - *

- * The equivalent of the big green "Merge pull request" button. + * Gets the api route. * - * @param msg - * Commit message. If null, the default one will be used. - * @param sha - * SHA that pull request head must match to allow merge. - * @throws IOException - * the io exception + * @return the api route */ - public void merge(String msg, String sha) throws IOException { - merge(msg, sha, null); + @Override + protected String getApiRoute() { + if (owner == null) { + // Issues returned from search to do not have an owner. Attempt to use url. + final URL url = Objects.requireNonNull(getUrl(), "Missing instance URL!"); + // The url sourced above is of the form '/repos///issues/', which + // subsequently issues requests against the `/issues/` handler, causing a 404 when + // asking for, say, a list of commits associated with a PR. Replace the `/issues/` + // with `/pulls/` to avoid that. + return StringUtils.prependIfMissing(url.toString().replace(root().getApiUrl(), ""), "/") + .replace("/issues/", "/pulls/"); + } + return "/repos/" + owner.getOwnerName() + "/" + owner.getName() + "/pulls/" + number; } /** - * Merge this pull request, using the specified merge method. - * - *

- * The equivalent of the big green "Merge pull request" button. + * for test purposes only. * - * @param msg - * Commit message. If null, the default one will be used. - * @param sha - * the sha - * @param method - * SHA that pull request head must match to allow merge. - * @throws IOException - * the io exception + * @return the mergeable no refresh */ - public void merge(String msg, String sha, MergeMethod method) throws IOException { - root().createRequest() - .method("PUT") - .with("commit_message", msg) - .with("sha", sha) - .with("merge_method", method) - .withUrlPath(getApiRoute() + "/merge") - .send(); - } - - /** The enum MergeMethod. */ - public enum MergeMethod { - - /** The merge. */ - MERGE, - /** The squash. */ - SQUASH, - /** The rebase. */ - REBASE + Boolean getMergeableNoRefresh() { + return mergeable; } - /** - * The status of auto merging a {@linkplain GHPullRequest}. + * Wrap up. * + * @param owner + * the owner + * @return the GH pull request */ - @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") - public static class AutoMerge { - - /** - * Create default AutoMerge instance - */ - public AutoMerge() { - } - - private GHUser enabled_by; - private MergeMethod merge_method; - private String commit_title; - private String commit_message; - - /** - * The user who enabled the auto merge of the pull request. - * - * @return the {@linkplain GHUser} - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHUser getEnabledBy() { - return enabled_by; - } - - /** - * The merge method of the auto merge. - * - * @return the {@linkplain MergeMethod} - */ - public MergeMethod getMergeMethod() { - return merge_method; - } - - /** - * the title of the commit, if e.g. {@linkplain MergeMethod#SQUASH} is used for the auto merge. - * - * @return the title of the commit - */ - public String getCommitTitle() { - return commit_title; - } - - /** - * the message of the commit, if e.g. {@linkplain MergeMethod#SQUASH} is used for the auto merge. - * - * @return the message of the commit - */ - public String getCommitMessage() { - return commit_message; - } + GHPullRequest wrapUp(GHRepository owner) { + this.wrap(owner); + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHPullRequestChanges.java b/src/main/java/org/kohsuke/github/GHPullRequestChanges.java index a866b33b38..d70e283f30 100644 --- a/src/main/java/org/kohsuke/github/GHPullRequestChanges.java +++ b/src/main/java/org/kohsuke/github/GHPullRequestChanges.java @@ -11,43 +11,6 @@ @SuppressFBWarnings("UWF_UNWRITTEN_FIELD") public class GHPullRequestChanges { - /** - * Create default GHPullRequestChanges instance - */ - public GHPullRequestChanges() { - } - - private GHCommitPointer base; - private GHFrom title; - private GHFrom body; - - /** - * Old target branch for pull request. - * - * @return old target branch info (or null if not changed) - */ - public GHCommitPointer getBase() { - return base; - } - - /** - * Old pull request title. - * - * @return old pull request title (or null if not changed) - */ - public GHFrom getTitle() { - return title; - } - - /** - * Old pull request body. - * - * @return old pull request body (or null if not changed) - */ - public GHFrom getBody() { - return body; - } - /** * The Class GHCommitPointer. * @@ -55,15 +18,15 @@ public GHFrom getBody() { */ public static class GHCommitPointer { + private GHFrom ref; + + private GHFrom sha; /** * Create default GHCommitPointer instance */ public GHCommitPointer() { } - private GHFrom ref; - private GHFrom sha; - /** * Named ref to the commit. This (from value) appears to be a "short ref" that doesn't include "refs/heads/" * portion. @@ -89,14 +52,14 @@ public GHFrom getSha() { */ public static class GHFrom { + private String from; + /** * Create default GHFrom instance */ public GHFrom() { } - private String from; - /** * Previous value that was changed. * @@ -106,4 +69,41 @@ public String getFrom() { return from; } } + private GHCommitPointer base; + private GHFrom body; + + private GHFrom title; + + /** + * Create default GHPullRequestChanges instance + */ + public GHPullRequestChanges() { + } + + /** + * Old target branch for pull request. + * + * @return old target branch info (or null if not changed) + */ + public GHCommitPointer getBase() { + return base; + } + + /** + * Old pull request body. + * + * @return old pull request body (or null if not changed) + */ + public GHFrom getBody() { + return body; + } + + /** + * Old pull request title. + * + * @return old pull request title (or null if not changed) + */ + public GHFrom getTitle() { + return title; + } } diff --git a/src/main/java/org/kohsuke/github/GHPullRequestCommitDetail.java b/src/main/java/org/kohsuke/github/GHPullRequestCommitDetail.java index f01da64f74..565270ef1b 100644 --- a/src/main/java/org/kohsuke/github/GHPullRequestCommitDetail.java +++ b/src/main/java/org/kohsuke/github/GHPullRequestCommitDetail.java @@ -40,74 +40,17 @@ justification = "JSON API") public class GHPullRequestCommitDetail { - /** - * Create default GHPullRequestCommitDetail instance - */ - public GHPullRequestCommitDetail() { - } - - private GHPullRequest owner; - - /** - * Wrap up. - * - * @param owner - * the owner - */ - void wrapUp(GHPullRequest owner) { - this.owner = owner; - } - - /** - * The type Tree. - */ - public static class Tree { - - /** - * Create default Tree instance - */ - public Tree() { - } - - /** The sha. */ - String sha; - - /** The url. */ - String url; - - /** - * Gets sha. - * - * @return the sha - */ - public String getSha() { - return sha; - } - - /** - * Gets url. - * - * @return the url - */ - public URL getUrl() { - return GitHubClient.parseURL(url); - } - } - /** * The type Commit. */ public static class Commit { - /** - * Create default Commit instance - */ - public Commit() { - } - /** The author. */ GitUser author; + /** The comment count. */ + Integer commentCount; + /** The committer. */ GitUser committer; @@ -120,8 +63,11 @@ public Commit() { /** The url. */ String url; - /** The comment count. */ - int comment_count; + /** + * Create default Commit instance + */ + public Commit() { + } /** * Gets author. @@ -133,39 +79,41 @@ public GitUser getAuthor() { } /** - * Gets committer. + * Gets comment count. * - * @return the committer + * @return the comment count */ - public GitUser getCommitter() { - return committer; + public Integer getCommentCount() { + return commentCount; } /** - * Gets message. + * Gets comment count. * - * @return the message + * @return the comment count + * @deprecated Use {@link #getCommentCount()} */ - public String getMessage() { - return message; + @Deprecated + public int getComment_count() { + return getCommentCount(); } /** - * Gets url. + * Gets committer. * - * @return the url + * @return the committer */ - public URL getUrl() { - return GitHubClient.parseURL(url); + public GitUser getCommitter() { + return committer; } /** - * Gets comment count. + * Gets message. * - * @return the comment count + * @return the message */ - public int getComment_count() { - return comment_count; + public String getMessage() { + return message; } /** @@ -176,6 +124,15 @@ public int getComment_count() { public Tree getTree() { return tree; } + + /** + * Gets url. + * + * @return the url + */ + public URL getUrl() { + return GitHubClient.parseURL(url); + } } /** @@ -183,20 +140,49 @@ public Tree getTree() { */ public static class CommitPointer { + /** The html url. */ + String htmlUrl; + + /** The sha. */ + String sha; + + /** The url. */ + String url; + /** * Create default CommitPointer instance */ public CommitPointer() { } - /** The sha. */ - String sha; + /** + * Gets html url. + * + * @return the html url + */ + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); + } - /** The url. */ - String url; + /** + * Gets html url. + * + * @return the html url + * @deprecated Use {@link #getHtmlUrl()} + */ + @Deprecated + public URL getHtml_url() { + return getHtmlUrl(); + } - /** The html url. */ - String html_url; + /** + * Gets sha. + * + * @return the sha + */ + public String getSha() { + return sha; + } /** * Gets url. @@ -206,14 +192,23 @@ public CommitPointer() { public URL getUrl() { return GitHubClient.parseURL(url); } + } + + /** + * The type Tree. + */ + public static class Tree { + + /** The sha. */ + String sha; + + /** The url. */ + String url; /** - * Gets html url. - * - * @return the html url + * Create default Tree instance */ - public URL getHtml_url() { - return GitHubClient.parseURL(html_url); + public Tree() { } /** @@ -224,42 +219,41 @@ public URL getHtml_url() { public String getSha() { return sha; } + + /** + * Gets url. + * + * @return the url + */ + public URL getUrl() { + return GitHubClient.parseURL(url); + } } - /** The sha. */ - String sha; + private GHPullRequest owner; + + /** The comments url. */ + String commentsUrl; /** The commit. */ Commit commit; - /** The url. */ - String url; - /** The html url. */ - String html_url; - - /** The comments url. */ - String comments_url; + String htmlUrl; /** The parents. */ CommitPointer[] parents; - /** - * Gets sha. - * - * @return the sha - */ - public String getSha() { - return sha; - } + /** The sha. */ + String sha; + + /** The url. */ + String url; /** - * Gets commit. - * - * @return the commit + * Create default GHPullRequestCommitDetail instance */ - public Commit getCommit() { - return commit; + public GHPullRequestCommitDetail() { } /** @@ -272,21 +266,21 @@ public URL getApiUrl() { } /** - * Gets url. + * Gets comments url. * - * @return the url + * @return the comments url */ - public URL getUrl() { - return GitHubClient.parseURL(html_url); + public URL getCommentsUrl() { + return GitHubClient.parseURL(commentsUrl); } /** - * Gets comments url. + * Gets commit. * - * @return the comments url + * @return the commit */ - public URL getCommentsUrl() { - return GitHubClient.parseURL(comments_url); + public Commit getCommit() { + return commit; } /** @@ -299,4 +293,32 @@ public CommitPointer[] getParents() { System.arraycopy(parents, 0, newValue, 0, parents.length); return newValue; } + + /** + * Gets sha. + * + * @return the sha + */ + public String getSha() { + return sha; + } + + /** + * Gets url. + * + * @return the url + */ + public URL getUrl() { + return GitHubClient.parseURL(htmlUrl); + } + + /** + * Wrap up. + * + * @param owner + * the owner + */ + void wrapUp(GHPullRequest owner) { + this.owner = owner; + } } diff --git a/src/main/java/org/kohsuke/github/GHPullRequestFileDetail.java b/src/main/java/org/kohsuke/github/GHPullRequestFileDetail.java index 2a316f49e7..147d30b4d3 100644 --- a/src/main/java/org/kohsuke/github/GHPullRequestFileDetail.java +++ b/src/main/java/org/kohsuke/github/GHPullRequestFileDetail.java @@ -35,72 +35,43 @@ */ public class GHPullRequestFileDetail { - /** - * Create default GHPullRequestFileDetail instance - */ - public GHPullRequestFileDetail() { - } - - /** The sha. */ - String sha; - - /** The filename. */ - String filename; - - /** The status. */ - String status; - /** The additions. */ int additions; - /** The deletions. */ - int deletions; + /** The blob url. */ + String blobUrl; /** The changes. */ int changes; - /** The blob url. */ - String blob_url; + /** The contents url. */ + String contentsUrl; - /** The raw url. */ - String raw_url; + /** The deletions. */ + int deletions; - /** The contents url. */ - String contents_url; + /** The filename. */ + String filename; /** The patch. */ String patch; /** The previous filename. */ - String previous_filename; + String previousFilename; - /** - * Gets sha of the file (not commit sha). - * - * @return the sha - * @see List pull requests - * files - */ - public String getSha() { - return sha; - } + /** The raw url. */ + String rawUrl; - /** - * Gets filename. - * - * @return the filename - */ - public String getFilename() { - return filename; - } + /** The sha. */ + String sha; + + /** The status. */ + String status; /** - * Gets status (added/modified/deleted). - * - * @return the status + * Create default GHPullRequestFileDetail instance */ - public String getStatus() { - return status; + public GHPullRequestFileDetail() { } /** @@ -113,12 +84,12 @@ public int getAdditions() { } /** - * Gets deletions. + * Gets blob url. * - * @return the deletions + * @return the blob url */ - public int getDeletions() { - return deletions; + public URL getBlobUrl() { + return GitHubClient.parseURL(blobUrl); } /** @@ -131,30 +102,30 @@ public int getChanges() { } /** - * Gets blob url. + * Gets contents url. * - * @return the blob url + * @return the contents url */ - public URL getBlobUrl() { - return GitHubClient.parseURL(blob_url); + public URL getContentsUrl() { + return GitHubClient.parseURL(contentsUrl); } /** - * Gets raw url. + * Gets deletions. * - * @return the raw url + * @return the deletions */ - public URL getRawUrl() { - return GitHubClient.parseURL(raw_url); + public int getDeletions() { + return deletions; } /** - * Gets contents url. + * Gets filename. * - * @return the contents url + * @return the filename */ - public URL getContentsUrl() { - return GitHubClient.parseURL(contents_url); + public String getFilename() { + return filename; } /** @@ -172,6 +143,35 @@ public String getPatch() { * @return the previous filename */ public String getPreviousFilename() { - return previous_filename; + return previousFilename; + } + + /** + * Gets raw url. + * + * @return the raw url + */ + public URL getRawUrl() { + return GitHubClient.parseURL(rawUrl); + } + + /** + * Gets sha of the file (not commit sha). + * + * @return the sha + * @see List pull requests + * files + */ + public String getSha() { + return sha; + } + + /** + * Gets status (added/modified/deleted). + * + * @return the status + */ + public String getStatus() { + return status; } } diff --git a/src/main/java/org/kohsuke/github/GHPullRequestQueryBuilder.java b/src/main/java/org/kohsuke/github/GHPullRequestQueryBuilder.java index 05f905e389..015451a77e 100644 --- a/src/main/java/org/kohsuke/github/GHPullRequestQueryBuilder.java +++ b/src/main/java/org/kohsuke/github/GHPullRequestQueryBuilder.java @@ -8,6 +8,21 @@ * @see GHRepository#queryPullRequests() GHRepository#queryPullRequests() */ public class GHPullRequestQueryBuilder extends GHQueryBuilder { + /** + * The enum Sort. + */ + public enum Sort { + + /** The created. */ + CREATED, + /** The long running. */ + LONG_RUNNING, + /** The popularity. */ + POPULARITY, + /** The updated. */ + UPDATED + } + private final GHRepository repo; /** @@ -22,14 +37,26 @@ public class GHPullRequestQueryBuilder extends GHQueryBuilder { } /** - * State gh pull request query builder. + * Base gh pull request query builder. * - * @param state - * the state + * @param base + * the base * @return the gh pull request query builder */ - public GHPullRequestQueryBuilder state(GHIssueState state) { - req.with("state", state); + public GHPullRequestQueryBuilder base(String base) { + req.with("base", base); + return this; + } + + /** + * Direction gh pull request query builder. + * + * @param d + * the d + * @return the gh pull request query builder + */ + public GHPullRequestQueryBuilder direction(GHDirection d) { + req.with("direction", d); return this; } @@ -49,14 +76,25 @@ public GHPullRequestQueryBuilder head(String head) { } /** - * Base gh pull request query builder. + * List. * - * @param base - * the base + * @return the paged iterable + */ + @Override + public PagedIterable list() { + return req.withUrlPath(repo.getApiTailUrl("pulls")) + .toIterable(GHPullRequest[].class, item -> item.wrapUp(repo)); + } + + /** + * Page size gh pull request query builder. + * + * @param pageSize + * the page size * @return the gh pull request query builder */ - public GHPullRequestQueryBuilder base(String base) { - req.with("base", base); + public GHPullRequestQueryBuilder pageSize(int pageSize) { + req.with("per_page", pageSize); return this; } @@ -73,40 +111,14 @@ public GHPullRequestQueryBuilder sort(Sort sort) { } /** - * The enum Sort. - */ - public enum Sort { - - /** The created. */ - CREATED, - /** The updated. */ - UPDATED, - /** The popularity. */ - POPULARITY, - /** The long running. */ - LONG_RUNNING - } - - /** - * Direction gh pull request query builder. + * State gh pull request query builder. * - * @param d - * the d + * @param state + * the state * @return the gh pull request query builder */ - public GHPullRequestQueryBuilder direction(GHDirection d) { - req.with("direction", d); + public GHPullRequestQueryBuilder state(GHIssueState state) { + req.with("state", state); return this; } - - /** - * List. - * - * @return the paged iterable - */ - @Override - public PagedIterable list() { - return req.withUrlPath(repo.getApiTailUrl("pulls")) - .toIterable(GHPullRequest[].class, item -> item.wrapUp(repo)); - } } diff --git a/src/main/java/org/kohsuke/github/GHPullRequestReview.java b/src/main/java/org/kohsuke/github/GHPullRequestReview.java index 6c97354dfd..69b720ab49 100644 --- a/src/main/java/org/kohsuke/github/GHPullRequestReview.java +++ b/src/main/java/org/kohsuke/github/GHPullRequestReview.java @@ -23,10 +23,12 @@ */ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.net.URL; +import java.time.Instant; import java.util.Date; import javax.annotation.CheckForNull; @@ -42,41 +44,231 @@ public class GHPullRequestReview extends GHObject { /** - * Create default GHPullRequestReview instance + * Represents a review comment as returned by the review comments endpoint. This is a limited view that does not + * include line-related fields such as {@code line}, {@code originalLine}, {@code side}, etc. + * + *

+ * To obtain the full {@link GHPullRequestReviewComment} with all fields, call + * {@link #readPullRequestReviewComment()}. + * + * @see GHPullRequest#listReviewComments() */ - public GHPullRequestReview() { - } + @SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD" }, justification = "JSON API") + public static class ReviewComment extends GHObject { - /** The owner. */ - GHPullRequest owner; + private GHCommentAuthorAssociation authorAssociation; + private String body; + private String commitId; + private String diffHunk; + private String htmlUrl; + private String originalCommitId; + private int originalPosition = -1; + private String path; + private int position = -1; + private Long pullRequestReviewId = -1L; + private String pullRequestUrl; + private GHPullRequestReviewCommentReactions reactions; + private GHUser user; + + GHPullRequest owner; + + /** + * Create default ReviewComment instance + */ + public ReviewComment() { + } + + /** + * Gets the author association to the project. + * + * @return the author association to the project + */ + public GHCommentAuthorAssociation getAuthorAssociation() { + return authorAssociation; + } + + /** + * The comment itself. + * + * @return the body + */ + public String getBody() { + return body; + } + + /** + * Gets commit id. + * + * @return the commit id + */ + public String getCommitId() { + return commitId; + } + + /** + * Gets diff hunk. + * + * @return the diff hunk + */ + public String getDiffHunk() { + return diffHunk; + } + + /** + * Gets the html url. + * + * @return the html url + */ + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); + } + + /** + * Gets commit id. + * + * @return the original commit id + */ + public String getOriginalCommitId() { + return originalCommitId; + } + + /** + * Gets original position. + * + * @return the original position + */ + public int getOriginalPosition() { + return originalPosition; + } + + /** + * Gets path. + * + * @return the path + */ + public String getPath() { + return path; + } + + /** + * Gets position. + * + * @return the position + */ + public int getPosition() { + return position; + } + + /** + * Gets The ID of the pull request review to which the comment belongs. + * + * @return {@link Long} the ID of the pull request review + */ + public Long getPullRequestReviewId() { + return pullRequestReviewId != null ? pullRequestReviewId : -1; + } + + /** + * Gets URL for the pull request that the review comment belongs to. + * + * @return {@link URL} the URL of the pull request + */ + public URL getPullRequestUrl() { + return GitHubClient.parseURL(pullRequestUrl); + } + + /** + * Gets the Reaction Rollup. + * + * @return {@link GHPullRequestReviewCommentReactions} the reaction rollup + */ + public GHPullRequestReviewCommentReactions getReactions() { + return reactions; + } + + /** + * Gets the user who posted this comment. + * + * @return the user + * @throws IOException + * the io exception + */ + public GHUser getUser() throws IOException { + return owner.root().getUser(user.getLogin()); + } + + /** + * Fetches the full {@link GHPullRequestReviewComment} from the API, which includes all fields such as + * {@link GHPullRequestReviewComment#getLine() line}, {@link GHPullRequestReviewComment#getOriginalLine() + * originalLine}, {@link GHPullRequestReviewComment#getSide() side}, etc. + * + * @return the full {@link GHPullRequestReviewComment} + * @throws IOException + * if an I/O error occurs + */ + public GHPullRequestReviewComment readPullRequestReviewComment() throws IOException { + return owner.root() + .createRequest() + .withUrlPath("/repos/" + owner.getRepository().getFullName() + "/pulls/comments/" + getId()) + .fetch(GHPullRequestReviewComment.class) + .wrapUp(owner); + } + + /** + * Wrap up. + * + * @param owner + * the owner + * @return the review comment + */ + ReviewComment wrapUp(GHPullRequest owner) { + this.owner = owner; + return this; + } + } private String body; - private GHUser user; - private String commit_id; + + private String commitId; + + private String htmlUrl; private GHPullRequestReviewState state; - private String submitted_at; - private String html_url; + private String submittedAt; + private GHUser user; + /** The owner. */ + GHPullRequest owner; + /** + * Create default GHPullRequestReview instance + */ + public GHPullRequestReview() { + } /** - * Wrap up. + * Deletes this review. * - * @param owner - * the owner - * @return the GH pull request review + * @throws IOException + * the io exception */ - GHPullRequestReview wrapUp(GHPullRequest owner) { - this.owner = owner; - return this; + public void delete() throws IOException { + owner.root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); } /** - * Gets the pull request to which this review is associated. + * Dismisses this review. * - * @return the parent + * @param message + * the message + * @throws IOException + * the io exception */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHPullRequest getParent() { - return owner; + public void dismiss(String message) throws IOException { + owner.root() + .createRequest() + .method("PUT") + .with("message", message) + .withUrlPath(getApiRoute() + "/dismissals") + .send(); + state = GHPullRequestReviewState.DISMISSED; } /** @@ -88,37 +280,26 @@ public String getBody() { return body; } - /** - * Gets the user who posted this review. - * - * @return the user - * @throws IOException - * the io exception - */ - public GHUser getUser() throws IOException { - if (user != null) { - return owner.root().getUser(user.getLogin()); - } - return null; - } - /** * Gets commit id. * * @return the commit id */ public String getCommitId() { - return commit_id; + return commitId; } /** - * Gets state. + * Since this method does not exist, we forward this value. * - * @return the state + * @return the created at + * @throws IOException + * Signals that an I/O exception has occurred. */ - @CheckForNull - public GHPullRequestReviewState getState() { - return state; + @Override + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getCreatedAt() throws IOException { + return getSubmittedAt(); } /** @@ -127,16 +308,27 @@ public GHPullRequestReviewState getState() { * @return the html url */ public URL getHtmlUrl() { - return GitHubClient.parseURL(html_url); + return GitHubClient.parseURL(htmlUrl); } /** - * Gets api route. + * Gets the pull request to which this review is associated. * - * @return the api route + * @return the parent */ - protected String getApiRoute() { - return owner.getApiRoute() + "/reviews/" + getId(); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHPullRequest getParent() { + return owner; + } + + /** + * Gets state. + * + * @return the state + */ + @CheckForNull + public GHPullRequestReviewState getState() { + return state; } /** @@ -144,20 +336,39 @@ protected String getApiRoute() { * * @return the submitted at */ - public Date getSubmittedAt() { - return GitHubClient.parseDate(submitted_at); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getSubmittedAt() { + return GitHubClient.parseInstant(submittedAt); } /** - * Since this method does not exist, we forward this value. + * Gets the user who posted this review. * - * @return the created at + * @return the user * @throws IOException - * Signals that an I/O exception has occurred. + * the io exception */ - @Override - public Date getCreatedAt() throws IOException { - return getSubmittedAt(); + public GHUser getUser() throws IOException { + return owner.root().intern(user); + } + + /** + * Obtains all the review comments associated with this pull request review. + * + *

+ * The GitHub API endpoint used by this method returns a limited set of fields. To obtain full comment data + * including line numbers, use {@link ReviewComment#readPullRequestReviewComment()} on individual comments, or use + * {@link GHPullRequest#listReviewComments()} instead. + * + * @return the paged iterable of {@link ReviewComment} objects + * @see GHPullRequest#listReviewComments() + * @see ReviewComment#readPullRequestReviewComment() + */ + public PagedIterable listReviewComments() { + return owner.root() + .createRequest() + .withUrlPath(getApiRoute() + "/comments") + .toIterable(ReviewComment[].class, item -> item.wrapUp(owner)); } /** @@ -183,42 +394,23 @@ public void submit(String body, GHPullRequestReviewEvent event) throws IOExcepti } /** - * Deletes this review. - * - * @throws IOException - * the io exception - */ - public void delete() throws IOException { - owner.root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); - } - - /** - * Dismisses this review. + * Gets api route. * - * @param message - * the message - * @throws IOException - * the io exception + * @return the api route */ - public void dismiss(String message) throws IOException { - owner.root() - .createRequest() - .method("PUT") - .with("message", message) - .withUrlPath(getApiRoute() + "/dismissals") - .send(); - state = GHPullRequestReviewState.DISMISSED; + protected String getApiRoute() { + return owner.getApiRoute() + "/reviews/" + getId(); } /** - * Obtains all the review comments associated with this pull request review. + * Wrap up. * - * @return the paged iterable + * @param owner + * the owner + * @return the GH pull request review */ - public PagedIterable listReviewComments() { - return owner.root() - .createRequest() - .withUrlPath(getApiRoute() + "/comments") - .toIterable(GHPullRequestReviewComment[].class, item -> item.wrapUp(owner)); + GHPullRequestReview wrapUp(GHPullRequest owner) { + this.owner = owner; + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHPullRequestReviewBuilder.java b/src/main/java/org/kohsuke/github/GHPullRequestReviewBuilder.java index c536dbfdc3..a971c53d70 100644 --- a/src/main/java/org/kohsuke/github/GHPullRequestReviewBuilder.java +++ b/src/main/java/org/kohsuke/github/GHPullRequestReviewBuilder.java @@ -12,131 +12,6 @@ * @see GHPullRequest#createReview() GHPullRequest#createReview() */ public class GHPullRequestReviewBuilder { - private final GHPullRequest pr; - private final Requester builder; - private final List comments = new ArrayList<>(); - - /** - * Instantiates a new GH pull request review builder. - * - * @param pr - * the pr - */ - GHPullRequestReviewBuilder(GHPullRequest pr) { - this.pr = pr; - this.builder = pr.root().createRequest(); - } - - // public GHPullRequestReview createReview(@Nullable String commitId, String body, GHPullRequestReviewEvent event, - // List comments) throws IOException - - /** - * The SHA of the commit that needs a review. Not using the latest commit SHA may render your review comment - * outdated if a subsequent commit modifies the line you specify as the position. Defaults to the most recent commit - * in the pull request when you do not specify a value. - * - * @param commitId - * the commit id - * @return the gh pull request review builder - */ - public GHPullRequestReviewBuilder commitId(String commitId) { - builder.with("commit_id", commitId); - return this; - } - - /** - * Required when using REQUEST_CHANGES or COMMENT for the event parameter. The body text of the pull request review. - * - * @param body - * the body - * @return the gh pull request review builder - */ - public GHPullRequestReviewBuilder body(String body) { - builder.with("body", body); - return this; - } - - /** - * The review action you want to perform. The review actions include: APPROVE, REQUEST_CHANGES, or COMMENT. By - * leaving this blank, you set the review action state to PENDING, which means you will need to - * {@linkplain GHPullRequestReview#submit(String, GHPullRequestReviewEvent) submit the pull request review} when you - * are ready. - * - * @param event - * the event - * @return the gh pull request review builder - */ - public GHPullRequestReviewBuilder event(GHPullRequestReviewEvent event) { - builder.with("event", event.action()); - return this; - } - - /** - * Comment gh pull request review builder. - * - * @param body - * Text of the review comment. - * @param path - * The relative path to the file that necessitates a review comment. - * @param position - * The position in the diff where you want to add a review comment. Note this value is not the same as - * the line number in the file. For help finding the position value, read the note below. - * @return the gh pull request review builder - */ - public GHPullRequestReviewBuilder comment(String body, String path, int position) { - comments.add(new DraftReviewComment(body, path, position)); - return this; - } - - /** - * Add a multi-line comment to the gh pull request review builder. - * - * @param body - * Text of the review comment. - * @param path - * The relative path to the file that necessitates a review comment. - * @param startLine - * The first line in the pull request diff that the multi-line comment applies to. - * @param endLine - * The last line of the range that the comment applies to. - * @return the gh pull request review builder - */ - public GHPullRequestReviewBuilder multiLineComment(String body, String path, int startLine, int endLine) { - this.comments.add(new MultilineDraftReviewComment(body, path, startLine, endLine)); - return this; - } - - /** - * Add a single line comment to the gh pull request review builder. - * - * @param body - * Text of the review comment. - * @param path - * The relative path to the file that necessitates a review comment. - * @param line - * The line of the blob in the pull request diff that the comment applies to. - * @return the gh pull request review builder - */ - public GHPullRequestReviewBuilder singleLineComment(String body, String path, int line) { - this.comments.add(new SingleLineDraftReviewComment(body, path, line)); - return this; - } - - /** - * Create gh pull request review. - * - * @return the gh pull request review - * @throws IOException - * the io exception - */ - public GHPullRequestReview create() throws IOException { - return builder.method("POST") - .with("comments", comments) - .withUrlPath(pr.getApiRoute() + "/reviews") - .fetch(GHPullRequestReview.class) - .wrapUp(pr); - } - /** * Common properties of the review comments, regardless of how the comment is positioned on the gh pull request. */ @@ -155,7 +30,6 @@ private interface ReviewComment { */ String getPath(); } - /** * Single line comment using the relative position in the diff. */ @@ -187,31 +61,26 @@ public int getPosition() { return position; } } - /** * Multi-line comment. */ static class MultilineDraftReviewComment implements ReviewComment { private final String body; - private final String path; private final int line; - private final int start_line; + private final String path; + private final int startLine; MultilineDraftReviewComment(final String body, final String path, final int startLine, final int line) { this.body = body; this.path = path; this.line = line; - this.start_line = startLine; + this.startLine = startLine; } public String getBody() { return this.body; } - public String getPath() { - return this.path; - } - /** * Gets end line of the comment. * @@ -221,13 +90,17 @@ public int getLine() { return line; } + public String getPath() { + return this.path; + } + /** * Gets start line of the comment. * * @return the start line of the comment. */ public int getStartLine() { - return start_line; + return startLine; } } @@ -236,8 +109,8 @@ public int getStartLine() { */ static class SingleLineDraftReviewComment implements ReviewComment { private final String body; - private final String path; private final int line; + private final String path; SingleLineDraftReviewComment(final String body, final String path, final int line) { this.body = body; @@ -249,10 +122,6 @@ public String getBody() { return this.body; } - public String getPath() { - return this.path; - } - /** * Gets line of the comment. * @@ -261,5 +130,136 @@ public String getPath() { public int getLine() { return line; } + + public String getPath() { + return this.path; + } + } + + // public GHPullRequestReview createReview(@Nullable String commitId, String body, GHPullRequestReviewEvent event, + // List comments) throws IOException + + private final Requester builder; + + private final List comments = new ArrayList<>(); + + private final GHPullRequest pr; + + /** + * Instantiates a new GH pull request review builder. + * + * @param pr + * the pr + */ + GHPullRequestReviewBuilder(GHPullRequest pr) { + this.pr = pr; + this.builder = pr.root().createRequest(); + } + + /** + * Required when using REQUEST_CHANGES or COMMENT for the event parameter. The body text of the pull request review. + * + * @param body + * the body + * @return the gh pull request review builder + */ + public GHPullRequestReviewBuilder body(String body) { + builder.with("body", body); + return this; + } + + /** + * Comment gh pull request review builder. + * + * @param body + * Text of the review comment. + * @param path + * The relative path to the file that necessitates a review comment. + * @param position + * The position in the diff where you want to add a review comment. Note this value is not the same as + * the line number in the file. For help finding the position value, read the note below. + * @return the gh pull request review builder + */ + public GHPullRequestReviewBuilder comment(String body, String path, int position) { + comments.add(new DraftReviewComment(body, path, position)); + return this; + } + + /** + * The SHA of the commit that needs a review. Not using the latest commit SHA may render your review comment + * outdated if a subsequent commit modifies the line you specify as the position. Defaults to the most recent commit + * in the pull request when you do not specify a value. + * + * @param commitId + * the commit id + * @return the gh pull request review builder + */ + public GHPullRequestReviewBuilder commitId(String commitId) { + builder.with("commit_id", commitId); + return this; + } + + /** + * Create gh pull request review. + * + * @return the gh pull request review + * @throws IOException + * the io exception + */ + public GHPullRequestReview create() throws IOException { + return builder.method("POST") + .with("comments", comments) + .withUrlPath(pr.getApiRoute() + "/reviews") + .fetch(GHPullRequestReview.class) + .wrapUp(pr); + } + + /** + * The review action you want to perform. The review actions include: APPROVE, REQUEST_CHANGES, or COMMENT. By + * leaving this blank, you set the review action state to PENDING, which means you will need to + * {@linkplain GHPullRequestReview#submit(String, GHPullRequestReviewEvent) submit the pull request review} when you + * are ready. + * + * @param event + * the event + * @return the gh pull request review builder + */ + public GHPullRequestReviewBuilder event(GHPullRequestReviewEvent event) { + builder.with("event", event.action()); + return this; + } + + /** + * Add a multi-line comment to the gh pull request review builder. + * + * @param body + * Text of the review comment. + * @param path + * The relative path to the file that necessitates a review comment. + * @param startLine + * The first line in the pull request diff that the multi-line comment applies to. + * @param endLine + * The last line of the range that the comment applies to. + * @return the gh pull request review builder + */ + public GHPullRequestReviewBuilder multiLineComment(String body, String path, int startLine, int endLine) { + this.comments.add(new MultilineDraftReviewComment(body, path, startLine, endLine)); + return this; + } + + /** + * Add a single line comment to the gh pull request review builder. + * + * @param body + * Text of the review comment. + * @param path + * The relative path to the file that necessitates a review comment. + * @param line + * The line of the blob in the pull request diff that the comment applies to. + * @return the gh pull request review builder + */ + public GHPullRequestReviewBuilder singleLineComment(String body, String path, int line) { + this.comments.add(new SingleLineDraftReviewComment(body, path, line)); + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHPullRequestReviewComment.java b/src/main/java/org/kohsuke/github/GHPullRequestReviewComment.java index 6c5e1a6808..753396e73e 100644 --- a/src/main/java/org/kohsuke/github/GHPullRequestReviewComment.java +++ b/src/main/java/org/kohsuke/github/GHPullRequestReviewComment.java @@ -29,8 +29,6 @@ import java.io.IOException; import java.net.URL; -import javax.annotation.CheckForNull; - // TODO: Auto-generated Javadoc /** * Review comment to the pull request. @@ -40,117 +38,103 @@ * @see GHPullRequest#createReviewComment(String, String, String, int) GHPullRequest#createReviewComment(String, String, * String, int) */ -public class GHPullRequestReviewComment extends GHObject implements Reactable { +public class GHPullRequestReviewComment extends GHIssueComment implements Refreshable { /** - * Create default GHPullRequestReviewComment instance + * The side of the diff to which the comment applies. + * + * @see Pull Request Review Comments API */ - public GHPullRequestReviewComment() { - } + public static enum Side { + /** Left side */ + LEFT, + /** Right side */ + RIGHT, + /** Unknown side */ + UNKNOWN; + + /** + * From. + * + * @param value + * the value + * @return the status + */ + public static Side from(String value) { + return EnumUtils.getEnumOrDefault(Side.class, value, Side.UNKNOWN); + } - /** The owner. */ - GHPullRequest owner; + } - private Long pull_request_review_id = -1L; - private String body; - private GHUser user; + // PR review comment specific fields (not in GHIssueComment) + private String commitId; + private String diffHunk; + private long inReplyToId = -1L; + private int line = -1; + private String originalCommitId; + private int originalLine = -1; + private int originalPosition = -1; + private Integer originalStartLine; private String path; - private String html_url; - private String pull_request_url; private int position = -1; - private int original_position = -1; - private long in_reply_to_id = -1L; - private Integer start_line = -1; - private Integer original_start_line = -1; - private String start_side; - private int line = -1; - private int original_line = -1; - private String side; - private String diff_hunk; - private String commit_id; - private String original_commit_id; - private String body_html; - private String body_text; + private Long pullRequestReviewId; + private String pullRequestUrl; private GHPullRequestReviewCommentReactions reactions; - private GHCommentAuthorAssociation author_association; - - /** - * Wrap up. - * - * @param owner - * the owner - * @return the GH pull request review comment - */ - GHPullRequestReviewComment wrapUp(GHPullRequest owner) { - this.owner = owner; - return this; - } + private String side; + private Integer startLine; + private String startSide; /** - * Gets the pull request to which this review comment is associated. - * - * @return the parent + * Create default GHPullRequestReviewComment instance */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHPullRequest getParent() { - return owner; + public GHPullRequestReviewComment() { } /** - * The comment itself. + * Creates the reaction. * - * @return the body + * @param content + * the content + * @return the GH reaction + * @throws IOException + * Signals that an I/O exception has occurred. */ - public String getBody() { - return body; + @Override + public GHReaction createReaction(ReactionContent content) throws IOException { + return owner.root() + .createRequest() + .method("POST") + .with("content", content.getContent()) + .withUrlPath(getApiRoute() + "/reactions") + .fetch(GHReaction.class); } /** - * Gets the user who posted this comment. + * Deletes this review comment. * - * @return the user * @throws IOException * the io exception */ - public GHUser getUser() throws IOException { - return owner.root().getUser(user.getLogin()); - } - - /** - * Gets path. - * - * @return the path - */ - public String getPath() { - return path; - } - - /** - * Gets position. - * - * @return the position - */ - @CheckForNull - public int getPosition() { - return position; - } - - /** - * Gets original position. - * - * @return the original position - */ - public int getOriginalPosition() { - return original_position; + @Override + public void delete() throws IOException { + owner.root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); } /** - * Gets diff hunk. + * Delete reaction. * - * @return the diff hunk + * @param reaction + * the reaction + * @throws IOException + * Signals that an I/O exception has occurred. */ - public String getDiffHunk() { - return diff_hunk; + @Override + public void deleteReaction(GHReaction reaction) throws IOException { + owner.root() + .createRequest() + .method("DELETE") + .withUrlPath(getApiRoute(), "reactions", String.valueOf(reaction.getId())) + .send(); } /** @@ -159,121 +143,117 @@ public String getDiffHunk() { * @return the commit id */ public String getCommitId() { - return commit_id; + return commitId; } /** - * Gets commit id. - * - * @return the commit id - */ - public String getOriginalCommitId() { - return original_commit_id; - } - - /** - * Gets the author association to the project. + * Gets diff hunk. * - * @return the author association to the project + * @return the diff hunk */ - public GHCommentAuthorAssociation getAuthorAssociation() { - return author_association; + public String getDiffHunk() { + return diffHunk; } /** * Gets in reply to id. * - * @return the in reply to id + * @return the in reply to id, or -1 if not a reply */ - @CheckForNull public long getInReplyToId() { - return in_reply_to_id; + return inReplyToId; } /** - * Gets the html url. + * Gets The line of the blob to which the comment applies. The last line of the range for a multi-line comment. * - * @return the html url + *

+ * This field is not available on {@link GHPullRequestReview.ReviewComment} objects returned by + * {@link GHPullRequestReview#listReviewComments()}. Use + * {@link GHPullRequestReview.ReviewComment#readPullRequestReviewComment()} or + * {@link GHPullRequest#listReviewComments()} to obtain this value. + * + * @return the line to which the comment applies, or -1 if not available */ - public URL getHtmlUrl() { - return GitHubClient.parseURL(html_url); + public int getLine() { + return line; } /** - * Gets api route. + * Gets commit id. * - * @return the api route + * @return the commit id */ - protected String getApiRoute() { - return getApiRoute(false); + public String getOriginalCommitId() { + return originalCommitId; } /** - * Gets api route. + * Gets The line of the blob to which the comment applies. The last line of the range for a multi-line comment. * - * @param includePullNumber - * if true, includes the owning pull request's number in the route. + *

+ * This field is not available on {@link GHPullRequestReview.ReviewComment} objects returned by + * {@link GHPullRequestReview#listReviewComments()}. Use + * {@link GHPullRequestReview.ReviewComment#readPullRequestReviewComment()} or + * {@link GHPullRequest#listReviewComments()} to obtain this value. * - * @return the api route + * @return the line to which the comment applies, or -1 if not available */ - protected String getApiRoute(boolean includePullNumber) { - return "/repos/" + owner.getRepository().getFullName() + "/pulls" - + (includePullNumber ? "/" + owner.getNumber() : "") + "/comments/" + getId(); + public int getOriginalLine() { + return originalLine; } /** - * Gets The first line of the range for a multi-line comment. + * Gets original position. * - * @return the start line + * @return the original position */ - public int getStartLine() { - return start_line != null ? start_line : -1; + public int getOriginalPosition() { + return originalPosition; } /** * Gets The first line of the range for a multi-line comment. * - * @return the original start line - */ - public int getOriginalStartLine() { - return original_start_line != null ? original_start_line : -1; - } - - /** - * Gets The side of the first line of the range for a multi-line comment. + *

+ * This field is not available on {@link GHPullRequestReview.ReviewComment} objects returned by + * {@link GHPullRequestReview#listReviewComments()}. Use + * {@link GHPullRequestReview.ReviewComment#readPullRequestReviewComment()} or + * {@link GHPullRequest#listReviewComments()} to obtain this value. * - * @return {@link Side} the side of the first line + * @return the original start line, or -1 if not available or not a multi-line comment */ - public Side getStartSide() { - return Side.from(start_side); + public int getOriginalStartLine() { + return originalStartLine != null ? originalStartLine : -1; } /** - * Gets The line of the blob to which the comment applies. The last line of the range for a multi-line comment. + * Gets the pull request to which this review comment is associated. * - * @return the line to which the comment applies + * @return the parent pull request */ - public int getLine() { - return line; + @Override + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHPullRequest getParent() { + return (GHPullRequest) owner; } /** - * Gets The line of the blob to which the comment applies. The last line of the range for a multi-line comment. + * Gets path. * - * @return the line to which the comment applies + * @return the path */ - public int getOriginalLine() { - return original_line; + public String getPath() { + return path; } /** - * Gets The side of the diff to which the comment applies. The side of the last line of the range for a multi-line - * comment + * Gets position. * - * @return {@link Side} the side if the diff to which the comment applies + * @return the position, or -1 if not available */ - public Side getSide() { - return Side.from(side); + public int getPosition() { + return position; } /** @@ -282,7 +262,7 @@ public Side getSide() { * @return {@link Long} the ID of the pull request review */ public Long getPullRequestReviewId() { - return pull_request_review_id != null ? pull_request_review_id : -1; + return pullRequestReviewId != null ? pullRequestReviewId : -1; } /** @@ -291,81 +271,104 @@ public Long getPullRequestReviewId() { * @return {@link URL} the URL of the pull request */ public URL getPullRequestUrl() { - return GitHubClient.parseURL(pull_request_url); + return GitHubClient.parseURL(pullRequestUrl); } /** - * Gets The body in html format. + * Gets the Reaction Rollup * - * @return {@link String} the body in html format + * @return {@link GHPullRequestReviewCommentReactions} the reaction rollup */ - public String getBodyHtml() { - return body_html; + public GHPullRequestReviewCommentReactions getReactions() { + return reactions; } /** - * Gets The body text. + * Gets The side of the diff to which the comment applies. The side of the last line of the range for a multi-line + * comment. * - * @return {@link String} the body text + *

+ * This field is not available on {@link GHPullRequestReview.ReviewComment} objects returned by + * {@link GHPullRequestReview#listReviewComments()}. Use + * {@link GHPullRequestReview.ReviewComment#readPullRequestReviewComment()} or + * {@link GHPullRequest#listReviewComments()} to obtain this value. + * + * @return {@link Side} the side of the diff to which the comment applies, or {@link Side#UNKNOWN} if not available */ - public String getBodyText() { - return body_text; + public Side getSide() { + return Side.from(side); } /** - * Gets the Reaction Rollup + * Gets The first line of the range for a multi-line comment. * - * @return {@link GHPullRequestReviewCommentReactions} the reaction rollup + *

+ * This field is not available on {@link GHPullRequestReview.ReviewComment} objects returned by + * {@link GHPullRequestReview#listReviewComments()}. Use + * {@link GHPullRequestReview.ReviewComment#readPullRequestReviewComment()} or + * {@link GHPullRequest#listReviewComments()} to obtain this value. + * + * @return the start line, or -1 if not available or not a multi-line comment */ - public GHPullRequestReviewCommentReactions getReactions() { - return reactions; + public int getStartLine() { + return startLine != null ? startLine : -1; } /** - * The side of the diff to which the comment applies + * Gets The side of the first line of the range for a multi-line comment. + * + *

+ * This field is not available on {@link GHPullRequestReview.ReviewComment} objects returned by + * {@link GHPullRequestReview#listReviewComments()}. Use + * {@link GHPullRequestReview.ReviewComment#readPullRequestReviewComment()} or + * {@link GHPullRequest#listReviewComments()} to obtain this value. + * + * @return {@link Side} the side of the first line, or {@link Side#UNKNOWN} if not available */ - public static enum Side { - /** Right side */ - RIGHT, - /** Left side */ - LEFT, - /** Unknown side */ - UNKNOWN; - - /** - * From. - * - * @param value - * the value - * @return the status - */ - public static Side from(String value) { - return EnumUtils.getEnumOrDefault(Side.class, value, Side.UNKNOWN); - } - + public Side getStartSide() { + return Side.from(startSide); } /** - * Updates the comment. + * Gets the user who posted this comment. * - * @param body - * the body + * @return the user * @throws IOException * the io exception */ - public void update(String body) throws IOException { - owner.root().createRequest().method("PATCH").with("body", body).withUrlPath(getApiRoute()).fetchInto(this); - this.body = body; + @Override + public GHUser getUser() throws IOException { + return owner.root().intern(user); } /** - * Deletes this review comment. + * List reactions. + * + * @return the paged iterable + */ + @Override + public PagedIterable listReactions() { + return owner.root() + .createRequest() + .withUrlPath(getApiRoute() + "/reactions") + .toIterable(GHReaction[].class, item -> owner.root()); + } + + /** + * Refreshes this comment by fetching the full data from the API. + * + *

+ * This is useful when the comment was obtained via {@link GHPullRequestReview#listReviewComments()}, which uses a + * GitHub API endpoint that does not return all fields. After calling this method, fields like {@link #getLine()}, + * {@link #getOriginalLine()}, {@link #getSide()}, etc. will return their actual values. * * @throws IOException - * the io exception + * if an I/O error occurs + * @see GHPullRequest#listReviewComments() */ - public void delete() throws IOException { - owner.root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); + @Override + public void refresh() throws IOException { + owner.root().createRequest().withUrlPath(getApiRoute()).fetchInto(this).wrapUp(getParent()); } /** @@ -384,52 +387,54 @@ public GHPullRequestReviewComment reply(String body) throws IOException { .with("body", body) .withUrlPath(getApiRoute(true) + "/replies") .fetch(GHPullRequestReviewComment.class) - .wrapUp(owner); + .wrapUp(getParent()); } /** - * Creates the reaction. + * Updates the comment. * - * @param content - * the content - * @return the GH reaction + * @param body + * the body * @throws IOException - * Signals that an I/O exception has occurred. + * the io exception */ - public GHReaction createReaction(ReactionContent content) throws IOException { - return owner.root() - .createRequest() - .method("POST") - .with("content", content.getContent()) - .withUrlPath(getApiRoute() + "/reactions") - .fetch(GHReaction.class); + @Override + public void update(String body) throws IOException { + owner.root().createRequest().method("PATCH").with("body", body).withUrlPath(getApiRoute()).fetchInto(this); + this.body = body; } /** - * Delete reaction. + * Gets api route. * - * @param reaction - * the reaction - * @throws IOException - * Signals that an I/O exception has occurred. + * @return the api route */ - public void deleteReaction(GHReaction reaction) throws IOException { - owner.root() - .createRequest() - .method("DELETE") - .withUrlPath(getApiRoute(), "reactions", String.valueOf(reaction.getId())) - .send(); + protected String getApiRoute() { + return getApiRoute(false); } /** - * List reactions. + * Gets api route. * - * @return the paged iterable + * @param includePullNumber + * if true, includes the owning pull request's number in the route. + * + * @return the api route */ - public PagedIterable listReactions() { - return owner.root() - .createRequest() - .withUrlPath(getApiRoute() + "/reactions") - .toIterable(GHReaction[].class, item -> owner.root()); + protected String getApiRoute(boolean includePullNumber) { + return "/repos/" + owner.getRepository().getFullName() + "/pulls" + + (includePullNumber ? "/" + owner.getNumber() : "") + "/comments/" + getId(); + } + + /** + * Wrap up. + * + * @param pullRequest + * the pull request owner + * @return the GH pull request review comment + */ + GHPullRequestReviewComment wrapUp(GHPullRequest pullRequest) { + this.owner = pullRequest; + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHPullRequestReviewCommentBuilder.java b/src/main/java/org/kohsuke/github/GHPullRequestReviewCommentBuilder.java index a3b267c9f9..82c1fe4f88 100644 --- a/src/main/java/org/kohsuke/github/GHPullRequestReviewCommentBuilder.java +++ b/src/main/java/org/kohsuke/github/GHPullRequestReviewCommentBuilder.java @@ -10,8 +10,8 @@ * @see GHPullRequest#createReviewComment() */ public class GHPullRequestReviewCommentBuilder { - private final GHPullRequest pr; private final Requester builder; + private final GHPullRequest pr; /** * Instantiates a new GH pull request review comment builder. @@ -24,20 +24,6 @@ public class GHPullRequestReviewCommentBuilder { this.builder = pr.root().createRequest(); } - /** - * The SHA of the commit that needs a review. Not using the latest commit SHA may render your review comment - * outdated if a subsequent commit modifies the line you specify as the position. Defaults to the most recent commit - * in the pull request when you do not specify a value. - * - * @param commitId - * the commit id - * @return the gh pull request review comment builder - */ - public GHPullRequestReviewCommentBuilder commitId(String commitId) { - builder.with("commit_id", commitId); - return this; - } - /** * The text of the pull request review comment. * @@ -51,29 +37,31 @@ public GHPullRequestReviewCommentBuilder body(String body) { } /** - * The relative path to the file that necessitates a comment. + * The SHA of the commit that needs a review. Not using the latest commit SHA may render your review comment + * outdated if a subsequent commit modifies the line you specify as the position. Defaults to the most recent commit + * in the pull request when you do not specify a value. * - * @param path - * the path + * @param commitId + * the commit id * @return the gh pull request review comment builder */ - public GHPullRequestReviewCommentBuilder path(String path) { - builder.with("path", path); + public GHPullRequestReviewCommentBuilder commitId(String commitId) { + builder.with("commit_id", commitId); return this; } /** - * The position in the diff where you want to add a review comment. + * Create gh pull request review comment. * - * @param position - * the position * @return the gh pull request review comment builder - * @implNote As position is deprecated in GitHub API, only keep this for internal usage (for retro-compatibility - * with {@link GHPullRequest#createReviewComment(String, String, String, int)}). + * @throws IOException + * the io exception */ - GHPullRequestReviewCommentBuilder position(int position) { - builder.with("position", position); - return this; + public GHPullRequestReviewComment create() throws IOException { + return builder.method("POST") + .withUrlPath(pr.getApiRoute() + "/comments") + .fetch(GHPullRequestReviewComment.class) + .wrapUp(pr); } /** @@ -111,17 +99,66 @@ public GHPullRequestReviewCommentBuilder lines(int startLine, int endLine) { } /** - * Create gh pull request review comment. + * The relative path to the file that necessitates a comment. * + * @param path + * the path * @return the gh pull request review comment builder - * @throws IOException - * the io exception */ - public GHPullRequestReviewComment create() throws IOException { - return builder.method("POST") - .withUrlPath(pr.getApiRoute() + "/comments") - .fetch(GHPullRequestReviewComment.class) - .wrapUp(pr); + public GHPullRequestReviewCommentBuilder path(String path) { + builder.with("path", path); + return this; + } + + /** + * The side of the diff in the pull request that the comment applies to. + *

+ * {@link #side(GHPullRequestReviewComment.Side)} and + * {@link #sides(GHPullRequestReviewComment.Side, GHPullRequestReviewComment.Side)} will overwrite each other's + * values. + * + * @param side + * side of the diff to which the comment applies + * @return the gh pull request review comment builder + */ + public GHPullRequestReviewCommentBuilder side(GHPullRequestReviewComment.Side side) { + builder.with("side", side); + builder.remove("start_side"); + return this; + } + + /** + * The sides of the diff in the pull request that the comment applies to. + *

+ * {@link #side(GHPullRequestReviewComment.Side)} and + * {@link #sides(GHPullRequestReviewComment.Side, GHPullRequestReviewComment.Side)} will overwrite each other's + * values. + * + * @param startSide + * side of the diff to which the start of the comment applies + * @param endSide + * side of the diff to which the end of the comment applies + * @return the gh pull request review comment builder + */ + public GHPullRequestReviewCommentBuilder sides(GHPullRequestReviewComment.Side startSide, + GHPullRequestReviewComment.Side endSide) { + builder.with("start_side", startSide); + builder.with("side", endSide); + return this; + } + + /** + * The position in the diff where you want to add a review comment. + * + * @param position + * the position + * @return the gh pull request review comment builder + * @implNote As position is deprecated in GitHub API, only keep this for internal usage (for retro-compatibility + * with {@link GHPullRequest#createReviewComment(String, String, String, int)}). + */ + GHPullRequestReviewCommentBuilder position(int position) { + builder.with("position", position); + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHPullRequestReviewCommentReactions.java b/src/main/java/org/kohsuke/github/GHPullRequestReviewCommentReactions.java index 8a9624d694..d460872c9c 100644 --- a/src/main/java/org/kohsuke/github/GHPullRequestReviewCommentReactions.java +++ b/src/main/java/org/kohsuke/github/GHPullRequestReviewCommentReactions.java @@ -14,60 +14,60 @@ */ public class GHPullRequestReviewCommentReactions { - /** - * Create default GHPullRequestReviewCommentReactions instance - */ - public GHPullRequestReviewCommentReactions() { - } + private int confused = -1; - private String url; + private int eyes = -1; - private int total_count = -1; - @JsonProperty("+1") - private int plus_one = -1; - @JsonProperty("-1") - private int minus_one = -1; - private int laugh = -1; - private int confused = -1; private int heart = -1; private int hooray = -1; - private int eyes = -1; + private int laugh = -1; + @JsonProperty("-1") + private int minusOne = -1; + @JsonProperty("+1") + private int plusOne = -1; private int rocket = -1; + private int totalCount = -1; + private String url; + /** + * Create default GHPullRequestReviewCommentReactions instance + */ + public GHPullRequestReviewCommentReactions() { + } /** - * Gets the URL of the comment's reactions + * Gets the number of confused reactions * - * @return the URL of the comment's reactions + * @return the number of confused reactions */ - public URL getUrl() { - return GitHubClient.parseURL(url); + public int getConfused() { + return confused; } /** - * Gets the total count of reactions + * Gets the number of eyes reactions * - * @return the number of total reactions + * @return the number of eyes reactions */ - public int getTotalCount() { - return total_count; + public int getEyes() { + return eyes; } /** - * Gets the number of +1 reactions + * Gets the number of heart reactions * - * @return the number of +1 reactions + * @return the number of heart reactions */ - public int getPlusOne() { - return plus_one; + public int getHeart() { + return heart; } /** - * Gets the number of -1 reactions + * Gets the number of hooray reactions * - * @return the number of -1 reactions + * @return the number of hooray reactions */ - public int getMinusOne() { - return minus_one; + public int getHooray() { + return hooray; } /** @@ -80,47 +80,47 @@ public int getLaugh() { } /** - * Gets the number of confused reactions + * Gets the number of -1 reactions * - * @return the number of confused reactions + * @return the number of -1 reactions */ - public int getConfused() { - return confused; + public int getMinusOne() { + return minusOne; } /** - * Gets the number of heart reactions + * Gets the number of +1 reactions * - * @return the number of heart reactions + * @return the number of +1 reactions */ - public int getHeart() { - return heart; + public int getPlusOne() { + return plusOne; } /** - * Gets the number of hooray reactions + * Gets the number of rocket reactions * - * @return the number of hooray reactions + * @return the number of rocket reactions */ - public int getHooray() { - return hooray; + public int getRocket() { + return rocket; } /** - * Gets the number of eyes reactions + * Gets the total count of reactions * - * @return the number of eyes reactions + * @return the number of total reactions */ - public int getEyes() { - return eyes; + public int getTotalCount() { + return totalCount; } /** - * Gets the number of rocket reactions + * Gets the URL of the comment's reactions * - * @return the number of rocket reactions + * @return the URL of the comment's reactions */ - public int getRocket() { - return rocket; + public URL getUrl() { + return GitHubClient.parseURL(url); } } diff --git a/src/main/java/org/kohsuke/github/GHPullRequestReviewEvent.java b/src/main/java/org/kohsuke/github/GHPullRequestReviewEvent.java index 025de45cad..8bffc057e8 100644 --- a/src/main/java/org/kohsuke/github/GHPullRequestReviewEvent.java +++ b/src/main/java/org/kohsuke/github/GHPullRequestReviewEvent.java @@ -29,14 +29,14 @@ */ public enum GHPullRequestReviewEvent { - /** The pending. */ - PENDING, /** The approve. */ APPROVE, - /** The request changes. */ - REQUEST_CHANGES, /** The comment. */ - COMMENT; + COMMENT, + /** The pending. */ + PENDING, + /** The request changes. */ + REQUEST_CHANGES; /** * Action. diff --git a/src/main/java/org/kohsuke/github/GHPullRequestReviewState.java b/src/main/java/org/kohsuke/github/GHPullRequestReviewState.java index 3d755f9555..e90b07d369 100644 --- a/src/main/java/org/kohsuke/github/GHPullRequestReviewState.java +++ b/src/main/java/org/kohsuke/github/GHPullRequestReviewState.java @@ -6,9 +6,6 @@ */ public enum GHPullRequestReviewState { - /** The pending. */ - PENDING, - /** The approved. */ APPROVED, @@ -19,7 +16,10 @@ public enum GHPullRequestReviewState { COMMENTED, /** The dismissed. */ - DISMISSED; + DISMISSED, + + /** The pending. */ + PENDING; /** * Action string. diff --git a/src/main/java/org/kohsuke/github/GHPullRequestSearchBuilder.java b/src/main/java/org/kohsuke/github/GHPullRequestSearchBuilder.java index 970bfb34d2..143f6e6ae2 100644 --- a/src/main/java/org/kohsuke/github/GHPullRequestSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHPullRequestSearchBuilder.java @@ -11,6 +11,32 @@ * issues and PRs */ public class GHPullRequestSearchBuilder extends GHSearchBuilder { + /** + * The sort order values. + */ + public enum Sort { + + /** The comments. */ + COMMENTS, + /** The created. */ + CREATED, + /** The relevance. */ + RELEVANCE, + /** The updated. */ + UPDATED + + } + + private static class PullRequestSearchResult extends SearchResult { + + private GHPullRequest[] items; + + @Override + GHPullRequest[] getItems(GitHub root) { + return items; + } + } + /** * Instantiates a new GH search builder. * @@ -22,14 +48,14 @@ public class GHPullRequestSearchBuilder extends GHSearchBuilder { } /** - * Repository gh pull request search builder. + * Assigned to gh pull request user. * - * @param repository - * the repository + * @param u + * the gh user * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder repo(GHRepository repository) { - q("repo", repository.getFullName()); + public GHPullRequestSearchBuilder assigned(GHUser u) { + q("assignee", u.getLogin()); return this; } @@ -46,120 +72,125 @@ public GHPullRequestSearchBuilder author(GHUser user) { } /** - * CreatedByMe gh pull request search builder. + * Base gh pull request search builder. * + * @param branch + * the base branch * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder createdByMe() { - q("author:@me"); + public GHPullRequestSearchBuilder base(GHBranch branch) { + q("base", branch.getName()); return this; } /** - * Assigned to gh pull request user. + * Closed gh pull request search builder. * - * @param u - * the gh user + * @param closed + * the closed * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder assigned(GHUser u) { - q("assignee", u.getLogin()); + public GHPullRequestSearchBuilder closed(LocalDate closed) { + q("closed", closed.format(DateTimeFormatter.ISO_DATE)); return this; } /** - * Mentions gh pull request search builder. + * Closed gh pull request search builder. * - * @param u - * the gh user + * @param from + * the closed starting from + * @param to + * the closed ending to * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder mentions(GHUser u) { - q("mentions", u.getLogin()); + public GHPullRequestSearchBuilder closed(LocalDate from, LocalDate to) { + String closedRange = from.format(DateTimeFormatter.ISO_DATE) + ".." + to.format(DateTimeFormatter.ISO_DATE); + q("closed", closedRange); return this; } /** - * Is open gh pull request search builder. - * - * @return the gh pull request search builder - */ - public GHPullRequestSearchBuilder isOpen() { - return q("is:open"); - } - - /** - * Is closed gh pull request search builder. - * - * @return the gh pull request search builder - */ - public GHPullRequestSearchBuilder isClosed() { - return q("is:closed"); - } - - /** - * Is merged gh pull request search builder. + * ClosedAfter gh pull request search builder. * + * @param closed + * the closed + * @param inclusive + * whether to include date * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder isMerged() { - return q("is:merged"); + public GHPullRequestSearchBuilder closedAfter(LocalDate closed, boolean inclusive) { + String comparisonSign = inclusive ? ">=" : ">"; + q("closed:" + comparisonSign + closed.format(DateTimeFormatter.ISO_DATE)); + return this; } /** - * Is draft gh pull request search builder. + * ClosedBefore gh pull request search builder. * + * @param closed + * the closed + * @param inclusive + * whether to include date * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder isDraft() { - return q("draft:true"); + public GHPullRequestSearchBuilder closedBefore(LocalDate closed, boolean inclusive) { + String comparisonSign = inclusive ? "<=" : "<"; + q("closed:" + comparisonSign + closed.format(DateTimeFormatter.ISO_DATE)); + return this; } /** - * Head gh pull request search builder. + * Commit gh pull request search builder. * - * @param branch - * the head branch + * @param sha + * the commit SHA * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder head(GHBranch branch) { - q("head", branch.getName()); + public GHPullRequestSearchBuilder commit(String sha) { + q("SHA", sha); return this; } /** - * Base gh pull request search builder. + * Created gh pull request search builder. * - * @param branch - * the base branch + * @param created + * the createdAt * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder base(GHBranch branch) { - q("base", branch.getName()); + public GHPullRequestSearchBuilder created(LocalDate created) { + q("created", created.format(DateTimeFormatter.ISO_DATE)); return this; } /** - * Commit gh pull request search builder. + * Created gh pull request search builder. * - * @param sha - * the commit SHA + * @param from + * the createdAt starting from + * @param to + * the createdAt ending to * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder commit(String sha) { - q("SHA", sha); + public GHPullRequestSearchBuilder created(LocalDate from, LocalDate to) { + String createdRange = from.format(DateTimeFormatter.ISO_DATE) + ".." + to.format(DateTimeFormatter.ISO_DATE); + q("created", createdRange); return this; } /** - * Created gh pull request search builder. + * CreatedAfter gh pull request search builder. * * @param created * the createdAt + * @param inclusive + * whether to include date * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder created(LocalDate created) { - q("created", created.format(DateTimeFormatter.ISO_DATE)); + public GHPullRequestSearchBuilder createdAfter(LocalDate created, boolean inclusive) { + String comparisonSign = inclusive ? ">=" : ">"; + q("created:" + comparisonSign + created.format(DateTimeFormatter.ISO_DATE)); return this; } @@ -179,227 +210,201 @@ public GHPullRequestSearchBuilder createdBefore(LocalDate created, boolean inclu } /** - * CreatedAfter gh pull request search builder. + * CreatedByMe gh pull request search builder. * - * @param created - * the createdAt - * @param inclusive - * whether to include date * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder createdAfter(LocalDate created, boolean inclusive) { - String comparisonSign = inclusive ? ">=" : ">"; - q("created:" + comparisonSign + created.format(DateTimeFormatter.ISO_DATE)); + public GHPullRequestSearchBuilder createdByMe() { + q("author:@me"); return this; } /** - * Created gh pull request search builder. + * Head gh pull request search builder. * - * @param from - * the createdAt starting from - * @param to - * the createdAt ending to + * @param branch + * the head branch * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder created(LocalDate from, LocalDate to) { - String createdRange = from.format(DateTimeFormatter.ISO_DATE) + ".." + to.format(DateTimeFormatter.ISO_DATE); - q("created", createdRange); + public GHPullRequestSearchBuilder head(GHBranch branch) { + q("head", branch.getName()); return this; } /** - * Merged gh pull request search builder. + * Labels gh pull request search builder. * - * @param merged - * the merged + * @param labels + * the labels * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder merged(LocalDate merged) { - q("merged", merged.format(DateTimeFormatter.ISO_DATE)); + public GHPullRequestSearchBuilder inLabels(Iterable labels) { + q("label", String.join(",", labels)); return this; } /** - * MergedBefore gh pull request search builder. + * Is closed gh pull request search builder. * - * @param merged - * the merged - * @param inclusive - * whether to include date * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder mergedBefore(LocalDate merged, boolean inclusive) { - String comparisonSign = inclusive ? "<=" : "<"; - q("merged:" + comparisonSign + merged.format(DateTimeFormatter.ISO_DATE)); - return this; + public GHPullRequestSearchBuilder isClosed() { + return q("is:closed"); } /** - * MergedAfter gh pull request search builder. + * Is draft gh pull request search builder. * - * @param merged - * the merged - * @param inclusive - * whether to include date * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder mergedAfter(LocalDate merged, boolean inclusive) { - String comparisonSign = inclusive ? ">=" : ">"; - q("merged:" + comparisonSign + merged.format(DateTimeFormatter.ISO_DATE)); - return this; + public GHPullRequestSearchBuilder isDraft() { + return q("draft:true"); } /** - * Merged gh pull request search builder. + * Is merged gh pull request search builder. * - * @param from - * the merged starting from - * @param to - * the merged ending to * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder merged(LocalDate from, LocalDate to) { - String mergedRange = from.format(DateTimeFormatter.ISO_DATE) + ".." + to.format(DateTimeFormatter.ISO_DATE); - q("merged", mergedRange); - return this; + public GHPullRequestSearchBuilder isMerged() { + return q("is:merged"); } /** - * Closed gh pull request search builder. + * Is open gh pull request search builder. * - * @param closed - * the closed * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder closed(LocalDate closed) { - q("closed", closed.format(DateTimeFormatter.ISO_DATE)); - return this; + public GHPullRequestSearchBuilder isOpen() { + return q("is:open"); } /** - * ClosedBefore gh pull request search builder. + * Label gh pull request search builder. * - * @param closed - * the closed - * @param inclusive - * whether to include date + * @param label + * the label * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder closedBefore(LocalDate closed, boolean inclusive) { - String comparisonSign = inclusive ? "<=" : "<"; - q("closed:" + comparisonSign + closed.format(DateTimeFormatter.ISO_DATE)); + public GHPullRequestSearchBuilder label(String label) { + q("label", label); return this; } + @Override + public PagedSearchIterable list() { + this.q("is:pr"); + return super.list(); + } + /** - * ClosedAfter gh pull request search builder. + * Mentions gh pull request search builder. * - * @param closed - * the closed - * @param inclusive - * whether to include date + * @param u + * the gh user * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder closedAfter(LocalDate closed, boolean inclusive) { - String comparisonSign = inclusive ? ">=" : ">"; - q("closed:" + comparisonSign + closed.format(DateTimeFormatter.ISO_DATE)); + public GHPullRequestSearchBuilder mentions(GHUser u) { + q("mentions", u.getLogin()); return this; } /** - * Closed gh pull request search builder. + * Merged gh pull request search builder. * - * @param from - * the closed starting from - * @param to - * the closed ending to + * @param merged + * the merged * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder closed(LocalDate from, LocalDate to) { - String closedRange = from.format(DateTimeFormatter.ISO_DATE) + ".." + to.format(DateTimeFormatter.ISO_DATE); - q("closed", closedRange); + public GHPullRequestSearchBuilder merged(LocalDate merged) { + q("merged", merged.format(DateTimeFormatter.ISO_DATE)); return this; } /** - * Updated gh pull request search builder. + * Merged gh pull request search builder. * - * @param updated - * the updated + * @param from + * the merged starting from + * @param to + * the merged ending to * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder updated(LocalDate updated) { - q("updated", updated.format(DateTimeFormatter.ISO_DATE)); + public GHPullRequestSearchBuilder merged(LocalDate from, LocalDate to) { + String mergedRange = from.format(DateTimeFormatter.ISO_DATE) + ".." + to.format(DateTimeFormatter.ISO_DATE); + q("merged", mergedRange); return this; } /** - * UpdatedBefore gh pull request search builder. + * MergedAfter gh pull request search builder. * - * @param updated - * the updated + * @param merged + * the merged * @param inclusive * whether to include date * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder updatedBefore(LocalDate updated, boolean inclusive) { - String comparisonSign = inclusive ? "<=" : "<"; - q("updated:" + comparisonSign + updated.format(DateTimeFormatter.ISO_DATE)); + public GHPullRequestSearchBuilder mergedAfter(LocalDate merged, boolean inclusive) { + String comparisonSign = inclusive ? ">=" : ">"; + q("merged:" + comparisonSign + merged.format(DateTimeFormatter.ISO_DATE)); return this; } /** - * UpdatedAfter gh pull request search builder. + * MergedBefore gh pull request search builder. * - * @param updated - * the updated + * @param merged + * the merged * @param inclusive * whether to include date * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder updatedAfter(LocalDate updated, boolean inclusive) { - String comparisonSign = inclusive ? ">=" : ">"; - q("updated:" + comparisonSign + updated.format(DateTimeFormatter.ISO_DATE)); + public GHPullRequestSearchBuilder mergedBefore(LocalDate merged, boolean inclusive) { + String comparisonSign = inclusive ? "<=" : "<"; + q("merged:" + comparisonSign + merged.format(DateTimeFormatter.ISO_DATE)); return this; } /** - * Updated gh pull request search builder. + * Order gh pull request search builder. * - * @param from - * the updated starting from - * @param to - * the updated ending to + * @param direction + * the direction * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder updated(LocalDate from, LocalDate to) { - String updatedRange = from.format(DateTimeFormatter.ISO_DATE) + ".." + to.format(DateTimeFormatter.ISO_DATE); - q("updated", updatedRange); + public GHPullRequestSearchBuilder order(GHDirection direction) { + req.with("order", direction); + return this; + } + + @Override + public GHPullRequestSearchBuilder q(String term) { + super.q(term); return this; } /** - * Label gh pull request search builder. + * Repository gh pull request search builder. * - * @param label - * the label + * @param repository + * the repository * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder label(String label) { - q("label", label); + public GHPullRequestSearchBuilder repo(GHRepository repository) { + q("repo", repository.getFullName()); return this; } /** - * Labels gh pull request search builder. + * Sort gh pull request search builder. * - * @param labels - * the labels + * @param sort + * the sort * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder inLabels(Iterable labels) { - q("label", String.join(",", labels)); + public GHPullRequestSearchBuilder sort(GHPullRequestSearchBuilder.Sort sort) { + req.with("sort", sort); return this; } @@ -416,69 +421,64 @@ public GHPullRequestSearchBuilder titleLike(String title) { } /** - * Order gh pull request search builder. + * Updated gh pull request search builder. * - * @param direction - * the direction + * @param updated + * the updated * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder order(GHDirection direction) { - req.with("order", direction); + public GHPullRequestSearchBuilder updated(LocalDate updated) { + q("updated", updated.format(DateTimeFormatter.ISO_DATE)); return this; } /** - * Sort gh pull request search builder. + * Updated gh pull request search builder. * - * @param sort - * the sort + * @param from + * the updated starting from + * @param to + * the updated ending to * @return the gh pull request search builder */ - public GHPullRequestSearchBuilder sort(GHPullRequestSearchBuilder.Sort sort) { - req.with("sort", sort); + public GHPullRequestSearchBuilder updated(LocalDate from, LocalDate to) { + String updatedRange = from.format(DateTimeFormatter.ISO_DATE) + ".." + to.format(DateTimeFormatter.ISO_DATE); + q("updated", updatedRange); return this; } - @Override - public GHPullRequestSearchBuilder q(String term) { - super.q(term); + /** + * UpdatedAfter gh pull request search builder. + * + * @param updated + * the updated + * @param inclusive + * whether to include date + * @return the gh pull request search builder + */ + public GHPullRequestSearchBuilder updatedAfter(LocalDate updated, boolean inclusive) { + String comparisonSign = inclusive ? ">=" : ">"; + q("updated:" + comparisonSign + updated.format(DateTimeFormatter.ISO_DATE)); return this; } - @Override - public PagedSearchIterable list() { - this.q("is:pr"); - return super.list(); + /** + * UpdatedBefore gh pull request search builder. + * + * @param updated + * the updated + * @param inclusive + * whether to include date + * @return the gh pull request search builder + */ + public GHPullRequestSearchBuilder updatedBefore(LocalDate updated, boolean inclusive) { + String comparisonSign = inclusive ? "<=" : "<"; + q("updated:" + comparisonSign + updated.format(DateTimeFormatter.ISO_DATE)); + return this; } @Override protected String getApiUrl() { return "/search/issues"; } - - /** - * The sort order values. - */ - public enum Sort { - - /** The comments. */ - COMMENTS, - /** The created. */ - CREATED, - /** The updated. */ - UPDATED, - /** The relevance. */ - RELEVANCE - - } - - private static class PullRequestSearchResult extends SearchResult { - - private GHPullRequest[] items; - - @Override - GHPullRequest[] getItems(GitHub root) { - return items; - } - } } diff --git a/src/main/java/org/kohsuke/github/GHRateLimit.java b/src/main/java/org/kohsuke/github/GHRateLimit.java index b7ca406a72..19c5870640 100644 --- a/src/main/java/org/kohsuke/github/GHRateLimit.java +++ b/src/main/java/org/kohsuke/github/GHRateLimit.java @@ -8,6 +8,7 @@ import org.kohsuke.github.connector.GitHubConnectorResponse; import java.time.Duration; +import java.time.Instant; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -30,435 +31,245 @@ @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", justification = "JSON API") public class GHRateLimit { - @Nonnull - private final Record core; - - @Nonnull - private final Record search; - - @Nonnull - private final Record graphql; - - @Nonnull - private final Record integrationManifest; - /** - * The default GHRateLimit provided to new {@link GitHubClient}s. - * - * Contains all expired records that will cause {@link GitHubClient#rateLimit(RateLimitTarget)} to refresh with new - * data when called. - * - * Private, but made internal for testing. - */ - @Nonnull - static final GHRateLimit DEFAULT = new GHRateLimit(UnknownLimitRecord.DEFAULT, - UnknownLimitRecord.DEFAULT, - UnknownLimitRecord.DEFAULT, - UnknownLimitRecord.DEFAULT); - - /** - * Creates a new {@link GHRateLimit} from a single record for the specified endpoint with place holders for other - * records. - * - * This is used to create {@link GHRateLimit} instances that can merged with other instances. - * - * @param record - * the rate limit record. Can be a regular {@link Record} constructed from header information or an - * {@link UnknownLimitRecord} placeholder. - * @param rateLimitTarget - * which rate limit record to fill - * @return a new {@link GHRateLimit} instance containing the supplied record - */ - @Nonnull - static GHRateLimit fromRecord(@Nonnull Record record, @Nonnull RateLimitTarget rateLimitTarget) { - if (rateLimitTarget == RateLimitTarget.CORE || rateLimitTarget == RateLimitTarget.NONE) { - return new GHRateLimit(record, - UnknownLimitRecord.DEFAULT, - UnknownLimitRecord.DEFAULT, - UnknownLimitRecord.DEFAULT); - } else if (rateLimitTarget == RateLimitTarget.SEARCH) { - return new GHRateLimit(UnknownLimitRecord.DEFAULT, - record, - UnknownLimitRecord.DEFAULT, - UnknownLimitRecord.DEFAULT); - } else if (rateLimitTarget == RateLimitTarget.GRAPHQL) { - return new GHRateLimit(UnknownLimitRecord.DEFAULT, - UnknownLimitRecord.DEFAULT, - record, - UnknownLimitRecord.DEFAULT); - } else if (rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST) { - return new GHRateLimit(UnknownLimitRecord.DEFAULT, - UnknownLimitRecord.DEFAULT, - UnknownLimitRecord.DEFAULT, - record); - } else { - throw new IllegalArgumentException("Unknown rate limit target: " + rateLimitTarget.toString()); - } - } - - /** - * Instantiates a new GH rate limit. - * - * @param core - * the core - * @param search - * the search - * @param graphql - * the graphql - * @param integrationManifest - * the integration manifest - */ - @JsonCreator - GHRateLimit(@Nonnull @JsonProperty("core") Record core, - @Nonnull @JsonProperty("search") Record search, - @Nonnull @JsonProperty("graphql") Record graphql, - @Nonnull @JsonProperty("integration_manifest") Record integrationManifest) { - // The Nonnull annotation is ignored by Jackson, we have to check manually - Objects.requireNonNull(core); - Objects.requireNonNull(search); - Objects.requireNonNull(graphql); - Objects.requireNonNull(integrationManifest); - - this.core = core; - this.search = search; - this.graphql = graphql; - this.integrationManifest = integrationManifest; - } - - /** - * Returns the date at which the Core API rate limit will reset. - * - * @return the calculated date at which the rate limit has or will reset. - */ - @Nonnull - public Date getResetDate() { - return getCore().getResetDate(); - } - - /** - * Gets the remaining number of Core APIs requests allowed before this connection will be throttled. - * - * @return an integer - * @since 1.100 - */ - public int getRemaining() { - return getCore().getRemaining(); - } - - /** - * Gets the total number of Core API calls per hour allotted for this connection. - * - * @return an integer - * @since 1.100 - */ - public int getLimit() { - return getCore().getLimit(); - } - - /** - * Gets the time in epoch seconds when the Core API rate limit will reset. - * - * @return a long - * @since 1.100 - */ - public long getResetEpochSeconds() { - return getCore().getResetEpochSeconds(); - } - - /** - * Whether the reset date for the Core API rate limit has passed. - * - * @return true if the rate limit reset date has passed. Otherwise false. - * @since 1.100 - */ - public boolean isExpired() { - return getCore().isExpired(); - } - - /** - * The core object provides the rate limit status for all non-search-related resources in the REST API. + * A rate limit record. * - * @return a rate limit record + * @author Liam Newman * @since 1.100 */ - @Nonnull - public Record getCore() { - return core; - } - - /** - * The search record provides the rate limit status for the Search API. - * - * @return a rate limit record - * @since 1.115 - */ - @Nonnull - public Record getSearch() { - return search; - } - - /** - * The graphql record provides the rate limit status for the GraphQL API. - * - * @return a rate limit record - * @since 1.115 - */ - @Nonnull - public Record getGraphQL() { - return graphql; - } - - /** - * The integration manifest record provides the rate limit status for the GitHub App Manifest code conversion - * endpoint. - * - * @return a rate limit record - * @since 1.115 - */ - @Nonnull - public Record getIntegrationManifest() { - return integrationManifest; - } - - /** - * To string. - * - * @return the string - */ - @Override - public String toString() { - return "GHRateLimit {" + "core " + getCore().toString() + ", search " + getSearch().toString() + ", graphql " - + getGraphQL().toString() + ", integrationManifest " + getIntegrationManifest().toString() + "}"; - } - - /** - * Equals. - * - * @param o - * the o - * @return true, if successful - */ - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - GHRateLimit rateLimit = (GHRateLimit) o; - return getCore().equals(rateLimit.getCore()) && getSearch().equals(rateLimit.getSearch()) - && getGraphQL().equals(rateLimit.getGraphQL()) - && getIntegrationManifest().equals(rateLimit.getIntegrationManifest()); - } - - /** - * Hash code. - * - * @return the int - */ - @Override - public int hashCode() { - return Objects.hash(getCore(), getSearch(), getGraphQL(), getIntegrationManifest()); - } - - /** - * Merge a {@link GHRateLimit} with another one to create a new {@link GHRateLimit} keeping the latest - * {@link Record}s from each. - * - * @param newLimit - * {@link GHRateLimit} with potentially updated {@link Record}s. - * @return a merged {@link GHRateLimit} with the latest {@link Record}s from these two instances. If the merged - * instance is equal to the current instance, the current instance is returned. - */ - @Nonnull - GHRateLimit getMergedRateLimit(@Nonnull GHRateLimit newLimit) { - - GHRateLimit merged = new GHRateLimit(getCore().currentOrUpdated(newLimit.getCore()), - getSearch().currentOrUpdated(newLimit.getSearch()), - getGraphQL().currentOrUpdated(newLimit.getGraphQL()), - getIntegrationManifest().currentOrUpdated(newLimit.getIntegrationManifest())); - - if (merged.equals(this)) { - merged = this; - } - - return merged; - } + public static class Record { + /** + * EpochSeconds time (UTC) at which this instance was created. + */ + private final long createdAtEpochSeconds = System.currentTimeMillis() / 1000; - /** - * Gets the specified {@link Record}. - * - * {@link RateLimitTarget#NONE} will return {@link UnknownLimitRecord#DEFAULT} to prevent any clients from - * accidentally waiting on that record to reset before continuing. - * - * @param rateLimitTarget - * the target rate limit record - * @return the target {@link Record} from this instance. - */ - @Nonnull - Record getRecord(@Nonnull RateLimitTarget rateLimitTarget) { - if (rateLimitTarget == RateLimitTarget.CORE) { - return getCore(); - } else if (rateLimitTarget == RateLimitTarget.SEARCH) { - return getSearch(); - } else if (rateLimitTarget == RateLimitTarget.GRAPHQL) { - return getGraphQL(); - } else if (rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST) { - return getIntegrationManifest(); - } else if (rateLimitTarget == RateLimitTarget.NONE) { - return UnknownLimitRecord.DEFAULT; - } else { - throw new IllegalArgumentException("Unknown rate limit target: " + rateLimitTarget.toString()); - } - } + /** + * Allotted API call per time period. + */ + private final int limit; - /** - * A limit record used as a placeholder when the actual limit is not known. - * - * @since 1.100 - */ - public static class UnknownLimitRecord extends Record { + /** + * Remaining calls that can be made. + */ + private final int remaining; - private static final long defaultUnknownLimitResetSeconds = Duration.ofSeconds(30).getSeconds(); + /** + * The time at which the current rate limit window resets in UTC epoch seconds. + */ + private final long resetEpochSeconds; /** - * The number of seconds until a {@link UnknownLimitRecord} will expire. - * - * This is set to a somewhat short duration, rather than a long one. This avoids - * {@link GitHubClient#rateLimit(RateLimitTarget)} requesting rate limit updates continuously, but also avoids - * holding on to stale unknown records indefinitely. - * - * When merging {@link GHRateLimit} instances, {@link UnknownLimitRecord}s will be superseded by incoming - * regular {@link Record}s. + * The date at which the rate limit will reset, adjusted to local machine time if the local machine's clock not + * synchronized with to the same clock as the GitHub server. * - * @see GHRateLimit#getMergedRateLimit(GHRateLimit) + * @see #calculateResetInstant(String) + * @see #getResetInstant() */ - static long unknownLimitResetSeconds = defaultUnknownLimitResetSeconds; - - /** The Constant unknownLimit. */ - static final int unknownLimit = 1000000; - - /** The Constant unknownRemaining. */ - static final int unknownRemaining = 999999; - - // The default UnknownLimitRecord is an expired record. - private static final UnknownLimitRecord DEFAULT = new UnknownLimitRecord(Long.MIN_VALUE); - - // The starting current UnknownLimitRecord is an expired record. - private static final AtomicReference current = new AtomicReference<>(DEFAULT); + @Nonnull + private final Instant resetInstant; /** - * Create a new unknown record that resets at the specified time. + * Instantiates a new Record. * + * @param limit + * the limit + * @param remaining + * the remaining * @param resetEpochSeconds - * the epoch second time when this record will expire. + * the reset epoch seconds */ - private UnknownLimitRecord(long resetEpochSeconds) { - super(unknownLimit, unknownRemaining, resetEpochSeconds); + public Record(@JsonProperty(value = "limit", required = true) int limit, + @JsonProperty(value = "remaining", required = true) int remaining, + @JsonProperty(value = "reset", required = true) long resetEpochSeconds) { + this(limit, remaining, resetEpochSeconds, null); } /** - * Current. + * Instantiates a new Record. Called by Jackson data binding or during header parsing. * - * @return the record + * @param limit + * the limit + * @param remaining + * the remaining + * @param resetEpochSeconds + * the reset epoch seconds + * @param connectorResponse + * the response info */ - static Record current() { - Record result = current.get(); - if (result.isExpired()) { - current.set(new UnknownLimitRecord(System.currentTimeMillis() / 1000L + unknownLimitResetSeconds)); - result = current.get(); + @JsonCreator + Record(@JsonProperty(value = "limit", required = true) int limit, + @JsonProperty(value = "remaining", required = true) int remaining, + @JsonProperty(value = "reset", required = true) long resetEpochSeconds, + @JacksonInject @CheckForNull GitHubConnectorResponse connectorResponse) { + this.limit = limit; + this.remaining = remaining; + this.resetEpochSeconds = resetEpochSeconds; + String updatedAt = null; + if (connectorResponse != null) { + updatedAt = connectorResponse.header("Date"); } - return result; + this.resetInstant = calculateResetInstant(updatedAt); } /** - * Reset the current UnknownLimitRecord. For use during testing only. + * Equals. + * + * @param o + * the o + * @return true, if successful */ - static void reset() { - current.set(DEFAULT); - unknownLimitResetSeconds = defaultUnknownLimitResetSeconds; + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Record record = (Record) o; + return getRemaining() == record.getRemaining() && getLimit() == record.getLimit() + && getResetEpochSeconds() == record.getResetEpochSeconds() + && getResetInstant().equals(record.getResetInstant()); } - } - /** - * A rate limit record. - * - * @author Liam Newman - * @since 1.100 - */ - public static class Record { /** - * Remaining calls that can be made. + * Gets the total number of API calls per hour allotted for this connection. + * + * @return an integer */ - private final int remaining; + public int getLimit() { + return limit; + } /** - * Allotted API call per time period. + * Gets the remaining number of requests allowed before this connection will be throttled. + * + * @return an integer */ - private final int limit; + public int getRemaining() { + return remaining; + } /** - * The time at which the current rate limit window resets in UTC epoch seconds. + * The date at which the rate limit will reset, adjusted to local machine time if the local machine's clock not + * synchronized with to the same clock as the GitHub server. + * + * If attempting to wait for the rate limit to reset, consider implementing a {@link RateLimitChecker} instead. + * + * @return the calculated date at which the rate limit has or will reset. + * @deprecated Use {@link #getResetInstant()} */ - private final long resetEpochSeconds; + @Nonnull + @Deprecated + public Date getResetDate() { + return Date.from(getResetInstant()); + } /** - * EpochSeconds time (UTC) at which this instance was created. + * Gets the time in epoch seconds when the rate limit will reset. + * + * This is the raw value returned by the server. This value is not adjusted if local machine time is not + * synchronized with server time. If attempting to check when the rate limit will reset, use + * {@link #getResetInstant()} or implement a {@link RateLimitChecker} instead. + * + * @return a long representing the time in epoch seconds when the rate limit will reset + * @see #getResetInstant() */ - private final long createdAtEpochSeconds = System.currentTimeMillis() / 1000; + public long getResetEpochSeconds() { + return resetEpochSeconds; + } /** - * The date at which the rate limit will reset, adjusted to local machine time if the local machine's clock not - * synchronized with to the same clock as the GitHub server. + * The Instant at which the rate limit will reset, adjusted to local machine time if the local machine's clock + * not synchronized with to the same clock as the GitHub server. + * + * If attempting to wait for the rate limit to reset, consider implementing a {@link RateLimitChecker} instead. * - * @see #calculateResetDate(String) - * @see #getResetDate() + * @return the calculated date at which the rate limit has or will reset. */ @Nonnull - private final Date resetDate; + public Instant getResetInstant() { + return resetInstant; + } /** - * Instantiates a new Record. + * Hash code. * - * @param limit - * the limit - * @param remaining - * the remaining - * @param resetEpochSeconds - * the reset epoch seconds + * @return the int */ - public Record(@JsonProperty(value = "limit", required = true) int limit, - @JsonProperty(value = "remaining", required = true) int remaining, - @JsonProperty(value = "reset", required = true) long resetEpochSeconds) { - this(limit, remaining, resetEpochSeconds, null); + @Override + public int hashCode() { + return Objects.hash(getRemaining(), getLimit(), getResetEpochSeconds(), getResetInstant()); } /** - * Instantiates a new Record. Called by Jackson data binding or during header parsing. + * Whether the rate limit reset date indicated by this instance is expired + * + * If attempting to wait for the rate limit to reset, consider implementing a {@link RateLimitChecker} instead. + * + * @return true if the rate limit reset date has passed. Otherwise false. + */ + public boolean isExpired() { + return getResetInstant().toEpochMilli() < System.currentTimeMillis(); + } + + /** + * To string. + * + * @return the string + */ + @Override + public String toString() { + return "{" + "remaining=" + getRemaining() + ", limit=" + getLimit() + ", resetDate=" + + GitHubClient.printInstant(getResetInstant()) + '}'; + } + + /** + * Recalculates the {@link #resetInstant} relative to the local machine clock. + *

+ * {@link RateLimitChecker}s and {@link RateLimitHandler}s use {@link #getResetInstant()} to make decisions + * about how long to wait for until for the rate limit to reset. That means that {@link #getResetInstant()} + * needs to be calculated based on the local machine clock. + *

+ *

+ * When we say that the clock on two machines is "synchronized", we mean that the UTC time returned from + * {@link System#currentTimeMillis()} on each machine is basically the same. For the purposes of rate limits an + * differences of up to a second can be ignored. + *

+ *

+ * When the clock on the local machine is synchronized to the same time as the clock on the GitHub server (via a + * time service for example), the {@link #resetDate} generated directly from {@link #resetEpochSeconds} will be + * accurate for the local machine as well. + *

+ *

+ * When the clock on the local machine is not synchronized with the server, the {@link #resetDate} must be + * recalculated relative to the local machine clock. This is done by taking the number of seconds between the + * response "Date" header and {@link #resetEpochSeconds} and then adding that to this record's + * {@link #createdAtEpochSeconds}. * - * @param limit - * the limit - * @param remaining - * the remaining - * @param resetEpochSeconds - * the reset epoch seconds - * @param connectorResponse - * the response info + * @param updatedAt + * a string date in RFC 1123 + * @return reset date based on the passed date */ - @JsonCreator - Record(@JsonProperty(value = "limit", required = true) int limit, - @JsonProperty(value = "remaining", required = true) int remaining, - @JsonProperty(value = "reset", required = true) long resetEpochSeconds, - @JacksonInject @CheckForNull GitHubConnectorResponse connectorResponse) { - this.limit = limit; - this.remaining = remaining; - this.resetEpochSeconds = resetEpochSeconds; - String updatedAt = null; - if (connectorResponse != null) { - updatedAt = connectorResponse.header("Date"); + @Nonnull + private Instant calculateResetInstant(@CheckForNull String updatedAt) { + long updatedAtEpochSeconds = createdAtEpochSeconds; + if (!StringUtils.isBlank(updatedAt)) { + try { + // Get the server date and reset data, will always return a time in GMT + updatedAtEpochSeconds = ZonedDateTime.parse(updatedAt, DateTimeFormatter.RFC_1123_DATE_TIME) + .toEpochSecond(); + } catch (DateTimeParseException e) { + if (LOGGER.isLoggable(FINEST)) { + LOGGER.log(FINEST, "Malformed Date header value " + updatedAt, e); + } + } } - this.resetDate = calculateResetDate(updatedAt); + + // This may seem odd but it results in an accurate or slightly pessimistic reset date + // based on system time rather than assuming the system time synchronized with the server + long calculatedSecondsUntilReset = resetEpochSeconds - updatedAtEpochSeconds; + return Instant.ofEpochMilli((createdAtEpochSeconds + calculatedSecondsUntilReset) * 1000); } /** @@ -503,156 +314,371 @@ && getRemaining() <= other.getRemaining())) { return other; } - // If none of the above, the current record is most valid. - return this; - } + // If none of the above, the current record is most valid. + return this; + } + } + + /** + * A limit record used as a placeholder when the actual limit is not known. + * + * @since 1.100 + */ + public static class UnknownLimitRecord extends Record { + + // The default UnknownLimitRecord is an expired record. + private static final UnknownLimitRecord DEFAULT = new UnknownLimitRecord(Long.MIN_VALUE); + + // The starting current UnknownLimitRecord is an expired record. + private static final AtomicReference current = new AtomicReference<>(DEFAULT); + + private static final long defaultUnknownLimitResetSeconds = Duration.ofSeconds(30).getSeconds(); + + /** The Constant unknownLimit. */ + static final int unknownLimit = 1000000; + + /** + * The number of seconds until a {@link UnknownLimitRecord} will expire. + * + * This is set to a somewhat short duration, rather than a long one. This avoids + * {@link GitHubClient#rateLimit(RateLimitTarget)} requesting rate limit updates continuously, but also avoids + * holding on to stale unknown records indefinitely. + * + * When merging {@link GHRateLimit} instances, {@link UnknownLimitRecord}s will be superseded by incoming + * regular {@link Record}s. + * + * @see GHRateLimit#getMergedRateLimit(GHRateLimit) + */ + static long unknownLimitResetSeconds = defaultUnknownLimitResetSeconds; + + /** The Constant unknownRemaining. */ + static final int unknownRemaining = 999999; + + /** + * Current. + * + * @return the record + */ + static Record current() { + Record result = current.get(); + if (result.isExpired()) { + current.set(new UnknownLimitRecord(System.currentTimeMillis() / 1000L + unknownLimitResetSeconds)); + result = current.get(); + } + return result; + } + + /** + * Reset the current UnknownLimitRecord. For use during testing only. + */ + static void reset() { + current.set(DEFAULT); + unknownLimitResetSeconds = defaultUnknownLimitResetSeconds; + } + + /** + * Create a new unknown record that resets at the specified time. + * + * @param resetEpochSeconds + * the epoch second time when this record will expire. + */ + private UnknownLimitRecord(long resetEpochSeconds) { + super(unknownLimit, unknownRemaining, resetEpochSeconds); + } + } + + private static final Logger LOGGER = Logger.getLogger(Requester.class.getName()); + + /** + * The default GHRateLimit provided to new {@link GitHubClient}s. + * + * Contains all expired records that will cause {@link GitHubClient#rateLimit(RateLimitTarget)} to refresh with new + * data when called. + * + * Private, but made internal for testing. + */ + @Nonnull + static final GHRateLimit DEFAULT = new GHRateLimit(UnknownLimitRecord.DEFAULT, + UnknownLimitRecord.DEFAULT, + UnknownLimitRecord.DEFAULT, + UnknownLimitRecord.DEFAULT); + + /** + * Creates a new {@link GHRateLimit} from a single record for the specified endpoint with place holders for other + * records. + * + * This is used to create {@link GHRateLimit} instances that can merged with other instances. + * + * @param record + * the rate limit record. Can be a regular {@link Record} constructed from header information or an + * {@link UnknownLimitRecord} placeholder. + * @param rateLimitTarget + * which rate limit record to fill + * @return a new {@link GHRateLimit} instance containing the supplied record + */ + @Nonnull + static GHRateLimit fromRecord(@Nonnull Record record, @Nonnull RateLimitTarget rateLimitTarget) { + if (rateLimitTarget == RateLimitTarget.CORE || rateLimitTarget == RateLimitTarget.NONE) { + return new GHRateLimit(record, + UnknownLimitRecord.DEFAULT, + UnknownLimitRecord.DEFAULT, + UnknownLimitRecord.DEFAULT); + } else if (rateLimitTarget == RateLimitTarget.SEARCH) { + return new GHRateLimit(UnknownLimitRecord.DEFAULT, + record, + UnknownLimitRecord.DEFAULT, + UnknownLimitRecord.DEFAULT); + } else if (rateLimitTarget == RateLimitTarget.GRAPHQL) { + return new GHRateLimit(UnknownLimitRecord.DEFAULT, + UnknownLimitRecord.DEFAULT, + record, + UnknownLimitRecord.DEFAULT); + } else if (rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST) { + return new GHRateLimit(UnknownLimitRecord.DEFAULT, + UnknownLimitRecord.DEFAULT, + UnknownLimitRecord.DEFAULT, + record); + } else { + throw new IllegalArgumentException("Unknown rate limit target: " + rateLimitTarget.toString()); + } + } + + @Nonnull + private final Record core; + + @Nonnull + private final Record graphql; + + @Nonnull + private final Record integrationManifest; + + @Nonnull + private final Record search; + + /** + * Instantiates a new GH rate limit. + * + * @param core + * the core + * @param search + * the search + * @param graphql + * the graphql + * @param integrationManifest + * the integration manifest + */ + @JsonCreator + GHRateLimit(@Nonnull @JsonProperty("core") Record core, + @Nonnull @JsonProperty("search") Record search, + @Nonnull @JsonProperty("graphql") Record graphql, + @Nonnull @JsonProperty("integration_manifest") Record integrationManifest) { + // The Nonnull annotation is ignored by Jackson, we have to check manually + Objects.requireNonNull(core); + Objects.requireNonNull(search); + Objects.requireNonNull(graphql); + Objects.requireNonNull(integrationManifest); + + this.core = core; + this.search = search; + this.graphql = graphql; + this.integrationManifest = integrationManifest; + } + + /** + * Equals. + * + * @param o + * the o + * @return true, if successful + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + GHRateLimit rateLimit = (GHRateLimit) o; + return getCore().equals(rateLimit.getCore()) && getSearch().equals(rateLimit.getSearch()) + && getGraphQL().equals(rateLimit.getGraphQL()) + && getIntegrationManifest().equals(rateLimit.getIntegrationManifest()); + } + + /** + * The core object provides the rate limit status for all non-search-related resources in the REST API. + * + * @return a rate limit record + * @since 1.100 + */ + @Nonnull + public Record getCore() { + return core; + } + + /** + * The graphql record provides the rate limit status for the GraphQL API. + * + * @return a rate limit record + * @since 1.115 + */ + @Nonnull + public Record getGraphQL() { + return graphql; + } + + /** + * The integration manifest record provides the rate limit status for the GitHub App Manifest code conversion + * endpoint. + * + * @return a rate limit record + * @since 1.115 + */ + @Nonnull + public Record getIntegrationManifest() { + return integrationManifest; + } - /** - * Recalculates the {@link #resetDate} relative to the local machine clock. - *

- * {@link RateLimitChecker}s and {@link RateLimitHandler}s use {@link #getResetDate()} to make decisions about - * how long to wait for until for the rate limit to reset. That means that {@link #getResetDate()} needs to be - * calculated based on the local machine clock. - *

- *

- * When we say that the clock on two machines is "synchronized", we mean that the UTC time returned from - * {@link System#currentTimeMillis()} on each machine is basically the same. For the purposes of rate limits an - * differences of up to a second can be ignored. - *

- *

- * When the clock on the local machine is synchronized to the same time as the clock on the GitHub server (via a - * time service for example), the {@link #resetDate} generated directly from {@link #resetEpochSeconds} will be - * accurate for the local machine as well. - *

- *

- * When the clock on the local machine is not synchronized with the server, the {@link #resetDate} must be - * recalculated relative to the local machine clock. This is done by taking the number of seconds between the - * response "Date" header and {@link #resetEpochSeconds} and then adding that to this record's - * {@link #createdAtEpochSeconds}. - * - * @param updatedAt - * a string date in RFC 1123 - * @return reset date based on the passed date - */ - @Nonnull - private Date calculateResetDate(@CheckForNull String updatedAt) { - long updatedAtEpochSeconds = createdAtEpochSeconds; - if (!StringUtils.isBlank(updatedAt)) { - try { - // Get the server date and reset data, will always return a time in GMT - updatedAtEpochSeconds = ZonedDateTime.parse(updatedAt, DateTimeFormatter.RFC_1123_DATE_TIME) - .toEpochSecond(); - } catch (DateTimeParseException e) { - if (LOGGER.isLoggable(FINEST)) { - LOGGER.log(FINEST, "Malformed Date header value " + updatedAt, e); - } - } - } + /** + * Gets the total number of Core API calls per hour allotted for this connection. + * + * @return an integer + * @since 1.100 + * @deprecated use {@link #getCore()} + */ + @Deprecated + public int getLimit() { + return getCore().getLimit(); + } - // This may seem odd but it results in an accurate or slightly pessimistic reset date - // based on system time rather than assuming the system time synchronized with the server - long calculatedSecondsUntilReset = resetEpochSeconds - updatedAtEpochSeconds; - return new Date((createdAtEpochSeconds + calculatedSecondsUntilReset) * 1000); - } + /** + * Gets the remaining number of Core APIs requests allowed before this connection will be throttled. + * + * @return an integer + * @since 1.100 + * @deprecated use {@link #getCore()} + */ + @Deprecated + public int getRemaining() { + return getCore().getRemaining(); + } - /** - * Gets the remaining number of requests allowed before this connection will be throttled. - * - * @return an integer - */ - public int getRemaining() { - return remaining; - } + /** + * Returns the date at which the Core API rate limit will reset. + * + * @return the calculated date at which the rate limit has or will reset. + * @deprecated use {@link #getCore()} + */ + @Nonnull + @Deprecated + public Date getResetDate() { + return getCore().getResetDate(); + } - /** - * Gets the total number of API calls per hour allotted for this connection. - * - * @return an integer - */ - public int getLimit() { - return limit; - } + /** + * Gets the time in epoch seconds when the Core API rate limit will reset. + * + * @return a long + * @since 1.100 + * @deprecated use {@link #getCore()} + */ + @Deprecated + public long getResetEpochSeconds() { + return getCore().getResetEpochSeconds(); + } - /** - * Gets the time in epoch seconds when the rate limit will reset. - * - * This is the raw value returned by the server. This value is not adjusted if local machine time is not - * synchronized with server time. If attempting to check when the rate limit will reset, use - * {@link #getResetDate()} or implement a {@link RateLimitChecker} instead. - * - * @return a long representing the time in epoch seconds when the rate limit will reset - * @see #getResetDate() - */ - public long getResetEpochSeconds() { - return resetEpochSeconds; - } + /** + * The search record provides the rate limit status for the Search API. + * + * @return a rate limit record + * @since 1.115 + */ + @Nonnull + public Record getSearch() { + return search; + } - /** - * Whether the rate limit reset date indicated by this instance is expired - * - * If attempting to wait for the rate limit to reset, consider implementing a {@link RateLimitChecker} instead. - * - * @return true if the rate limit reset date has passed. Otherwise false. - */ - public boolean isExpired() { - return getResetDate().getTime() < System.currentTimeMillis(); - } + /** + * Hash code. + * + * @return the int + */ + @Override + public int hashCode() { + return Objects.hash(getCore(), getSearch(), getGraphQL(), getIntegrationManifest()); + } - /** - * The date at which the rate limit will reset, adjusted to local machine time if the local machine's clock not - * synchronized with to the same clock as the GitHub server. - * - * If attempting to wait for the rate limit to reset, consider implementing a {@link RateLimitChecker} instead. - * - * @return the calculated date at which the rate limit has or will reset. - */ - @Nonnull - public Date getResetDate() { - return new Date(resetDate.getTime()); - } + /** + * Whether the reset date for the Core API rate limit has passed. + * + * @return true if the rate limit reset date has passed. Otherwise false. + * @since 1.100 + * @deprecated use {@link #getCore()} + */ + @Deprecated + public boolean isExpired() { + return getCore().isExpired(); + } - /** - * To string. - * - * @return the string - */ - @Override - public String toString() { - return "{" + "remaining=" + getRemaining() + ", limit=" + getLimit() + ", resetDate=" + getResetDate() - + '}'; - } + /** + * To string. + * + * @return the string + */ + @Override + public String toString() { + return "GHRateLimit {" + "core " + getCore().toString() + ", search " + getSearch().toString() + ", graphql " + + getGraphQL().toString() + ", integrationManifest " + getIntegrationManifest().toString() + "}"; + } - /** - * Equals. - * - * @param o - * the o - * @return true, if successful - */ - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Record record = (Record) o; - return getRemaining() == record.getRemaining() && getLimit() == record.getLimit() - && getResetEpochSeconds() == record.getResetEpochSeconds() - && getResetDate().equals(record.getResetDate()); - } + /** + * Merge a {@link GHRateLimit} with another one to create a new {@link GHRateLimit} keeping the latest + * {@link Record}s from each. + * + * @param newLimit + * {@link GHRateLimit} with potentially updated {@link Record}s. + * @return a merged {@link GHRateLimit} with the latest {@link Record}s from these two instances. If the merged + * instance is equal to the current instance, the current instance is returned. + */ + @Nonnull + GHRateLimit getMergedRateLimit(@Nonnull GHRateLimit newLimit) { - /** - * Hash code. - * - * @return the int - */ - @Override - public int hashCode() { - return Objects.hash(getRemaining(), getLimit(), getResetEpochSeconds(), getResetDate()); + GHRateLimit merged = new GHRateLimit(getCore().currentOrUpdated(newLimit.getCore()), + getSearch().currentOrUpdated(newLimit.getSearch()), + getGraphQL().currentOrUpdated(newLimit.getGraphQL()), + getIntegrationManifest().currentOrUpdated(newLimit.getIntegrationManifest())); + + if (merged.equals(this)) { + merged = this; } + + return merged; } - private static final Logger LOGGER = Logger.getLogger(Requester.class.getName()); + /** + * Gets the specified {@link Record}. + * + * {@link RateLimitTarget#NONE} will return {@link UnknownLimitRecord#DEFAULT} to prevent any clients from + * accidentally waiting on that record to reset before continuing. + * + * @param rateLimitTarget + * the target rate limit record + * @return the target {@link Record} from this instance. + */ + @Nonnull + Record getRecord(@Nonnull RateLimitTarget rateLimitTarget) { + if (rateLimitTarget == RateLimitTarget.CORE) { + return getCore(); + } else if (rateLimitTarget == RateLimitTarget.SEARCH) { + return getSearch(); + } else if (rateLimitTarget == RateLimitTarget.GRAPHQL) { + return getGraphQL(); + } else if (rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST) { + return getIntegrationManifest(); + } else if (rateLimitTarget == RateLimitTarget.NONE) { + return UnknownLimitRecord.DEFAULT; + } else { + throw new IllegalArgumentException("Unknown rate limit target: " + rateLimitTarget.toString()); + } + } } diff --git a/src/main/java/org/kohsuke/github/GHReaction.java b/src/main/java/org/kohsuke/github/GHReaction.java index 57218b8773..3f16bf41c9 100644 --- a/src/main/java/org/kohsuke/github/GHReaction.java +++ b/src/main/java/org/kohsuke/github/GHReaction.java @@ -11,15 +11,15 @@ */ public class GHReaction extends GHObject { + private ReactionContent content; + + private GHUser user; /** * Create default GHReaction instance */ public GHReaction() { } - private GHUser user; - private ReactionContent content; - /** * The kind of reaction left. * diff --git a/src/main/java/org/kohsuke/github/GHRef.java b/src/main/java/org/kohsuke/github/GHRef.java index b85c650e2d..53ddabc328 100644 --- a/src/main/java/org/kohsuke/github/GHRef.java +++ b/src/main/java/org/kohsuke/github/GHRef.java @@ -15,80 +15,48 @@ public class GHRef extends GitHubInteractiveObject { /** - * Create default GHRef instance - */ - public GHRef() { - } - - private String ref, url; - private GHObject object; - - /** - * Name of the ref, such as "refs/tags/abc". - * - * @return the ref + * The type GHObject. */ - public String getRef() { - return ref; - } + @SuppressFBWarnings( + value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, + justification = "JSON API") + public static class GHObject { - /** - * The API URL of this tag, such as https://api.github.com/repos/jenkinsci/jenkins/git/refs/tags/1.312 - * - * @return the url - */ - public URL getUrl() { - return GitHubClient.parseURL(url); - } + private String type, sha, url; - /** - * The object that this ref points to. - * - * @return the object - */ - public GHObject getObject() { - return object; - } + /** + * Create default GHObject instance + */ + public GHObject() { + } - /** - * Updates this ref to the specified commit. - * - * @param sha - * The SHA1 value to set this reference to - * @throws IOException - * the io exception - */ - public void updateTo(String sha) throws IOException { - updateTo(sha, false); - } + /** + * SHA1 of this object. + * + * @return the sha + */ + public String getSha() { + return sha; + } - /** - * Updates this ref to the specified commit. - * - * @param sha - * The SHA1 value to set this reference to - * @param force - * Whether or not to force this ref update. - * @throws IOException - * the io exception - */ - public void updateTo(String sha, Boolean force) throws IOException { - root().createRequest() - .method("PATCH") - .with("sha", sha) - .with("force", force) - .withUrlPath(url) - .fetch(GHRef.class); - } + /** + * Type of the object, such as "commit". + * + * @return the type + */ + public String getType() { + return type; + } - /** - * Deletes this ref from the repository using the GitHub API. - * - * @throws IOException - * the io exception - */ - public void delete() throws IOException { - root().createRequest().method("DELETE").withUrlPath(url).send(); + /** + * API URL to this Git data, such as + * https://api.github.com/repos/jenkinsci/jenkins/git/commits/b72322675eb0114363a9a86e9ad5a170d1d07ac0 + * + * @return the url + */ + public URL getUrl() { + return GitHubClient.parseURL(url); + } } /** @@ -136,7 +104,6 @@ static GHRef read(GHRepository repository, String refName) throws IOException { } return result; } - /** * Retrieves all refs of the given type for the current GitHub repository. * @@ -160,47 +127,98 @@ static PagedIterable readMatching(GHRepository repository, String refType } /** - * The type GHObject. + * Retrieves all refs that match the given prefix using the matching-refs endpoint. + * + * @param repository + * the repository to read from + * @param refPrefix + * the ref prefix to search for e.g. heads/main or tags/v1 + * @return paged iterable of all refs matching the specified prefix */ - @SuppressFBWarnings( - value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, - justification = "JSON API") - public static class GHObject { - - /** - * Create default GHObject instance - */ - public GHObject() { + static PagedIterable readMatchingRefs(GHRepository repository, String refPrefix) { + if (refPrefix.startsWith("refs/")) { + refPrefix = refPrefix.replaceFirst("refs/", ""); } - private String type, sha, url; + String url = repository.getApiTailUrl(String.format("git/matching-refs/%s", refPrefix)); + return repository.root().createRequest().withUrlPath(url).toIterable(GHRef[].class, item -> repository.root()); + } - /** - * Type of the object, such as "commit". - * - * @return the type - */ - public String getType() { - return type; - } + private GHObject object; - /** - * SHA1 of this object. - * - * @return the sha - */ - public String getSha() { - return sha; - } + private String ref, url; - /** - * API URL to this Git data, such as - * https://api.github.com/repos/jenkinsci/jenkins/git/commits/b72322675eb0114363a9a86e9ad5a170d1d07ac0 - * - * @return the url - */ - public URL getUrl() { - return GitHubClient.parseURL(url); - } + /** + * Create default GHRef instance + */ + public GHRef() { + } + + /** + * Deletes this ref from the repository using the GitHub API. + * + * @throws IOException + * the io exception + */ + public void delete() throws IOException { + root().createRequest().method("DELETE").withUrlPath(url).send(); + } + + /** + * The object that this ref points to. + * + * @return the object + */ + public GHObject getObject() { + return object; + } + + /** + * Name of the ref, such as "refs/tags/abc". + * + * @return the ref + */ + public String getRef() { + return ref; + } + + /** + * The API URL of this tag, such as https://api.github.com/repos/jenkinsci/jenkins/git/refs/tags/1.312 + * + * @return the url + */ + public URL getUrl() { + return GitHubClient.parseURL(url); + } + + /** + * Updates this ref to the specified commit. + * + * @param sha + * The SHA1 value to set this reference to + * @throws IOException + * the io exception + */ + public void updateTo(String sha) throws IOException { + updateTo(sha, false); + } + + /** + * Updates this ref to the specified commit. + * + * @param sha + * The SHA1 value to set this reference to + * @param force + * Whether or not to force this ref update. + * @throws IOException + * the io exception + */ + public void updateTo(String sha, Boolean force) throws IOException { + root().createRequest() + .method("PATCH") + .with("sha", sha) + .with("force", force) + .withUrlPath(url) + .fetch(GHRef.class); } } diff --git a/src/main/java/org/kohsuke/github/GHRelease.java b/src/main/java/org/kohsuke/github/GHRelease.java index 1c6c82851d..5c8144aabf 100644 --- a/src/main/java/org/kohsuke/github/GHRelease.java +++ b/src/main/java/org/kohsuke/github/GHRelease.java @@ -8,11 +8,12 @@ import java.io.InputStream; import java.net.URL; import java.net.URLEncoder; +import java.time.Instant; import java.util.Collections; import java.util.Date; import java.util.List; -import static java.lang.String.*; +import static java.lang.String.format; // TODO: Auto-generated Javadoc /** @@ -24,36 +25,62 @@ public class GHRelease extends GHObject { /** - * Create default GHRelease instance + * Wrap. + * + * @param releases + * the releases + * @param owner + * the owner + * @return the GH release[] */ - public GHRelease() { + static GHRelease[] wrap(GHRelease[] releases, GHRepository owner) { + for (GHRelease release : releases) { + release.wrap(owner); + } + return releases; } - /** The owner. */ - GHRepository owner; - - private String html_url; - private String assets_url; private List assets; - private String upload_url; - private String tag_name; - private String target_commitish; - private String name; + + private String assetsUrl; private String body; + private String discussionUrl; private boolean draft; + private String htmlUrl; + private String name; private boolean prerelease; - private Date published_at; - private String tarball_url; - private String zipball_url; - private String discussion_url; + private String publishedAt; + private String tagName; + private String tarballUrl; + private String targetCommitish; + private String uploadUrl; + private String zipballUrl; + /** The owner. */ + GHRepository owner; /** - * Gets discussion url. Only present if a discussion relating to the release exists + * Create default GHRelease instance + */ + public GHRelease() { + } + + /** + * Deletes this release. * - * @return the discussion url + * @throws IOException + * the io exception */ - public String getDiscussionUrl() { - return discussion_url; + public void delete() throws IOException { + root().createRequest().method("DELETE").withUrlPath(owner.getApiTailUrl("releases/" + getId())).send(); + } + + /** + * Get the cached assets. + * + * @return the assets + */ + public List getAssets() { + return Collections.unmodifiableList(assets); } /** @@ -62,7 +89,7 @@ public String getDiscussionUrl() { * @return the assets url */ public String getAssetsUrl() { - return assets_url; + return assetsUrl; } /** @@ -75,12 +102,12 @@ public String getBody() { } /** - * Is draft boolean. + * Gets discussion url. Only present if a discussion relating to the release exists * - * @return the boolean + * @return the discussion url */ - public boolean isDraft() { - return draft; + public String getDiscussionUrl() { + return discussionUrl; } /** @@ -89,7 +116,7 @@ public boolean isDraft() { * @return the html url */ public URL getHtmlUrl() { - return GitHubClient.parseURL(html_url); + return GitHubClient.parseURL(htmlUrl); } /** @@ -101,16 +128,6 @@ public String getName() { return name; } - /** - * Sets name. - * - * @param name - * the name - */ - public void setName(String name) { - this.name = name; - } - /** * Gets owner. * @@ -122,21 +139,23 @@ public GHRepository getOwner() { } /** - * Is prerelease boolean. + * Gets published at. * - * @return the boolean + * @return the published at */ - public boolean isPrerelease() { - return prerelease; + public Instant getPublishedAt() { + return GitHubClient.parseInstant(publishedAt); } /** * Gets published at. * * @return the published at + * @deprecated Use #getPublishedAt() */ + @Deprecated public Date getPublished_at() { - return new Date(published_at.getTime()); + return Date.from(getPublishedAt()); } /** @@ -145,7 +164,16 @@ public Date getPublished_at() { * @return the tag name */ public String getTagName() { - return tag_name; + return tagName; + } + + /** + * Gets tarball url. + * + * @return the tarball url + */ + public String getTarballUrl() { + return tarballUrl; } /** @@ -154,7 +182,7 @@ public String getTagName() { * @return the target commitish */ public String getTargetCommitish() { - return target_commitish; + return targetCommitish; } /** @@ -163,7 +191,7 @@ public String getTargetCommitish() { * @return the upload url */ public String getUploadUrl() { - return upload_url; + return uploadUrl; } /** @@ -172,44 +200,44 @@ public String getUploadUrl() { * @return the zipball url */ public String getZipballUrl() { - return zipball_url; + return zipballUrl; } /** - * Gets tarball url. + * Is draft boolean. * - * @return the tarball url + * @return the boolean */ - public String getTarballUrl() { - return tarball_url; + public boolean isDraft() { + return draft; } /** - * Wrap. + * Is prerelease boolean. * - * @param owner - * the owner - * @return the GH release + * @return the boolean */ - GHRelease wrap(GHRepository owner) { - this.owner = owner; - return this; + public boolean isPrerelease() { + return prerelease; } /** - * Wrap. + * Re-fetch the assets of this release. * - * @param releases - * the releases - * @param owner - * the owner - * @return the GH release[] + * @return the assets iterable */ - static GHRelease[] wrap(GHRelease[] releases, GHRepository owner) { - for (GHRelease release : releases) { - release.wrap(owner); - } - return releases; + public PagedIterable listAssets() { + Requester builder = owner.root().createRequest(); + return builder.withUrlPath(getApiTailUrl("assets")).toIterable(GHAsset[].class, item -> item.wrap(this)); + } + + /** + * Updates this release via a builder. + * + * @return the gh release updater + */ + public GHReleaseUpdater update() { + return new GHReleaseUpdater(this); } /** @@ -260,45 +288,19 @@ public GHAsset uploadAsset(String filename, InputStream stream, String contentTy return builder.contentType(contentType).with(stream).withUrlPath(url).fetch(GHAsset.class).wrap(this); } - /** - * Get the cached assets. - * - * @return the assets - */ - public List getAssets() { - return Collections.unmodifiableList(assets); - } - - /** - * Re-fetch the assets of this release. - * - * @return the assets iterable - */ - public PagedIterable listAssets() { - Requester builder = owner.root().createRequest(); - return builder.withUrlPath(getApiTailUrl("assets")).toIterable(GHAsset[].class, item -> item.wrap(this)); - } - - /** - * Deletes this release. - * - * @throws IOException - * the io exception - */ - public void delete() throws IOException { - root().createRequest().method("DELETE").withUrlPath(owner.getApiTailUrl("releases/" + getId())).send(); + private String getApiTailUrl(String end) { + return owner.getApiTailUrl(format("releases/%s/%s", getId(), end)); } /** - * Updates this release via a builder. + * Wrap. * - * @return the gh release updater + * @param owner + * the owner + * @return the GH release */ - public GHReleaseUpdater update() { - return new GHReleaseUpdater(this); - } - - private String getApiTailUrl(String end) { - return owner.getApiTailUrl(format("releases/%s/%s", getId(), end)); + GHRelease wrap(GHRepository owner) { + this.owner = owner; + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHReleaseBuilder.java b/src/main/java/org/kohsuke/github/GHReleaseBuilder.java index 9b2b4a4f0b..3862aaa1c0 100644 --- a/src/main/java/org/kohsuke/github/GHReleaseBuilder.java +++ b/src/main/java/org/kohsuke/github/GHReleaseBuilder.java @@ -12,9 +12,32 @@ * @see GHRepository#createRelease(String) GHRepository#createRelease(String) */ public class GHReleaseBuilder { - private final GHRepository repo; + /** + * Values for whether this release should be the latest. + */ + public static enum MakeLatest { + + /** Do not make this the latest release */ + FALSE, + /** Latest release is determined by date and higher semantic version */ + LEGACY, + /** Make this the latest release */ + TRUE; + + /** + * To string. + * + * @return the string + */ + @Override + public String toString() { + return name().toLowerCase(Locale.ROOT); + } + } private final Requester builder; + private final GHRepository repo; + /** * Instantiates a new Gh release builder. * @@ -42,65 +65,51 @@ public GHReleaseBuilder body(String body) { return this; } - /** - * Specifies the commitish value that determines where the Git tag is created from. Can be any branch or commit SHA. - * - * @param commitish - * Defaults to the repository’s default branch (usually "main"). Unused if the Git tag already exists. - * @return the gh release builder - */ - public GHReleaseBuilder commitish(String commitish) { - builder.with("target_commitish", commitish); - return this; - } - /** * Optional. * - * @param draft - * {@code true} to create a draft (unpublished) release, {@code false} to create a published one. Default - * is {@code false}. + * @param categoryName + * the category of the discussion to be created for the release. Category should already exist * @return the gh release builder */ - public GHReleaseBuilder draft(boolean draft) { - builder.with("draft", draft); + public GHReleaseBuilder categoryName(String categoryName) { + builder.with("discussion_category_name", categoryName); return this; } /** - * Name gh release builder. + * Specifies the commitish value that determines where the Git tag is created from. Can be any branch or commit SHA. * - * @param name - * the name of the release + * @param commitish + * Defaults to the repository’s default branch (usually "main"). Unused if the Git tag already exists. * @return the gh release builder */ - public GHReleaseBuilder name(String name) { - builder.with("name", name); + public GHReleaseBuilder commitish(String commitish) { + builder.with("target_commitish", commitish); return this; } /** - * Optional. + * Create gh release. * - * @param prerelease - * {@code true} to identify the release as a prerelease. {@code false} to identify the release as a full - * release. Default is {@code false}. - * @return the gh release builder + * @return the gh release + * @throws IOException + * the io exception */ - public GHReleaseBuilder prerelease(boolean prerelease) { - builder.with("prerelease", prerelease); - return this; + public GHRelease create() throws IOException { + return builder.withUrlPath(repo.getApiTailUrl("releases")).fetch(GHRelease.class).wrap(repo); } /** * Optional. * - * @param categoryName - * the category of the discussion to be created for the release. Category should already exist + * @param draft + * {@code true} to create a draft (unpublished) release, {@code false} to create a published one. Default + * is {@code false}. * @return the gh release builder */ - public GHReleaseBuilder categoryName(String categoryName) { - builder.with("discussion_category_name", categoryName); + public GHReleaseBuilder draft(boolean draft) { + builder.with("draft", draft); return this; } @@ -117,29 +126,6 @@ public GHReleaseBuilder generateReleaseNotes(boolean generateReleaseNotes) { return this; } - /** - * Values for whether this release should be the latest. - */ - public static enum MakeLatest { - - /** Make this the latest release */ - TRUE, - /** Do not make this the latest release */ - FALSE, - /** Latest release is determined by date and higher semantic version */ - LEGACY; - - /** - * To string. - * - * @return the string - */ - @Override - public String toString() { - return name().toLowerCase(Locale.ROOT); - } - } - /** * Optional. * @@ -153,13 +139,27 @@ public GHReleaseBuilder makeLatest(MakeLatest latest) { } /** - * Create gh release. + * Name gh release builder. * - * @return the gh release - * @throws IOException - * the io exception + * @param name + * the name of the release + * @return the gh release builder */ - public GHRelease create() throws IOException { - return builder.withUrlPath(repo.getApiTailUrl("releases")).fetch(GHRelease.class).wrap(repo); + public GHReleaseBuilder name(String name) { + builder.with("name", name); + return this; + } + + /** + * Optional. + * + * @param prerelease + * {@code true} to identify the release as a prerelease. {@code false} to identify the release as a full + * release. Default is {@code false}. + * @return the gh release builder + */ + public GHReleaseBuilder prerelease(boolean prerelease) { + builder.with("prerelease", prerelease); + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHReleaseUpdater.java b/src/main/java/org/kohsuke/github/GHReleaseUpdater.java index 83113412d8..13dc07588b 100644 --- a/src/main/java/org/kohsuke/github/GHReleaseUpdater.java +++ b/src/main/java/org/kohsuke/github/GHReleaseUpdater.java @@ -25,26 +25,26 @@ public class GHReleaseUpdater { } /** - * Tag gh release updater. + * Body gh release updater. * - * @param tag - * the tag + * @param body + * The release notes body. * @return the gh release updater */ - public GHReleaseUpdater tag(String tag) { - builder.with("tag_name", tag); + public GHReleaseUpdater body(String body) { + builder.with("body", body); return this; } /** - * Body gh release updater. + * Optional. * - * @param body - * The release notes body. - * @return the gh release updater + * @param categoryName + * the category of the discussion to be created for the release. Category should already exist + * @return the gh release builder */ - public GHReleaseUpdater body(String body) { - builder.with("body", body); + public GHReleaseUpdater categoryName(String categoryName) { + builder.with("discussion_category_name", categoryName); return this; } @@ -73,6 +73,18 @@ public GHReleaseUpdater draft(boolean draft) { return this; } + /** + * Optional. + * + * @param latest + * Whether to make this the latest release. Default is {@code TRUE} + * @return the gh release builder + */ + public GHReleaseUpdater makeLatest(GHReleaseBuilder.MakeLatest latest) { + builder.with("make_latest", latest); + return this; + } + /** * Name gh release updater. * @@ -99,26 +111,14 @@ public GHReleaseUpdater prerelease(boolean prerelease) { } /** - * Optional. - * - * @param categoryName - * the category of the discussion to be created for the release. Category should already exist - * @return the gh release builder - */ - public GHReleaseUpdater categoryName(String categoryName) { - builder.with("discussion_category_name", categoryName); - return this; - } - - /** - * Optional. + * Tag gh release updater. * - * @param latest - * Whether to make this the latest release. Default is {@code TRUE} - * @return the gh release builder + * @param tag + * the tag + * @return the gh release updater */ - public GHReleaseUpdater makeLatest(GHReleaseBuilder.MakeLatest latest) { - builder.with("make_latest", latest); + public GHReleaseUpdater tag(String tag) { + builder.with("tag_name", tag); return this; } diff --git a/src/main/java/org/kohsuke/github/GHRepoHook.java b/src/main/java/org/kohsuke/github/GHRepoHook.java index e654f591c9..18c4c7b6db 100644 --- a/src/main/java/org/kohsuke/github/GHRepoHook.java +++ b/src/main/java/org/kohsuke/github/GHRepoHook.java @@ -11,15 +11,13 @@ class GHRepoHook extends GHHook { transient GHRepository repository; /** - * Wrap. + * Gets the api route. * - * @param owner - * the owner - * @return the GH repo hook + * @return the api route */ - GHRepoHook wrap(GHRepository owner) { - this.repository = owner; - return this; + @Override + String getApiRoute() { + return String.format("/repos/%s/%s/hooks/%d", repository.getOwnerName(), repository.getName(), getId()); } /** @@ -33,12 +31,14 @@ GitHub root() { } /** - * Gets the api route. + * Wrap. * - * @return the api route + * @param owner + * the owner + * @return the GH repo hook */ - @Override - String getApiRoute() { - return String.format("/repos/%s/%s/hooks/%d", repository.getOwnerName(), repository.getName(), getId()); + GHRepoHook wrap(GHRepository owner) { + this.repository = owner; + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHRepository.java b/src/main/java/org/kohsuke/github/GHRepository.java index 27478c89ec..757ee8cdcb 100644 --- a/src/main/java/org/kohsuke/github/GHRepository.java +++ b/src/main/java/org/kohsuke/github/GHRepository.java @@ -24,6 +24,7 @@ package org.kohsuke.github; import com.fasterxml.jackson.annotation.JsonProperty; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -37,6 +38,7 @@ import java.io.InputStreamReader; import java.io.Reader; import java.net.URL; +import java.time.Instant; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -69,62 +71,186 @@ public class GHRepository extends GHObject { /** - * Create default GHRepository instance + * Affiliation of a repository collaborator. */ - public GHRepository() { + public enum CollaboratorAffiliation { + + /** The all. */ + ALL, + /** The direct. */ + DIRECT, + /** The outside. */ + OUTSIDE } - private String nodeId, description, homepage, name, full_name; + /** + * The type Contributor. + */ + public static class Contributor extends GHUser { - private String html_url; // this is the UI + private int contributions; - /* - * The license information makes use of the preview API. - * - * See: https://developer.github.com/v3/licenses/ - */ - private GHLicense license; + /** + * Create default Contributor instance + */ + public Contributor() { + } - private String git_url, ssh_url, clone_url, svn_url, mirror_url; + /** + * Equals. + * + * @param obj + * the obj + * @return true, if successful + */ + @Override + public boolean equals(Object obj) { + // We ignore contributions in the calculation + return super.equals(obj); + } - private GHUser owner; // not fully populated. beware. + /** + * Gets contributions. + * + * @return the contributions + */ + public int getContributions() { + return contributions; + } - private boolean has_issues, has_wiki, fork, has_downloads, has_pages, archived, disabled, has_projects; + /** + * Hash code. + * + * @return the int + */ + @Override + public int hashCode() { + // We ignore contributions in the calculation + return super.hashCode(); + } + } - private boolean allow_squash_merge; + /** + * Sort orders for listing forks. + */ + public enum ForkSort { - private boolean allow_merge_commit; + /** The newest. */ + NEWEST, + /** The oldest. */ + OLDEST, + /** The stargazers. */ + STARGAZERS + } - private boolean allow_rebase_merge; + /** + * A {@link GHRepositoryBuilder} that allows multiple properties to be updated per request. + * + * Consumer must call {@link #done()} to commit changes. + */ + @BetaApi + public static class Setter extends GHRepositoryBuilder { - private boolean allow_forking; + /** + * Instantiates a new setter. + * + * @param repository + * the repository + */ + protected Setter(@Nonnull GHRepository repository) { + super(GHRepository.class, repository.root(), null); + // even when we don't change the name, we need to send it in + // this requirement may be out-of-date, but we do not want to break it + requester.with("name", repository.name); - private boolean delete_branch_on_merge; + requester.method("PATCH").withUrlPath(repository.getApiTailUrl("")); + } + } - @JsonProperty("private") - private boolean _private; + /** + * A {@link GHRepositoryBuilder} that allows multiple properties to be updated per request. + * + * Consumer must call {@link #done()} to commit changes. + */ + @BetaApi + public static class Updater extends GHRepositoryBuilder { - private String visibility; + /** + * Instantiates a new updater. + * + * @param repository + * the repository + */ + protected Updater(@Nonnull GHRepository repository) { + super(Updater.class, repository.root(), null); + // even when we don't change the name, we need to send it in + // this requirement may be out-of-date, but we do not want to break it + requester.with("name", repository.name); - private int forks_count, stargazers_count, watchers_count, size, open_issues_count, subscribers_count; + requester.method("PATCH").withUrlPath(repository.getApiTailUrl("")); + } + } - private String pushed_at; + /** + * Visibility of a repository. + */ + public enum Visibility { - private Map milestones = Collections.synchronizedMap(new WeakHashMap<>()); + /** The internal. */ + INTERNAL, - private String default_branch, language; + /** The private. */ + PRIVATE, - private GHRepository template_repository; + /** The public. */ + PUBLIC, - private Map commits = Collections.synchronizedMap(new WeakHashMap<>()); + /** + * Placeholder for unexpected data values. + * + * This avoids throwing exceptions during data binding or reading when the list of allowed values returned from + * GitHub is expanded. + * + * Do not pass this value to any methods. If this value is returned during a request, check the log output and + * report an issue for the missing value. + */ + UNKNOWN; - @SkipFromToString - private GHRepoPermission permissions; + /** + * From. + * + * @param value + * the value + * @return the visibility + */ + public static Visibility from(String value) { + return EnumUtils.getNullableEnumOrDefault(Visibility.class, value, Visibility.UNKNOWN); + } - private GHRepository source, parent; + /** + * To string. + * + * @return the string + */ + @Override + public String toString() { + return name().toLowerCase(Locale.ROOT); + } + } - private Boolean isTemplate; - private boolean compareUsePaginatedCommits; + // Only used within listCodeownersErrors(). + private static class GHCodeownersErrors { + List errors; + } + + // Only used within listTopics(). + private static class Topics { + List names; + } + + static class GHRepoPermission { + boolean pull, push, admin; + } /** * Read. @@ -143,1635 +269,1641 @@ static GHRepository read(GitHub root, String owner, String name) throws IOExcept return root.createRequest().withUrlPath("/repos/" + owner + '/' + name).fetch(GHRepository.class); } - /** - * Create deployment gh deployment builder. + private boolean allowForking; + + private boolean allowMergeCommit; + + private boolean allowRebaseMerge; + + private boolean allowSquashMerge; + + private Map commits = Collections.synchronizedMap(new WeakHashMap<>()); + + private boolean compareUsePaginatedCommits; + + private String defaultBranch, language; + + private boolean deleteBranchOnMerge; + + private int forksCount, stargazersCount, watchersCount, size, openIssuesCount, subscribersCount; + + private String gitUrl, sshUrl, cloneUrl, svnUrl, mirrorUrl; + + private boolean hasIssues, hasWiki, fork, hasDownloads, hasPages, archived, disabled, hasProjects; + + private String htmlUrl; // this is the UI + + @JsonProperty("private") + private boolean isPrivate; + private Boolean isTemplate; + + /* + * The license information makes use of the preview API. * - * @param ref - * the ref - * @return the gh deployment builder + * See: https://developer.github.com/v3/licenses/ */ - public GHDeploymentBuilder createDeployment(String ref) { - return new GHDeploymentBuilder(this, ref); - } + private GHLicense license; + + private Map milestones = Collections.synchronizedMap(new WeakHashMap<>()); + + private String nodeId, description, homepage, name, fullName; + + private GHUser owner; // not fully populated. beware. + + @SkipFromToString + private GHRepoPermission permissions; + + private String pushedAt; + + private GHRepository source, parent; + + private GHRepository templateRepository; + + private String visibility; /** - * List deployments paged iterable. - * - * @param sha - * the sha - * @param ref - * the ref - * @param task - * the task - * @param environment - * the environment - * @return the paged iterable + * Create default GHRepository instance */ - public PagedIterable listDeployments(String sha, String ref, String task, String environment) { - return root().createRequest() - .with("sha", sha) - .with("ref", ref) - .with("task", task) - .with("environment", environment) - .withUrlPath(getApiTailUrl("deployments")) - .toIterable(GHDeployment[].class, item -> item.wrap(this)); + public GHRepository() { } /** - * Obtains a single {@link GHDeployment} by its ID. + * Add collaborators. * - * @param id - * the id - * @return the deployment + * @param users + * the users * @throws IOException * the io exception */ - public GHDeployment getDeployment(long id) throws IOException { - return root().createRequest() - .withUrlPath(getApiTailUrl("deployments/" + id)) - .fetch(GHDeployment.class) - .wrap(this); - } - - static class GHRepoPermission { - boolean pull, push, admin; + public void addCollaborators(Collection users) throws IOException { + modifyCollaborators(users, "PUT", null); } /** - * Gets node id. + * Add collaborators. * - * @return the node id + * @param users + * the users + * @param permission + * the permission level + * @throws IOException + * the io exception */ - public String getNodeId() { - return nodeId; + public void addCollaborators(Collection users, GHOrganization.RepositoryRole permission) + throws IOException { + modifyCollaborators(users, "PUT", permission); } /** - * Gets description. + * Add collaborators. * - * @return the description - */ - public String getDescription() { - return description; - } - - /** - * Gets homepage. + * @param permission + * the permission level + * @param users + * the users * - * @return the homepage + * @throws IOException + * the io exception */ - public String getHomepage() { - return homepage; + public void addCollaborators(GHOrganization.RepositoryRole permission, GHUser... users) throws IOException { + addCollaborators(asList(users), permission); } /** - * Gets the git:// URL to this repository, such as "git://github.com/kohsuke/jenkins.git" This URL is read-only. + * Add collaborators. * - * @return the git transport url + * @param users + * the users + * @throws IOException + * the io exception */ - public String getGitTransportUrl() { - return git_url; + public void addCollaborators(GHUser... users) throws IOException { + addCollaborators(asList(users)); } /** - * Gets the HTTPS URL to this repository, such as "https://github.com/kohsuke/jenkins.git" This URL is read-only. + * Add deploy key gh deploy key. * - * @return the http transport url + * @param title + * the title + * @param key + * the key + * @return the gh deploy key + * @throws IOException + * the io exception */ - public String getHttpTransportUrl() { - return clone_url; + public GHDeployKey addDeployKey(String title, String key) throws IOException { + return addDeployKey(title, key, false); } /** - * Gets the Subversion URL to access this repository: https://github.com/rails/rails + * Add deploy key gh deploy key. * - * @return the svn url + * @param title + * the title + * @param key + * the key + * @param readOnly + * read-only ability of the key + * @return the gh deploy key + * @throws IOException + * the io exception */ - public String getSvnUrl() { - return svn_url; + public GHDeployKey addDeployKey(String title, String key, boolean readOnly) throws IOException { + return root().createRequest() + .method("POST") + .with("title", title) + .with("key", key) + .with("read_only", readOnly) + .withUrlPath(getApiTailUrl("keys")) + .fetch(GHDeployKey.class) + .lateBind(this); } /** - * Gets the Mirror URL to access this repository: https://github.com/apache/tomee mirrored from - * git://git.apache.org/tomee.git + * Allow private fork. * - * @return the mirror url + * @param value + * the value + * @throws IOException + * the io exception */ - public String getMirrorUrl() { - return mirror_url; + public void allowForking(boolean value) throws IOException { + set().allowForking(value); } /** - * Gets the SSH URL to access this repository, such as git@github.com:rails/rails.git + * Allow merge commit. * - * @return the ssh url + * @param value + * the value + * @throws IOException + * the io exception */ - public String getSshUrl() { - return ssh_url; + public void allowMergeCommit(boolean value) throws IOException { + set().allowMergeCommit(value); } /** - * Gets the html url. + * Allow rebase merge. * - * @return the html url + * @param value + * the value + * @throws IOException + * the io exception */ - public URL getHtmlUrl() { - return GitHubClient.parseURL(html_url); + public void allowRebaseMerge(boolean value) throws IOException { + set().allowRebaseMerge(value); } /** - * Short repository name without the owner. For example 'jenkins' in case of http://github.com/jenkinsci/jenkins + * Allow squash merge. * - * @return the name + * @param value + * the value + * @throws IOException + * the io exception */ - public String getName() { - return name; + public void allowSquashMerge(boolean value) throws IOException { + set().allowSquashMerge(value); } /** - * Full repository name including the owner or organization. For example 'jenkinsci/jenkins' in case of - * http://github.com/jenkinsci/jenkins + * Will archive and this repository as read-only. When a repository is archived, any operation that can change its + * state is forbidden. This applies symmetrically if trying to unarchive it. * - * @return the full name + *

+ * When you try to do any operation that modifies a read-only repository, it returns the response: + * + *

+     * org.kohsuke.github.HttpException: {
+     *     "message":"Repository was archived so is read-only.",
+     *     "documentation_url":"https://developer.github.com/v3/repos/#edit"
+     * }
+     * 
+ * + * @throws IOException + * In case of any networking error or error from the server. */ - public String getFullName() { - return full_name; + public void archive() throws IOException { + set().archive(); + // Generally would not update this record, + // but doing so here since this will result in any other update actions failing + archived = true; } /** - * Has pull access boolean. + * Create an autolink gh autolink builder. * - * @return the boolean + * @return the gh autolink builder */ - public boolean hasPullAccess() { - return permissions != null && permissions.pull; + public GHAutolinkBuilder createAutolink() { + return new GHAutolinkBuilder(this); } /** - * Has push access boolean. + * Create blob gh blob builder. * - * @return the boolean + * @return the gh blob builder */ - public boolean hasPushAccess() { - return permissions != null && permissions.push; + public GHBlobBuilder createBlob() { + return new GHBlobBuilder(this); } /** - * Has admin access boolean. + * Creates a check run for a commit. * - * @return the boolean + * @param name + * an identifier for the run + * @param headSHA + * the commit hash + * @return a builder which you should customize, then call {@link GHCheckRunBuilder#create} */ - public boolean hasAdminAccess() { - return permissions != null && permissions.admin; + public @NonNull GHCheckRunBuilder createCheckRun(@NonNull String name, @NonNull String headSHA) { + return new GHCheckRunBuilder(this, name, headSHA); } /** - * Gets the primary programming language. + * Create commit gh commit builder. * - * @return the language + * @return the gh commit builder */ - public String getLanguage() { - return language; + public GHCommitBuilder createCommit() { + return new GHCommitBuilder(this); } /** - * Gets owner. + * Create commit status gh commit status. * - * @return the owner + * @param sha1 + * the sha 1 + * @param state + * the state + * @param targetUrl + * the target url + * @param description + * the description + * @return the gh commit status * @throws IOException * the io exception + * @see #createCommitStatus(String, GHCommitState, String, String, String) #createCommitStatus(String, + * GHCommitState,String,String,String) */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHUser getOwner() throws IOException { - return isOffline() ? owner : root().getUser(getOwnerName()); // because 'owner' isn't fully populated + public GHCommitStatus createCommitStatus(String sha1, GHCommitState state, String targetUrl, String description) + throws IOException { + return createCommitStatus(sha1, state, targetUrl, description, null); } /** - * Gets issue. + * Creates a commit status. * - * @param number - * the number of the issue - * @return the issue + * @param sha1 + * the sha 1 + * @param state + * the state + * @param targetUrl + * Optional parameter that points to the URL that has more details. + * @param description + * Optional short description. + * @param context + * Optional commit status context. + * @return the gh commit status * @throws IOException * the io exception */ - public GHIssue getIssue(int number) throws IOException { - return root().createRequest().withUrlPath(getApiTailUrl("issues/" + number)).fetch(GHIssue.class).wrap(this); + public GHCommitStatus createCommitStatus(String sha1, + GHCommitState state, + String targetUrl, + String description, + String context) throws IOException { + return root().createRequest() + .method("POST") + .with("state", state) + .with("target_url", targetUrl) + .with("description", description) + .with("context", context) + .withUrlPath(String.format("/repos/%s/%s/statuses/%s", getOwnerName(), this.name, sha1)) + .fetch(GHCommitStatus.class); } /** - * Create issue gh issue builder. + * Creates a new content, or update an existing content. * - * @param title - * the title - * @return the gh issue builder + * @return the gh content builder */ - public GHIssueBuilder createIssue(String title) { - return new GHIssueBuilder(this, title); + public GHContentBuilder createContent() { + return new GHContentBuilder(this); } /** - * Gets issues. + * Create deployment gh deployment builder. * - * @param state - * the state - * @return the issues - * @throws IOException - * the io exception + * @param ref + * the ref + * @return the gh deployment builder */ - public List getIssues(GHIssueState state) throws IOException { - return queryIssues().state(state).list().toList(); + public GHDeploymentBuilder createDeployment(String ref) { + return new GHDeploymentBuilder(this, ref); } /** - * Gets issues. - * - * @param state - * the state - * @param milestone - * the milestone - * @return the issues - * @throws IOException - * the io exception - */ - public List getIssues(GHIssueState state, GHMilestone milestone) throws IOException { - return queryIssues().milestone(milestone == null ? "none" : "" + milestone.getNumber()) - .state(state) - .list() - .toList(); - } - - /** - * Retrieves issues. - * - * @return the gh issue query builder - */ - public GHIssueQueryBuilder.ForRepository queryIssues() { - return new GHIssueQueryBuilder.ForRepository(this); - } - - /** - * Create release gh release builder. + * Create fork gh repository fork builder. + * (https://docs.github.com/en/rest/repos/forks?apiVersion=2022-11-28#create-a-fork) * - * @param tag - * the tag - * @return the gh release builder + * @return the gh repository fork builder */ - public GHReleaseBuilder createRelease(String tag) { - return new GHReleaseBuilder(this, tag); + public GHRepositoryForkBuilder createFork() { + return new GHRepositoryForkBuilder(this); } /** - * Creates a named ref, such as tag, branch, etc. + * See https://api.github.com/hooks for possible names and their configuration scheme. TODO: produce type-safe + * binding * * @param name - * The name of the fully qualified reference (ie: refs/heads/main). If it doesn't start with 'refs' and - * have at least two slashes, it will be rejected. - * @param sha - * The SHA1 value to set this reference to - * @return the gh ref + * Type of the hook to be created. See https://api.github.com/hooks for possible names. + * @param config + * The configuration hash. + * @param events + * Can be null. Types of events to hook into. + * @param active + * the active + * @return the gh hook * @throws IOException * the io exception */ - public GHRef createRef(String name, String sha) throws IOException { - return root().createRequest() - .method("POST") - .with("ref", name) - .with("sha", sha) - .withUrlPath(getApiTailUrl("git/refs")) - .fetch(GHRef.class); + public GHHook createHook(String name, Map config, Collection events, boolean active) + throws IOException { + return GHHooks.repoContext(this, owner).createHook(name, config, events, active); } /** - * Gets release. + * Create issue gh issue builder. * - * @param id - * the id - * @return the release - * @throws IOException - * the io exception + * @param title + * the title + * @return the gh issue builder */ - public GHRelease getRelease(long id) throws IOException { - try { - return root().createRequest() - .withUrlPath(getApiTailUrl("releases/" + id)) - .fetch(GHRelease.class) - .wrap(this); - } catch (FileNotFoundException e) { - return null; // no release for this id - } + public GHIssueBuilder createIssue(String title) { + return new GHIssueBuilder(this, title); } /** - * Gets release by tag name. + * Create label gh label. * - * @param tag - * the tag - * @return the release by tag name + * @param name + * the name + * @param color + * the color + * @return the gh label * @throws IOException * the io exception */ - public GHRelease getReleaseByTagName(String tag) throws IOException { - try { - return root().createRequest() - .withUrlPath(getApiTailUrl("releases/tags/" + tag)) - .fetch(GHRelease.class) - .wrap(this); - } catch (FileNotFoundException e) { - return null; // no release for this tag - } + public GHLabel createLabel(String name, String color) throws IOException { + return GHLabel.create(this).name(name).color(color).description("").done(); } /** - * Gets latest release. + * Description is still in preview. * - * @return the latest release + * @param name + * the name + * @param color + * the color + * @param description + * the description + * @return gh label * @throws IOException * the io exception */ - public GHRelease getLatestRelease() throws IOException { - try { - return root().createRequest() - .withUrlPath(getApiTailUrl("releases/latest")) - .fetch(GHRelease.class) - .wrap(this); - } catch (FileNotFoundException e) { - return null; // no latest release - } + public GHLabel createLabel(String name, String color, String description) throws IOException { + return GHLabel.create(this).name(name).color(color).description(description).done(); } /** - * List releases paged iterable. + * Create milestone gh milestone. * - * @return the paged iterable + * @param title + * the title + * @param description + * the description + * @return the gh milestone + * @throws IOException + * the io exception */ - public PagedIterable listReleases() { + public GHMilestone createMilestone(String title, String description) throws IOException { return root().createRequest() - .withUrlPath(getApiTailUrl("releases")) - .toIterable(GHRelease[].class, item -> item.wrap(this)); + .method("POST") + .with("title", title) + .with("description", description) + .withUrlPath(getApiTailUrl("milestones")) + .fetch(GHMilestone.class) + .lateBind(this); } /** - * List tags paged iterable. + * Create a project for this repository. * - * @return the paged iterable + * @param name + * the name + * @param body + * the body + * @return the gh project + * @throws IOException + * the io exception */ - public PagedIterable listTags() { + public GHProject createProject(String name, String body) throws IOException { return root().createRequest() - .withUrlPath(getApiTailUrl("tags")) - .toIterable(GHTag[].class, item -> item.wrap(this)); + .method("POST") + .with("name", name) + .with("body", body) + .withUrlPath(getApiTailUrl("projects")) + .fetch(GHProject.class) + .lateBind(this); } /** - * List languages for the specified repository. The value on the right of a language is the number of bytes of code - * written in that language. { "C": 78769, "Python": 7769 } + * Creates a new pull request. * - * @return the map + * @param title + * Required. The title of the pull request. + * @param head + * Required. The name of the branch where your changes are implemented. For cross-repository pull + * requests in the same network, namespace head with a user like this: username:branch. + * @param base + * Required. The name of the branch you want your changes pulled into. This should be an existing branch + * on the current repository. + * @param body + * The contents of the pull request. This is the markdown description of a pull request. + * @return the gh pull request * @throws IOException * the io exception */ - public Map listLanguages() throws IOException { - HashMap result = new HashMap<>(); - root().createRequest().withUrlPath(getApiTailUrl("languages")).fetch(HashMap.class).forEach((key, value) -> { - Long addValue = -1L; - if (value instanceof Integer) { - addValue = Long.valueOf((Integer) value); - } - result.put(key.toString(), addValue); - }); - return result; - } - - /** - * Gets owner name. - * - * @return the owner name - */ - public String getOwnerName() { - // consistency of the GitHub API is super... some serialized forms of GHRepository populate - // a full GHUser while others populate only the owner and email. This later form is super helpful - // in putting the login in owner.name not owner.login... thankfully we can easily identify this - // second set because owner.login will be null - return owner.login != null ? owner.login : owner.name; - } - - /** - * Has issues boolean. - * - * @return the boolean - */ - public boolean hasIssues() { - return has_issues; - } - - /** - * Has projects boolean. - * - * @return the boolean - */ - public boolean hasProjects() { - return has_projects; - } - - /** - * Has wiki boolean. - * - * @return the boolean - */ - public boolean hasWiki() { - return has_wiki; - } - - /** - * Is fork boolean. - * - * @return the boolean - */ - public boolean isFork() { - return fork; - } - - /** - * Is archived boolean. - * - * @return the boolean - */ - public boolean isArchived() { - return archived; - } - - /** - * Is disabled boolean. - * - * @return the boolean - */ - public boolean isDisabled() { - return disabled; - } - - /** - * Is allow squash merge boolean. - * - * @return the boolean - */ - public boolean isAllowSquashMerge() { - return allow_squash_merge; - } - - /** - * Is allow merge commit boolean. - * - * @return the boolean - */ - public boolean isAllowMergeCommit() { - return allow_merge_commit; - } - - /** - * Is allow rebase merge boolean. - * - * @return the boolean - */ - public boolean isAllowRebaseMerge() { - return allow_rebase_merge; - } - - /** - * Is allow private forks - * - * @return the boolean - */ - public boolean isAllowForking() { - return allow_forking; - } - - /** - * Automatically deleting head branches when pull requests are merged. - * - * @return the boolean - */ - public boolean isDeleteBranchOnMerge() { - return delete_branch_on_merge; - } - - /** - * Returns the number of all forks of this repository. This not only counts direct forks, but also forks of forks, - * and so on. - * - * @return the forks - */ - public int getForksCount() { - return forks_count; - } - - /** - * Gets stargazers count. - * - * @return the stargazers count - */ - public int getStargazersCount() { - return stargazers_count; - } - - /** - * Is private boolean. - * - * @return the boolean - */ - public boolean isPrivate() { - return _private; - } - - /** - * Visibility of a repository. - */ - public enum Visibility { - - /** The public. */ - PUBLIC, - - /** The internal. */ - INTERNAL, - - /** The private. */ - PRIVATE, - - /** - * Placeholder for unexpected data values. - * - * This avoids throwing exceptions during data binding or reading when the list of allowed values returned from - * GitHub is expanded. - * - * Do not pass this value to any methods. If this value is returned during a request, check the log output and - * report an issue for the missing value. - */ - UNKNOWN; - - /** - * From. - * - * @param value - * the value - * @return the visibility - */ - public static Visibility from(String value) { - return EnumUtils.getNullableEnumOrDefault(Visibility.class, value, Visibility.UNKNOWN); - } - - /** - * To string. - * - * @return the string - */ - @Override - public String toString() { - return name().toLowerCase(Locale.ROOT); - } - } - - /** - * Gets the visibility of the repository. - * - * @return the visibility - */ - public Visibility getVisibility() { - if (visibility == null) { - try { - populate(); - } catch (final IOException e) { - // Convert this to a runtime exception to avoid messy method signature - throw new GHException("Could not populate the visibility of the repository", e); - } - } - return Visibility.from(visibility); - } - - /** - * Is template boolean. - * - * @return the boolean - */ - public boolean isTemplate() { - if (isTemplate == null) { - try { - populate(); - } catch (IOException e) { - // Convert this to a runtime exception to avoid messy method signature - throw new GHException("Could not populate the template setting of the repository", e); - } - // if this somehow is not populated, set it to false; - isTemplate = Boolean.TRUE.equals(isTemplate); - } - return isTemplate; - } - - /** - * Has downloads boolean. - * - * @return the boolean - */ - public boolean hasDownloads() { - return has_downloads; - } - - /** - * Has pages boolean. - * - * @return the boolean - */ - public boolean hasPages() { - return has_pages; + public GHPullRequest createPullRequest(String title, String head, String base, String body) throws IOException { + return createPullRequest(title, head, base, body, true); } /** - * Gets the count of watchers. + * Creates a new pull request. Maintainer's permissions aware. * - * @return the watchers + * @param title + * Required. The title of the pull request. + * @param head + * Required. The name of the branch where your changes are implemented. For cross-repository pull + * requests in the same network, namespace head with a user like this: username:branch. + * @param base + * Required. The name of the branch you want your changes pulled into. This should be an existing branch + * on the current repository. + * @param body + * The contents of the pull request. This is the markdown description of a pull request. + * @param maintainerCanModify + * Indicates whether maintainers can modify the pull request. + * @return the gh pull request + * @throws IOException + * the io exception */ - public int getWatchersCount() { - return watchers_count; + public GHPullRequest createPullRequest(String title, + String head, + String base, + String body, + boolean maintainerCanModify) throws IOException { + return createPullRequest(title, head, base, body, maintainerCanModify, false); } /** - * Gets open issue count. + * Creates a new pull request. Maintainer's permissions and draft aware. * - * @return the open issue count + * @param title + * Required. The title of the pull request. + * @param head + * Required. The name of the branch where your changes are implemented. For cross-repository pull + * requests in the same network, namespace head with a user like this: username:branch. + * @param base + * Required. The name of the branch you want your changes pulled into. This should be an existing branch + * on the current repository. + * @param body + * The contents of the pull request. This is the markdown description of a pull request. + * @param maintainerCanModify + * Indicates whether maintainers can modify the pull request. + * @param draft + * Indicates whether to create a draft pull request or not. + * @return the gh pull request + * @throws IOException + * the io exception */ - public int getOpenIssueCount() { - return open_issues_count; + public GHPullRequest createPullRequest(String title, + String head, + String base, + String body, + boolean maintainerCanModify, + boolean draft) throws IOException { + return root().createRequest() + .method("POST") + .with("title", title) + .with("head", head) + .with("base", base) + .with("body", body) + .with("maintainer_can_modify", maintainerCanModify) + .with("draft", draft) + .withUrlPath(getApiTailUrl("pulls")) + .fetch(GHPullRequest.class) + .wrapUp(this); } /** - * Gets subscribers count. + * Creates a named ref, such as tag, branch, etc. * - * @return the subscribers count + * @param name + * The name of the fully qualified reference (ie: refs/heads/main). If it doesn't start with 'refs' and + * have at least two slashes, it will be rejected. + * @param sha + * The SHA1 value to set this reference to + * @return the gh ref + * @throws IOException + * the io exception */ - public int getSubscribersCount() { - return subscribers_count; + public GHRef createRef(String name, String sha) throws IOException { + return root().createRequest() + .method("POST") + .with("ref", name) + .with("sha", sha) + .withUrlPath(getApiTailUrl("git/refs")) + .fetch(GHRef.class); } /** - * Gets pushed at. + * Create release gh release builder. * - * @return null if the repository was never pushed at. + * @param tag + * the tag + * @return the gh release builder */ - public Date getPushedAt() { - return GitHubClient.parseDate(pushed_at); + public GHReleaseBuilder createRelease(String tag) { + return new GHReleaseBuilder(this, tag); } /** - * Returns the primary branch you'll configure in the "Admin > Options" config page. + * Set/Update a repository secret + * "https://docs.github.com/rest/reference/actions#create-or-update-a-repository-secret" * - * @return This field is null until the user explicitly configures the default branch. + * @param secretName + * the name of the secret + * @param encryptedValue + * The encrypted value for this secret + * @param publicKeyId + * The id of the Public Key used to encrypt this secret + * @throws IOException + * the io exception */ - public String getDefaultBranch() { - return default_branch; + public void createSecret(String secretName, String encryptedValue, String publicKeyId) throws IOException { + root().createRequest() + .method("PUT") + .with("encrypted_value", encryptedValue) + .with("key_id", publicKeyId) + .withUrlPath(getApiTailUrl("actions/secrets") + "/" + secretName) + .send(); } /** - * Get Repository template was the repository created from. + * Create a tag. See https://developer.github.com/v3/git/tags/#create-a-tag-object * - * @return the repository template + * @param tag + * The tag's name. + * @param message + * The tag message. + * @param object + * The SHA of the git object this is tagging. + * @param type + * The type of the object we're tagging: "commit", "tree" or "blob". + * @return The newly created tag. + * @throws IOException + * Signals that an I/O exception has occurred. */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHRepository getTemplateRepository() { - return (GHRepository) template_repository; + public GHTagObject createTag(String tag, String message, String object, String type) throws IOException { + return root().createRequest() + .method("POST") + .with("tag", tag) + .with("message", message) + .with("object", object) + .with("type", type) + .withUrlPath(getApiTailUrl("git/tags")) + .fetch(GHTagObject.class) + .wrap(this); } /** - * Gets size. + * Create tree gh tree builder. * - * @return the size + * @return the gh tree builder */ - public int getSize() { - return size; + public GHTreeBuilder createTree() { + return new GHTreeBuilder(this); } /** - * Affiliation of a repository collaborator. + * Create a repository variable. + * + * @param name + * the variable name (e.g. test-variable) + * @param value + * the value + * @throws IOException + * the io exception */ - public enum CollaboratorAffiliation { - - /** The all. */ - ALL, - /** The direct. */ - DIRECT, - /** The outside. */ - OUTSIDE + public void createVariable(String name, String value) throws IOException { + GHRepositoryVariable.create(this).name(name).value(value).done(); } /** - * Gets the collaborators on this repository. This set always appear to include the owner. + * Create web hook gh hook. * - * @return the collaborators + * @param url + * the url + * @return the gh hook * @throws IOException * the io exception */ - public GHPersonSet getCollaborators() throws IOException { - return new GHPersonSet(listCollaborators().toList()); + public GHHook createWebHook(URL url) throws IOException { + return createWebHook(url, null); } /** - * Lists up the collaborators on this repository. + * Create web hook gh hook. * - * @return Users paged iterable + * @param url + * the url + * @param events + * the events + * @return the gh hook + * @throws IOException + * the io exception */ - public PagedIterable listCollaborators() { - return listUsers("collaborators"); + public GHHook createWebHook(URL url, Collection events) throws IOException { + return createHook("web", Collections.singletonMap("url", url.toExternalForm()), events, true); } /** - * Lists up the collaborators on this repository. + * Deletes this repository. * - * @param affiliation - * Filter users by affiliation - * @return Users paged iterable + * @throws IOException + * the io exception */ - public PagedIterable listCollaborators(CollaboratorAffiliation affiliation) { - return listUsers(root().createRequest().with("affiliation", affiliation), "collaborators"); + public void delete() throws IOException { + try { + root().createRequest().method("DELETE").withUrlPath(getApiTailUrl("")).send(); + } catch (FileNotFoundException x) { + throw (FileNotFoundException) new FileNotFoundException("Failed to delete " + getOwnerName() + "/" + name + + "; might not exist, or you might need the delete_repo scope in your token: http://stackoverflow.com/a/19327004/12916") + .initCause(x); + } } /** - * Lists all - * the - * available assignees to which issues may be assigned. + * Delete autolink. + * (https://docs.github.com/en/rest/repos/autolinks?apiVersion=2022-11-28#delete-an-autolink-reference-from-a-repository) * - * @return the paged iterable + * @param autolinkId + * the autolink id + * @throws IOException + * the io exception */ - public PagedIterable listAssignees() { - return listUsers("assignees"); + public void deleteAutolink(int autolinkId) throws IOException { + root().createRequest() + .method("DELETE") + .withHeader("Accept", "application/vnd.github+json") + .withUrlPath(String.format("/repos/%s/%s/autolinks/%d", getOwnerName(), getName(), autolinkId)) + .send(); } /** - * Checks if the given user is an assignee for this repository. + * After pull requests are merged, you can have head branches deleted automatically. * - * @param u - * the u - * @return the boolean + * @param value + * the value * @throws IOException * the io exception */ - public boolean hasAssignee(GHUser u) throws IOException { - return root().createRequest().withUrlPath(getApiTailUrl("assignees/" + u.getLogin())).fetchHttpStatusCode() - / 100 == 2; + public void deleteBranchOnMerge(boolean value) throws IOException { + set().deleteBranchOnMerge(value); } /** - * Gets the names of the collaborators on this repository. This method deviates from the principle of this library - * but it works a lot faster than {@link #getCollaborators()}. + * Deletes hook. * - * @return the collaborator names + * @param id + * the id * @throws IOException * the io exception */ - public Set getCollaboratorNames() throws IOException { - Set r = new HashSet<>(); - // no initializer - we just want to the logins - PagedIterable users = root().createRequest() - .withUrlPath(getApiTailUrl("collaborators")) - .toIterable(GHUser[].class, null); - for (GHUser u : users.toArray()) { - r.add(u.login); - } - return r; + public void deleteHook(int id) throws IOException { + GHHooks.repoContext(this, owner).deleteHook(id); } /** - * Gets the names of the collaborators on this repository. This method deviates from the principle of this library - * but it works a lot faster than {@link #getCollaborators()}. + * Create a repository dispatch event, which can be used to start a workflow/action from outside github, as + * described on https://docs.github.com/en/rest/reference/repos#create-a-repository-dispatch-event * - * @param affiliation - * Filter users by affiliation - * @return the collaborator names + * @param + * type of client payload + * @param eventType + * the eventType + * @param clientPayload + * a custom payload , can be nullable * @throws IOException * the io exception */ - public Set getCollaboratorNames(CollaboratorAffiliation affiliation) throws IOException { - Set r = new HashSet<>(); - // no initializer - we just want to the logins - PagedIterable users = root().createRequest() - .withUrlPath(getApiTailUrl("collaborators")) - .with("affiliation", affiliation) - .toIterable(GHUser[].class, null); - for (GHUser u : users.toArray()) { - r.add(u.login); - } - return r; + public void dispatch(String eventType, @Nullable T clientPayload) throws IOException { + root().createRequest() + .method("POST") + .withUrlPath(getApiTailUrl("dispatches")) + .with("event_type", eventType) + .with("client_payload", clientPayload) + .send(); } /** - * Checks if the given user is a collaborator for this repository. + * Enable downloads. * - * @param user - * a {@link GHUser} - * @return true if the user is a collaborator for this repository + * @param v + * the v * @throws IOException * the io exception */ - public boolean isCollaborator(GHUser user) throws IOException { - return root().createRequest() - .withUrlPath(getApiTailUrl("collaborators/" + user.getLogin())) - .fetchHttpStatusCode() == 204; + public void enableDownloads(boolean v) throws IOException { + set().downloads(v); } /** - * Obtain permission for a given user in this repository. + * Enables or disables the issue tracker for this repository. * - * @param user - * a {@link GHUser#getLogin} - * @return the permission + * @param v + * the v * @throws IOException * the io exception */ - public GHPermissionType getPermission(String user) throws IOException { - GHPermission perm = root().createRequest() - .withUrlPath(getApiTailUrl("collaborators/" + user + "/permission")) - .fetch(GHPermission.class); - return perm.getPermissionType(); + public void enableIssueTracker(boolean v) throws IOException { + set().issues(v); } /** - * Obtain permission for a given user in this repository. + * Enables or disables projects for this repository. * - * @param u - * the user - * @return the permission + * @param v + * the v * @throws IOException * the io exception */ - public GHPermissionType getPermission(GHUser u) throws IOException { - return getPermission(u.getLogin()); + public void enableProjects(boolean v) throws IOException { + set().projects(v); } /** - * Check if a user has at least the given permission in this repository. + * Enables or disables Wiki for this repository. * - * @param user - * a {@link GHUser#getLogin} - * @param permission - * the permission to check - * @return true if the user has at least this permission level + * @param v + * the v * @throws IOException * the io exception */ - public boolean hasPermission(String user, GHPermissionType permission) throws IOException { - return getPermission(user).implies(permission); + public void enableWiki(boolean v) throws IOException { + set().wiki(v); } /** - * Check if a user has at least the given permission in this repository. + * Equals. * - * @param user - * the user - * @param permission - * the permission to check - * @return true if the user has at least this permission level - * @throws IOException - * the io exception + * @param obj + * the obj + * @return true, if successful */ - public boolean hasPermission(GHUser user, GHPermissionType permission) throws IOException { - return hasPermission(user.getLogin(), permission); + @Override + public boolean equals(Object obj) { + if (obj instanceof GHRepository) { + GHRepository that = (GHRepository) obj; + return this.getOwnerName().equals(that.getOwnerName()) && this.name.equals(that.name); + } + return false; } /** - * If this repository belongs to an organization, return a set of teams. + * Forks this repository as your repository. * - * @return the teams + * @return Newly forked repository that belong to you. * @throws IOException * the io exception + * @deprecated Use {@link #createFork()} */ - public Set getTeams() throws IOException { - GHOrganization org = root().getOrganization(getOwnerName()); - return root().createRequest() - .withUrlPath(getApiTailUrl("teams")) - .toIterable(GHTeam[].class, item -> item.wrapUp(org)) - .toSet(); + @Deprecated + public GHRepository fork() throws IOException { + return this.createFork().create(); } /** - * Add collaborators. - * - * @param permission - * the permission level - * @param users - * the users + * Forks this repository into an organization. * + * @param org + * the org + * @return Newly forked repository that belong to you. * @throws IOException * the io exception + * @deprecated Use {@link #createFork()} */ - public void addCollaborators(GHOrganization.RepositoryRole permission, GHUser... users) throws IOException { - addCollaborators(asList(users), permission); + @Deprecated + public GHRepository forkTo(GHOrganization org) throws IOException { + return this.createFork().organization(org).create(); } /** - * Add collaborators. + * Gets an artifact by id. * - * @param users - * the users + * @param id + * the id of the artifact + * @return the artifact * @throws IOException * the io exception */ - public void addCollaborators(GHUser... users) throws IOException { - addCollaborators(asList(users)); + public GHArtifact getArtifact(long id) throws IOException { + return root().createRequest() + .withUrlPath(getApiTailUrl("actions/artifacts"), String.valueOf(id)) + .fetch(GHArtifact.class) + .wrapUp(this); } /** - * Add collaborators. + * Obtains the metadata & the content of a blob. * - * @param users - * the users + *

+ * This method retrieves the whole content in memory, so beware when you are dealing with large BLOB. + * + * @param blobSha + * the blob sha + * @return the blob * @throws IOException * the io exception + * @see Get a blob + * @see #readBlob(String) #readBlob(String) */ - public void addCollaborators(Collection users) throws IOException { - modifyCollaborators(users, "PUT", null); + public GHBlob getBlob(String blobSha) throws IOException { + String target = getApiTailUrl("git/blobs/" + blobSha); + return root().createRequest().withUrlPath(target).fetch(GHBlob.class); } /** - * Add collaborators. + * Gets branch. * - * @param users - * the users - * @param permission - * the permission level + * @param name + * the name + * @return the branch * @throws IOException * the io exception */ - public void addCollaborators(Collection users, GHOrganization.RepositoryRole permission) - throws IOException { - modifyCollaborators(users, "PUT", permission); + public GHBranch getBranch(String name) throws IOException { + return root().createRequest().withUrlPath(getApiTailUrl("branches/" + name)).fetch(GHBranch.class).wrap(this); } /** - * Remove collaborators. + * Gets branches by {@linkplain GHBranch#getName() their names}. * - * @param users - * the users + * @return the branches * @throws IOException * the io exception */ - public void removeCollaborators(GHUser... users) throws IOException { - removeCollaborators(asList(users)); + public Map getBranches() throws IOException { + Map r = new TreeMap(); + for (GHBranch p : root().createRequest() + .withUrlPath(getApiTailUrl("branches")) + .toIterable(GHBranch[].class, item -> item.wrap(this)) + .toArray()) { + r.put(p.getName(), p); + } + return r; } /** - * Remove collaborators. + * Gets check runs for given ref. * - * @param users - * the users - * @throws IOException - * the io exception + * @param ref + * ref + * @return check runs for given ref + * @see List check runs + * for a specific ref */ - public void removeCollaborators(Collection users) throws IOException { - modifyCollaborators(users, "DELETE", null); + public PagedIterable getCheckRuns(String ref) { + GitHubRequest request = root().createRequest() + .withUrlPath(String.format("/repos/%s/%s/commits/%s/check-runs", getOwnerName(), name, ref)) + .build(); + return new GHCheckRunsIterable(this, request); } - private void modifyCollaborators(@NonNull Collection users, - @NonNull String method, - @CheckForNull GHOrganization.RepositoryRole permission) throws IOException { - Requester requester = root().createRequest().method(method); - if (permission != null) { - requester = requester.with("permission", permission.toString()).inBody(); - } - - // Make sure that the users collection doesn't have any duplicates - for (GHUser user : new LinkedHashSet<>(users)) { - requester.withUrlPath(getApiTailUrl("collaborators/" + user.getLogin())).send(); - } + /** + * Gets check runs for given ref which validate provided parameters + * + * @param ref + * the Git reference + * @param params + * a map of parameters to filter check runs + * @return check runs for the given ref + * @see List check runs + * for a specific ref + */ + public PagedIterable getCheckRuns(String ref, Map params) { + GitHubRequest request = root().createRequest() + .withUrlPath(String.format("/repos/%s/%s/commits/%s/check-runs", getOwnerName(), name, ref)) + .with(params) + .build(); + return new GHCheckRunsIterable(this, request); } /** - * Sets email service hook. + * https://developer.github.com/v3/repos/traffic/#clones * - * @param address - * the address + * @return the clone traffic * @throws IOException * the io exception */ - public void setEmailServiceHook(String address) throws IOException { - Map config = new HashMap<>(); - config.put("address", address); - root().createRequest() - .method("POST") - .with("name", "email") - .with("config", config) - .with("active", true) - .withUrlPath(getApiTailUrl("hooks")) - .send(); + public GHRepositoryCloneTraffic getCloneTraffic() throws IOException { + return root().createRequest() + .withUrlPath(getApiTailUrl("/traffic/clones")) + .fetch(GHRepositoryCloneTraffic.class); } /** - * Enables or disables the issue tracker for this repository. + * Gets the names of the collaborators on this repository. This method deviates from the principle of this library + * but it works a lot faster than {@link #getCollaborators()}. * - * @param v - * the v + * @return the collaborator names * @throws IOException * the io exception */ - public void enableIssueTracker(boolean v) throws IOException { - set().issues(v); + public Set getCollaboratorNames() throws IOException { + Set r = new HashSet<>(); + // no initializer - we just want to the logins + PagedIterable users = root().createRequest() + .withUrlPath(getApiTailUrl("collaborators")) + .toIterable(GHUser[].class, null); + for (GHUser u : users.toArray()) { + r.add(u.login); + } + return r; } /** - * Enables or disables projects for this repository. + * Gets the names of the collaborators on this repository. This method deviates from the principle of this library + * but it works a lot faster than {@link #getCollaborators()}. * - * @param v - * the v + * @param affiliation + * Filter users by affiliation + * @return the collaborator names * @throws IOException * the io exception */ - public void enableProjects(boolean v) throws IOException { - set().projects(v); + public Set getCollaboratorNames(CollaboratorAffiliation affiliation) throws IOException { + Set r = new HashSet<>(); + // no initializer - we just want to the logins + PagedIterable users = root().createRequest() + .withUrlPath(getApiTailUrl("collaborators")) + .with("affiliation", affiliation) + .toIterable(GHUser[].class, null); + for (GHUser u : users.toArray()) { + r.add(u.login); + } + return r; } /** - * Enables or disables Wiki for this repository. + * Gets the collaborators on this repository. This set always appear to include the owner. * - * @param v - * the v + * @return the collaborators * @throws IOException * the io exception */ - public void enableWiki(boolean v) throws IOException { - set().wiki(v); + public GHPersonSet getCollaborators() throws IOException { + return new GHPersonSet(listCollaborators().toList()); } /** - * Enable downloads. + * Gets a commit object in this repository. * - * @param v - * the v + * @param sha1 + * the sha 1 + * @return the commit * @throws IOException * the io exception */ - public void enableDownloads(boolean v) throws IOException { - set().downloads(v); + public GHCommit getCommit(String sha1) throws IOException { + GHCommit c = commits.get(sha1); + if (c == null) { + c = root().createRequest() + .withUrlPath(String.format("/repos/%s/%s/commits/%s", getOwnerName(), name, sha1)) + .fetch(GHCommit.class) + .wrapUp(this); + commits.put(sha1, c); + } + return c; } /** - * Rename this repository. + * Gets compare. * - * @param name - * the name + * @param id1 + * the id 1 + * @param id2 + * the id 2 + * @return the compare * @throws IOException * the io exception */ - public void renameTo(String name) throws IOException { - set().name(name); + public GHCompare getCompare(GHBranch id1, GHBranch id2) throws IOException { + + GHRepository owner1 = id1.getOwner(); + GHRepository owner2 = id2.getOwner(); + + // If the owner of the branches is different, we have a cross-fork compare. + if (owner1 != null && owner2 != null) { + String ownerName1 = owner1.getOwnerName(); + String ownerName2 = owner2.getOwnerName(); + if (!StringUtils.equals(ownerName1, ownerName2)) { + String qualifiedName1 = String.format("%s:%s", ownerName1, id1.getName()); + String qualifiedName2 = String.format("%s:%s", ownerName2, id2.getName()); + return getCompare(qualifiedName1, qualifiedName2); + } + } + + return getCompare(id1.getName(), id2.getName()); } /** - * Sets description. + * Gets compare. * - * @param value - * the value + * @param id1 + * the id 1 + * @param id2 + * the id 2 + * @return the compare * @throws IOException * the io exception */ - public void setDescription(String value) throws IOException { - set().description(value); + public GHCompare getCompare(GHCommit id1, GHCommit id2) throws IOException { + return getCompare(id1.getSHA1(), id2.getSHA1()); } /** - * Sets homepage. + * Gets a comparison between 2 points in the repository. This would be similar to calling + * git log id1...id2 against a local repository. * - * @param value - * the value + * @param id1 + * an identifier for the first point to compare from, this can be a sha1 ID (for a commit, tag etc) or a + * direct tag name + * @param id2 + * an identifier for the second point to compare to. Can be the same as the first point. + * @return the comparison output * @throws IOException - * the io exception + * on failure communicating with GitHub */ - public void setHomepage(String value) throws IOException { - set().homepage(value); + public GHCompare getCompare(String id1, String id2) throws IOException { + final Requester requester = root().createRequest() + .withUrlPath(getApiTailUrl(String.format("compare/%s...%s", id1, id2))); + + if (compareUsePaginatedCommits) { + requester.with("per_page", 1).with("page", 1); + } + requester.injectMappingValue("GHCompare_usePaginatedCommits", compareUsePaginatedCommits); + GHCompare compare = requester.fetch(GHCompare.class); + return compare.lateBind(this); } /** - * Sets default branch. + * Returns the primary branch you'll configure in the "Admin > Options" config page. * - * @param value - * the value - * @throws IOException - * the io exception + * @return This field is null until the user explicitly configures the default branch. */ - public void setDefaultBranch(String value) throws IOException { - set().defaultBranch(value); + public String getDefaultBranch() { + return defaultBranch; } /** - * Sets private. + * Gets deploy keys. * - * @param value - * the value + * @return the deploy keys * @throws IOException * the io exception */ - public void setPrivate(boolean value) throws IOException { - set().private_(value); + public List getDeployKeys() throws IOException { + return root().createRequest() + .withUrlPath(getApiTailUrl("keys")) + .toIterable(GHDeployKey[].class, item -> item.lateBind(this)) + .toList(); } /** - * Sets visibility. + * Obtains a single {@link GHDeployment} by its ID. * - * @param value - * the value + * @param id + * the id + * @return the deployment * @throws IOException * the io exception */ - public void setVisibility(final Visibility value) throws IOException { - root().createRequest() - .method("PATCH") - .with("name", name) - .with("visibility", value) - .withUrlPath(getApiTailUrl("")) - .send(); + public GHDeployment getDeployment(long id) throws IOException { + return root().createRequest() + .withUrlPath(getApiTailUrl("deployments/" + id)) + .fetch(GHDeployment.class) + .wrap(this); } /** - * Allow squash merge. + * Gets description. * - * @param value - * the value - * @throws IOException - * the io exception + * @return the description */ - public void allowSquashMerge(boolean value) throws IOException { - set().allowSquashMerge(value); + public String getDescription() { + return description; } /** - * Allow merge commit. + * Gets directory content. * - * @param value - * the value + * @param path + * the path + * @return the directory content * @throws IOException * the io exception */ - public void allowMergeCommit(boolean value) throws IOException { - set().allowMergeCommit(value); + public List getDirectoryContent(String path) throws IOException { + return getDirectoryContent(path, null); } /** - * Allow rebase merge. + * Gets directory content. * - * @param value - * the value + * @param path + * the path + * @param ref + * the ref + * @return the directory content * @throws IOException * the io exception */ - public void allowRebaseMerge(boolean value) throws IOException { - set().allowRebaseMerge(value); + public List getDirectoryContent(String path, String ref) throws IOException { + Requester requester = root().createRequest(); + while (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + String target = getApiTailUrl("contents/" + path); + + return requester.with("ref", ref) + .withUrlPath(target) + .toIterable(GHContent[].class, item -> item.wrap(this)) + .toList(); } /** - * Allow private fork. + * Gets file content. * - * @param value - * the value + * @param path + * the path + * @return the file content * @throws IOException * the io exception */ - public void allowForking(boolean value) throws IOException { - set().allowForking(value); + public GHContent getFileContent(String path) throws IOException { + return getFileContent(path, null); } /** - * After pull requests are merged, you can have head branches deleted automatically. + * Gets file content. * - * @param value - * the value + * @param path + * the path + * @param ref + * the ref + * @return the file content * @throws IOException * the io exception */ - public void deleteBranchOnMerge(boolean value) throws IOException { - set().deleteBranchOnMerge(value); + public GHContent getFileContent(String path, String ref) throws IOException { + Requester requester = root().createRequest(); + String target = getApiTailUrl("contents/" + path); + + return requester.with("ref", ref).withUrlPath(target).fetch(GHContent.class).wrap(this); } /** - * Deletes this repository. + * Returns the number of all forks of this repository. This not only counts direct forks, but also forks of forks, + * and so on. * - * @throws IOException - * the io exception + * @return the forks */ - public void delete() throws IOException { - try { - root().createRequest().method("DELETE").withUrlPath(getApiTailUrl("")).send(); - } catch (FileNotFoundException x) { - throw (FileNotFoundException) new FileNotFoundException("Failed to delete " + getOwnerName() + "/" + name - + "; might not exist, or you might need the delete_repo scope in your token: http://stackoverflow.com/a/19327004/12916") - .initCause(x); - } + public int getForksCount() { + return forksCount; } /** - * Will archive and this repository as read-only. When a repository is archived, any operation that can change its - * state is forbidden. This applies symmetrically if trying to unarchive it. - * - *

- * When you try to do any operation that modifies a read-only repository, it returns the response: - * - *

-     * org.kohsuke.github.HttpException: {
-     *     "message":"Repository was archived so is read-only.",
-     *     "documentation_url":"https://developer.github.com/v3/repos/#edit"
-     * }
-     * 
+ * Full repository name including the owner or organization. For example 'jenkinsci/jenkins' in case of + * http://github.com/jenkinsci/jenkins * - * @throws IOException - * In case of any networking error or error from the server. + * @return the full name */ - public void archive() throws IOException { - set().archive(); - // Generally would not update this record, - // but doing so here since this will result in any other update actions failing - archived = true; + public String getFullName() { + return fullName; } /** - * Creates a builder that can be used to bulk update repository settings. + * Gets the git:// URL to this repository, such as "git://github.com/kohsuke/jenkins.git" This URL is read-only. * - * @return the repository updater + * @return the git transport url */ - public Updater update() { - return new Updater(this); + public String getGitTransportUrl() { + return gitUrl; } /** - * Creates a builder that can be used to bulk update repository settings. + * Gets homepage. * - * @return the repository updater + * @return the homepage */ - public Setter set() { - return new Setter(this); + public String getHomepage() { + return homepage; } /** - * Sort orders for listing forks. + * Gets hook. + * + * @param id + * the id + * @return the hook + * @throws IOException + * the io exception */ - public enum ForkSort { + public GHHook getHook(int id) throws IOException { + return GHHooks.repoContext(this, owner).getHook(id); + } - /** The newest. */ - NEWEST, - /** The oldest. */ - OLDEST, - /** The stargazers. */ - STARGAZERS + /** + * Retrieves the currently configured hooks. + * + * @return the hooks + * @throws IOException + * the io exception + */ + public List getHooks() throws IOException { + return GHHooks.repoContext(this, owner).getHooks(); } /** - * Lists all the direct forks of this repository, sorted by github api default, currently {@link ForkSort#NEWEST - * ForkSort.NEWEST}*. + * Gets the html url. * - * @return the paged iterable + * @return the html url */ - public PagedIterable listForks() { - return listForks(null); + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); } /** - * Lists all the direct forks of this repository, sorted by the given sort order. + * Gets the HTTPS URL to this repository, such as "https://github.com/kohsuke/jenkins.git" This URL is read-only. * - * @param sort - * the sort order. If null, defaults to github api default, currently {@link ForkSort#NEWEST - * ForkSort.NEWEST}. - * @return the paged iterable + * @return the http transport url */ - public PagedIterable listForks(final ForkSort sort) { - return root().createRequest() - .with("sort", sort) - .withUrlPath(getApiTailUrl("forks")) - .toIterable(GHRepository[].class, null); + public String getHttpTransportUrl() { + return cloneUrl; } /** - * Forks this repository as your repository. + * Gets issue. * - * @return Newly forked repository that belong to you. + * @param number + * the number of the issue + * @return the issue * @throws IOException * the io exception - * @deprecated Use {@link #createFork()} */ - @Deprecated - public GHRepository fork() throws IOException { - return this.createFork().create(); + public GHIssue getIssue(int number) throws IOException { + return root().createRequest().withUrlPath(getApiTailUrl("issues/" + number)).fetch(GHIssue.class).wrap(this); } /** - * Sync this repository fork branch + * Get a single issue event. See https://developer.github.com/v3/issues/events/#get-a-single-event * - * @param branch - * the branch to sync - * @return The current repository + * @param id + * the id + * @return the issue event * @throws IOException * the io exception */ - public GHBranchSync sync(String branch) throws IOException { - return root().createRequest() - .method("POST") - .with("branch", branch) - .withUrlPath(getApiTailUrl("merge-upstream")) - .fetch(GHBranchSync.class) - .wrap(this); + public GHIssueEvent getIssueEvent(long id) throws IOException { + return root().createRequest().withUrlPath(getApiTailUrl("issues/events/" + id)).fetch(GHIssueEvent.class); } /** - * Forks this repository into an organization. + * Gets issues. * - * @param org - * the org - * @return Newly forked repository that belong to you. + * @param state + * the state + * @return the issues * @throws IOException * the io exception - * @deprecated Use {@link #createFork()} + * @deprecated Use {@link #queryIssues()} instead. */ @Deprecated - public GHRepository forkTo(GHOrganization org) throws IOException { - return this.createFork().organization(org).create(); + public List getIssues(GHIssueState state) throws IOException { + return queryIssues().state(state).list().toList(); } /** - * Retrieves a specified pull request. + * Gets issues. * - * @param number - * the number of the pull request - * @return the pull request + * @param state + * the state + * @param milestone + * the milestone + * @return the issues * @throws IOException * the io exception + * @deprecated Use {@link #queryIssues()} instead. */ - public GHPullRequest getPullRequest(int number) throws IOException { - return root().createRequest() - .withUrlPath(getApiTailUrl("pulls/" + number)) - .fetch(GHPullRequest.class) - .wrapUp(this); + @Deprecated + public List getIssues(GHIssueState state, GHMilestone milestone) throws IOException { + return queryIssues().milestone(milestone == null ? "none" : "" + milestone.getNumber()) + .state(state) + .list() + .toList(); } /** - * Retrieves pull requests. + * Gets label. * - * @return the gh pull request query builder + * @param name + * the name + * @return the label + * @throws IOException + * the io exception */ - public GHPullRequestQueryBuilder queryPullRequests() { - return new GHPullRequestQueryBuilder(this); + public GHLabel getLabel(String name) throws IOException { + return GHLabel.read(this, name); } /** - * Retrieves pull requests according to search terms. + * Gets the primary programming language. * - * @return gh pull request search builder for current repository + * @return the language */ - public GHPullRequestSearchBuilder searchPullRequests() { - return new GHPullRequestSearchBuilder(this.root()).repo(this); + public String getLanguage() { + return language; } /** - * Creates a new pull request. + * Gets the last status of this commit, which is what gets shown in the UI. * - * @param title - * Required. The title of the pull request. - * @param head - * Required. The name of the branch where your changes are implemented. For cross-repository pull - * requests in the same network, namespace head with a user like this: username:branch. - * @param base - * Required. The name of the branch you want your changes pulled into. This should be an existing branch - * on the current repository. - * @param body - * The contents of the pull request. This is the markdown description of a pull request. - * @return the gh pull request + * @param sha1 + * the sha 1 + * @return the last commit status * @throws IOException * the io exception */ - public GHPullRequest createPullRequest(String title, String head, String base, String body) throws IOException { - return createPullRequest(title, head, base, body, true); + public GHCommitStatus getLastCommitStatus(String sha1) throws IOException { + List v = listCommitStatuses(sha1).toList(); + return v.isEmpty() ? null : v.get(0); } /** - * Creates a new pull request. Maintainer's permissions aware. + * Gets latest release. * - * @param title - * Required. The title of the pull request. - * @param head - * Required. The name of the branch where your changes are implemented. For cross-repository pull - * requests in the same network, namespace head with a user like this: username:branch. - * @param base - * Required. The name of the branch you want your changes pulled into. This should be an existing branch - * on the current repository. - * @param body - * The contents of the pull request. This is the markdown description of a pull request. - * @param maintainerCanModify - * Indicates whether maintainers can modify the pull request. - * @return the gh pull request + * @return the latest release * @throws IOException * the io exception */ - public GHPullRequest createPullRequest(String title, - String head, - String base, - String body, - boolean maintainerCanModify) throws IOException { - return createPullRequest(title, head, base, body, maintainerCanModify, false); + public GHRelease getLatestRelease() throws IOException { + try { + return root().createRequest() + .withUrlPath(getApiTailUrl("releases/latest")) + .fetch(GHRelease.class) + .wrap(this); + } catch (FileNotFoundException e) { + return null; // no latest release + } } /** - * Creates a new pull request. Maintainer's permissions and draft aware. + * Gets the basic license details for the repository. * - * @param title - * Required. The title of the pull request. - * @param head - * Required. The name of the branch where your changes are implemented. For cross-repository pull - * requests in the same network, namespace head with a user like this: username:branch. - * @param base - * Required. The name of the branch you want your changes pulled into. This should be an existing branch - * on the current repository. - * @param body - * The contents of the pull request. This is the markdown description of a pull request. - * @param maintainerCanModify - * Indicates whether maintainers can modify the pull request. - * @param draft - * Indicates whether to create a draft pull request or not. - * @return the gh pull request + * @return null if there's no license. * @throws IOException - * the io exception + * as usual but also if you don't use the preview connector */ - public GHPullRequest createPullRequest(String title, - String head, - String base, - String body, - boolean maintainerCanModify, - boolean draft) throws IOException { - return root().createRequest() - .method("POST") - .with("title", title) - .with("head", head) - .with("base", base) - .with("body", body) - .with("maintainer_can_modify", maintainerCanModify) - .with("draft", draft) - .withUrlPath(getApiTailUrl("pulls")) - .fetch(GHPullRequest.class) - .wrapUp(this); + public GHLicense getLicense() throws IOException { + GHContentWithLicense lic = getLicenseContent_(); + return lic != null ? lic.license : null; } /** - * Retrieves the currently configured hooks. + * Retrieves the contents of the repository's license file - makes an additional API call. * - * @return the hooks + * @return details regarding the license contents, or null if there's no license. * @throws IOException - * the io exception + * as usual but also if you don't use the preview connector */ - public List getHooks() throws IOException { - return GHHooks.repoContext(this, owner).getHooks(); + public GHContent getLicenseContent() throws IOException { + return getLicenseContent_(); } /** - * Gets hook. + * Gets milestone. * - * @param id - * the id - * @return the hook + * @param number + * the number + * @return the milestone * @throws IOException * the io exception */ - public GHHook getHook(int id) throws IOException { - return GHHooks.repoContext(this, owner).getHook(id); + public GHMilestone getMilestone(int number) throws IOException { + GHMilestone m = milestones.get(number); + if (m == null) { + m = root().createRequest().withUrlPath(getApiTailUrl("milestones/" + number)).fetch(GHMilestone.class); + m.owner = this; + milestones.put(m.getNumber(), m); + } + return m; + } + + /** + * Gets the Mirror URL to access this repository: https://github.com/apache/tomee mirrored from + * git://git.apache.org/tomee.git + * + * @return the mirror url + */ + public String getMirrorUrl() { + return mirrorUrl; + } + + /** + * Short repository name without the owner. For example 'jenkins' in case of http://github.com/jenkinsci/jenkins + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * Gets node id. + * + * @return the node id + */ + public String getNodeId() { + return nodeId; + } + + /** + * Gets open issue count. + * + * @return the open issue count + */ + public int getOpenIssueCount() { + return openIssuesCount; } /** - * Deletes hook. + * Gets owner. * - * @param id - * the id + * @return the owner * @throws IOException * the io exception */ - public void deleteHook(int id) throws IOException { - GHHooks.repoContext(this, owner).deleteHook(id); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHUser getOwner() throws IOException { + return isOffline() ? owner : root().getUser(getOwnerName()); // because 'owner' isn't fully populated } /** - * Sets {@link #getCompare(String, String)} to return a {@link GHCompare} that uses a paginated commit list instead - * of limiting to 250 results. - * - * By default, {@link GHCompare} returns all commits in the comparison as part of the request, limited to 250 - * results. More recently GitHub added the ability to return the commits as a paginated query allowing for more than - * 250 results. + * Gets owner name. * - * @param value - * true if you want commits returned in paginated form. + * @return the owner name */ - public void setCompareUsePaginatedCommits(boolean value) { - compareUsePaginatedCommits = value; + public String getOwnerName() { + // consistency of the GitHub API is super... some serialized forms of GHRepository populate + // a full GHUser while others populate only the owner and email. This later form is super helpful + // in putting the login in owner.name not owner.login... thankfully we can easily identify this + // second set because owner.login will be null + return owner.login != null ? owner.login : owner.name; } /** - * Gets a comparison between 2 points in the repository. This would be similar to calling - * git log id1...id2 against a local repository. + * Forked repositories have a 'parent' attribute that specifies the repository this repository is directly forked + * from. If we keep traversing {@link #getParent()} until it returns null, that is {@link #getSource()}. * - * @param id1 - * an identifier for the first point to compare from, this can be a sha1 ID (for a commit, tag etc) or a - * direct tag name - * @param id2 - * an identifier for the second point to compare to. Can be the same as the first point. - * @return the comparison output + * @return {@link GHRepository} that points to the repository where this repository is forked directly from. + * Otherwise null. * @throws IOException - * on failure communicating with GitHub + * the io exception + * @see #getSource() #getSource() */ - public GHCompare getCompare(String id1, String id2) throws IOException { - final Requester requester = root().createRequest() - .withUrlPath(getApiTailUrl(String.format("compare/%s...%s", id1, id2))); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHRepository getParent() throws IOException { + if (fork && parent == null) { + populate(); + } - if (compareUsePaginatedCommits) { - requester.with("per_page", 1).with("page", 1); + if (parent == null) { + return null; } - requester.injectMappingValue("GHCompare_usePaginatedCommits", compareUsePaginatedCommits); - GHCompare compare = requester.fetch(GHCompare.class); - return compare.lateBind(this); + return parent; } /** - * Gets compare. + * Obtain permission for a given user in this repository. * - * @param id1 - * the id 1 - * @param id2 - * the id 2 - * @return the compare + * @param u + * the user + * @return the permission * @throws IOException * the io exception */ - public GHCompare getCompare(GHCommit id1, GHCommit id2) throws IOException { - return getCompare(id1.getSHA1(), id2.getSHA1()); + public GHPermissionType getPermission(GHUser u) throws IOException { + return getPermission(u.getLogin()); } /** - * Gets compare. + * Obtain permission for a given user in this repository. * - * @param id1 - * the id 1 - * @param id2 - * the id 2 - * @return the compare + * @param user + * a {@link GHUser#getLogin} + * @return the permission * @throws IOException * the io exception */ - public GHCompare getCompare(GHBranch id1, GHBranch id2) throws IOException { - - GHRepository owner1 = id1.getOwner(); - GHRepository owner2 = id2.getOwner(); - - // If the owner of the branches is different, we have a cross-fork compare. - if (owner1 != null && owner2 != null) { - String ownerName1 = owner1.getOwnerName(); - String ownerName2 = owner2.getOwnerName(); - if (!StringUtils.equals(ownerName1, ownerName2)) { - String qualifiedName1 = String.format("%s:%s", ownerName1, id1.getName()); - String qualifiedName2 = String.format("%s:%s", ownerName2, id2.getName()); - return getCompare(qualifiedName1, qualifiedName2); - } - } - - return getCompare(id1.getName(), id2.getName()); + public GHPermissionType getPermission(String user) throws IOException { + GHPermission perm = root().createRequest() + .withUrlPath(getApiTailUrl("collaborators/" + user + "/permission")) + .fetch(GHPermission.class); + return perm.getPermissionType(); } /** - * Retrieves all refs for the github repository. + * Gets the public key for the given repo. * - * @return an array of GHRef elements corresponding with the refs in the remote repository. + * @return the public key * @throws IOException - * on failure communicating with GitHub + * the io exception */ - public GHRef[] getRefs() throws IOException { - return listRefs().toArray(); + public GHRepositoryPublicKey getPublicKey() throws IOException { + return root().createRequest() + .withUrlPath(getApiTailUrl("/actions/secrets/public-key")) + .fetch(GHRepositoryPublicKey.class) + .wrapUp(this); } /** - * Retrieves all refs for the github repository. + * Retrieves a specified pull request. * - * @return paged iterable of all refs + * @param number + * the number of the pull request + * @return the pull request + * @throws IOException + * the io exception */ - public PagedIterable listRefs() { - return listRefs(""); + public GHPullRequest getPullRequest(int number) throws IOException { + return root().createRequest() + .withUrlPath(getApiTailUrl("pulls/" + number)) + .fetch(GHPullRequest.class) + .wrapUp(this); } /** - * Retrieves all refs of the given type for the current GitHub repository. + * Retrieves all the pull requests of a particular state. * - * @param refType - * the type of reg to search for e.g. tags or commits - * @return an array of all refs matching the request type + * @param state + * the state + * @return the pull requests * @throws IOException - * on failure communicating with GitHub, potentially due to an invalid ref type being requested + * the io exception + * @deprecated Use {@link #queryPullRequests()} */ - public GHRef[] getRefs(String refType) throws IOException { - return listRefs(refType).toArray(); + @Deprecated + public List getPullRequests(GHIssueState state) throws IOException { + return queryPullRequests().state(state).list().toList(); } /** - * Retrieves all refs of the given type for the current GitHub repository. + * Gets pushed at. * - * @param refType - * the type of reg to search for e.g. tags or commits - * @return paged iterable of all refs of the specified type + * @return null if the repository was never pushed at. */ - public PagedIterable listRefs(String refType) { - return GHRef.readMatching(this, refType); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getPushedAt() { + return GitHubClient.parseInstant(pushedAt); + } + + /** + * https://developer.github.com/v3/repos/contents/#get-the-readme + * + * @return the readme + * @throws IOException + * the io exception + */ + public GHContent getReadme() throws IOException { + Requester requester = root().createRequest(); + return requester.withUrlPath(getApiTailUrl("readme")).fetch(GHContent.class).wrap(this); } /** @@ -1788,881 +1920,760 @@ public GHRef getRef(String refName) throws IOException { } /** - * Returns the annotated tag object. Only valid if the {@link GHRef#getObject()} has a - * {@link GHRef.GHObject#getType()} of {@code tag}. + * Retrieves all refs for the github repository. * - * @param sha - * the sha of the tag object - * @return the annotated tag object + * @return an array of GHRef elements corresponding with the refs in the remote repository. * @throws IOException - * the io exception + * on failure communicating with GitHub */ - public GHTagObject getTagObject(String sha) throws IOException { - return root().createRequest().withUrlPath(getApiTailUrl("git/tags/" + sha)).fetch(GHTagObject.class).wrap(this); + public GHRef[] getRefs() throws IOException { + return listRefs().toArray(); } /** - * Retrieve a tree of the given type for the current GitHub repository. + * Retrieves all refs of the given type for the current GitHub repository. * - * @param sha - * sha number or branch name ex: "main" - * @return refs matching the request type + * @param refType + * the type of reg to search for e.g. tags or commits + * @return an array of all refs matching the request type * @throws IOException - * on failure communicating with GitHub, potentially due to an invalid tree type being requested + * on failure communicating with GitHub, potentially due to an invalid ref type being requested */ - public GHTree getTree(String sha) throws IOException { - String url = String.format("/repos/%s/%s/git/trees/%s", getOwnerName(), name, sha); - return root().createRequest().withUrlPath(url).fetch(GHTree.class).wrap(this); + public GHRef[] getRefs(String refType) throws IOException { + return listRefs(refType).toArray(); } /** - * Create tree gh tree builder. + * Gets release. * - * @return the gh tree builder + * @param id + * the id + * @return the release + * @throws IOException + * the io exception */ - public GHTreeBuilder createTree() { - return new GHTreeBuilder(this); + public GHRelease getRelease(long id) throws IOException { + try { + return root().createRequest() + .withUrlPath(getApiTailUrl("releases/" + id)) + .fetch(GHRelease.class) + .wrap(this); + } catch (FileNotFoundException e) { + return null; // no release for this id + } } /** - * Retrieves the tree for the current GitHub repository, recursively as described in here: - * https://developer.github.com/v3/git/trees/#get-a-tree-recursively + * Gets release by tag name. * - * @param sha - * sha number or branch name ex: "main" - * @param recursive - * use 1 - * @return the tree recursive + * @param tag + * the tag + * @return the release by tag name * @throws IOException - * on failure communicating with GitHub, potentially due to an invalid tree type being requested + * the io exception */ - public GHTree getTreeRecursive(String sha, int recursive) throws IOException { - String url = String.format("/repos/%s/%s/git/trees/%s", getOwnerName(), name, sha); - return root().createRequest().with("recursive", recursive).withUrlPath(url).fetch(GHTree.class).wrap(this); + public GHRelease getReleaseByTagName(String tag) throws IOException { + try { + return root().createRequest() + .withUrlPath(getApiTailUrl("releases/tags/" + tag)) + .fetch(GHRelease.class) + .wrap(this); + } catch (FileNotFoundException e) { + return null; // no release for this tag + } } /** - * Obtains the metadata & the content of a blob. - * - *

- * This method retrieves the whole content in memory, so beware when you are dealing with large BLOB. + * Exports the software bill of materials (SBOM) for a repository. * - * @param blobSha - * the blob sha - * @return the blob + * @return the SBOM export result containing the SPDX-formatted SBOM * @throws IOException * the io exception - * @see Get a blob - * @see #readBlob(String) #readBlob(String) + * @see SBOM API documentation */ - public GHBlob getBlob(String blobSha) throws IOException { - String target = getApiTailUrl("git/blobs/" + blobSha); - return root().createRequest().withUrlPath(target).fetch(GHBlob.class); + public GHSBOMExportResult getSBOM() throws IOException { + return root().createRequest() + .withUrlPath(getApiTailUrl("dependency-graph/sbom")) + .fetch(GHSBOMExportResult.class); } /** - * Create blob gh blob builder. + * Gets size. * - * @return the gh blob builder + * @return the size */ - public GHBlobBuilder createBlob() { - return new GHBlobBuilder(this); + public int getSize() { + return size; } /** - * Reads the content of a blob as a stream for better efficiency. + * Forked repositories have a 'source' attribute that specifies the ultimate source of the forking chain. * - * @param blobSha - * the blob sha - * @return the input stream + * @return {@link GHRepository} that points to the root repository where this repository is forked (indirectly or + * directly) from. Otherwise null. * @throws IOException * the io exception - * @see Get a blob - * @see #getBlob(String) #getBlob(String) + * @see #getParent() #getParent() */ - public InputStream readBlob(String blobSha) throws IOException { - String target = getApiTailUrl("git/blobs/" + blobSha); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHRepository getSource() throws IOException { + if (fork && source == null) { + populate(); + } + if (source == null) { + return null; + } - // https://developer.github.com/v3/media/ describes this media type - return root().createRequest() - .withHeader("Accept", "application/vnd.github.raw") - .withUrlPath(target) - .fetchStream(Requester::copyInputStream); + return source; } /** - * Gets a commit object in this repository. + * Gets the SSH URL to access this repository, such as git@github.com:rails/rails.git * - * @param sha1 - * the sha 1 - * @return the commit - * @throws IOException - * the io exception + * @return the ssh url */ - public GHCommit getCommit(String sha1) throws IOException { - GHCommit c = commits.get(sha1); - if (c == null) { - c = root().createRequest() - .withUrlPath(String.format("/repos/%s/%s/commits/%s", getOwnerName(), name, sha1)) - .fetch(GHCommit.class) - .wrapUp(this); - commits.put(sha1, c); - } - return c; + public String getSshUrl() { + return sshUrl; } /** - * Create commit gh commit builder. + * Gets stargazers count. * - * @return the gh commit builder + * @return the stargazers count */ - public GHCommitBuilder createCommit() { - return new GHCommitBuilder(this); + public int getStargazersCount() { + return stargazersCount; } /** - * Lists all the commits. + * Returns the statistics for this repository. * - * @return the paged iterable + * @return the statistics */ - public PagedIterable listCommits() { - return root().createRequest() - .withUrlPath(String.format("/repos/%s/%s/commits", getOwnerName(), name)) - .toIterable(GHCommit[].class, item -> item.wrapUp(this)); + public GHRepositoryStatistics getStatistics() { + // TODO: Use static object and introduce refresh() method, + // instead of returning new object each time. + return new GHRepositoryStatistics(this); } /** - * Search commits by specifying filters through a builder pattern. + * Gets subscribers count. * - * @return the gh commit query builder + * @return the subscribers count */ - public GHCommitQueryBuilder queryCommits() { - return new GHCommitQueryBuilder(this); + public int getSubscribersCount() { + return subscribersCount; } /** - * Lists up all the commit comments in this repository. + * Returns the current subscription. * - * @return the paged iterable + * @return null if no subscription exists. + * @throws IOException + * the io exception */ - public PagedIterable listCommitComments() { - return root().createRequest() - .withUrlPath(String.format("/repos/%s/%s/comments", getOwnerName(), name)) - .toIterable(GHCommitComment[].class, item -> item.wrap(this)); + public GHSubscription getSubscription() throws IOException { + try { + return root().createRequest() + .withUrlPath(getApiTailUrl("subscription")) + .fetch(GHSubscription.class) + .wrapUp(this); + } catch (FileNotFoundException e) { + return null; + } } /** - * Lists all comments on a specific commit. - * - * @param commitSha - * the hash of the commit + * Gets the Subversion URL to access this repository: https://github.com/rails/rails * - * @return the paged iterable + * @return the svn url */ - public PagedIterable listCommitComments(String commitSha) { - return root().createRequest() - .withUrlPath(String.format("/repos/%s/%s/commits/%s/comments", getOwnerName(), name, commitSha)) - .toIterable(GHCommitComment[].class, item -> item.wrap(this)); + public String getSvnUrl() { + return svnUrl; } /** - * Gets the basic license details for the repository. + * Returns the annotated tag object. Only valid if the {@link GHRef#getObject()} has a + * {@link GHRef.GHObject#getType()} of {@code tag}. * - * @return null if there's no license. + * @param sha + * the sha of the tag object + * @return the annotated tag object * @throws IOException - * as usual but also if you don't use the preview connector + * the io exception */ - public GHLicense getLicense() throws IOException { - GHContentWithLicense lic = getLicenseContent_(); - return lic != null ? lic.license : null; + public GHTagObject getTagObject(String sha) throws IOException { + return root().createRequest().withUrlPath(getApiTailUrl("git/tags/" + sha)).fetch(GHTagObject.class).wrap(this); } /** - * Retrieves the contents of the repository's license file - makes an additional API call. + * If this repository belongs to an organization, return a set of teams. * - * @return details regarding the license contents, or null if there's no license. + * @return the teams * @throws IOException - * as usual but also if you don't use the preview connector + * the io exception */ - public GHContent getLicenseContent() throws IOException { - return getLicenseContent_(); - } - - private GHContentWithLicense getLicenseContent_() throws IOException { - try { - return root().createRequest() - .withUrlPath(getApiTailUrl("license")) - .fetch(GHContentWithLicense.class) - .wrap(this); - } catch (FileNotFoundException e) { - return null; - } + public Set getTeams() throws IOException { + GHOrganization org = root().getOrganization(getOwnerName()); + return root().createRequest() + .withUrlPath(getApiTailUrl("teams")) + .toIterable(GHTeam[].class, item -> item.wrapUp(org)) + .toSet(); } /** - * /** Lists all the commit statuses attached to the given commit, newer ones first. + * Get Repository template was the repository created from. * - * @param sha1 - * the sha 1 - * @return the paged iterable + * @return the repository template */ - public PagedIterable listCommitStatuses(final String sha1) { - return root().createRequest() - .withUrlPath(String.format("/repos/%s/%s/statuses/%s", getOwnerName(), name, sha1)) - .toIterable(GHCommitStatus[].class, null); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHRepository getTemplateRepository() { + return templateRepository; } /** - * Gets the last status of this commit, which is what gets shown in the UI. + * Get the top 10 popular contents over the last 14 days as described on + * https://docs.github.com/en/rest/metrics/traffic?apiVersion=2022-11-28#get-top-referral-paths * - * @param sha1 - * the sha 1 - * @return the last commit status + * @return list of top referral paths * @throws IOException * the io exception */ - public GHCommitStatus getLastCommitStatus(String sha1) throws IOException { - List v = listCommitStatuses(sha1).toList(); - return v.isEmpty() ? null : v.get(0); + public List getTopReferralPaths() throws IOException { + return Arrays.asList(root().createRequest() + .method("GET") + .withUrlPath(getApiTailUrl("/traffic/popular/paths")) + .fetch(GHRepositoryTrafficTopReferralPath[].class)); } /** - * Gets check runs for given ref. + * Get the top 10 referrers over the last 14 days as described on + * https://docs.github.com/en/rest/metrics/traffic?apiVersion=2022-11-28#get-top-referral-sources * - * @param ref - * ref - * @return check runs for given ref - * @see List check runs - * for a specific ref + * @return list of top referrers + * @throws IOException + * the io exception */ - public PagedIterable getCheckRuns(String ref) { - GitHubRequest request = root().createRequest() - .withUrlPath(String.format("/repos/%s/%s/commits/%s/check-runs", getOwnerName(), name, ref)) - .build(); - return new GHCheckRunsIterable(this, request); + public List getTopReferralSources() throws IOException { + return Arrays.asList(root().createRequest() + .method("GET") + .withUrlPath(getApiTailUrl("/traffic/popular/referrers")) + .fetch(GHRepositoryTrafficTopReferralSources[].class)); } /** - * Gets check runs for given ref which validate provided parameters + * Retrieve a tree of the given type for the current GitHub repository. * - * @param ref - * the Git reference - * @param params - * a map of parameters to filter check runs - * @return check runs for the given ref - * @see List check runs - * for a specific ref + * @param sha + * sha number or branch name ex: "main" + * @return refs matching the request type + * @throws IOException + * on failure communicating with GitHub, potentially due to an invalid tree type being requested */ - public PagedIterable getCheckRuns(String ref, Map params) { - GitHubRequest request = root().createRequest() - .withUrlPath(String.format("/repos/%s/%s/commits/%s/check-runs", getOwnerName(), name, ref)) - .with(params) - .build(); - return new GHCheckRunsIterable(this, request); + public GHTree getTree(String sha) throws IOException { + String url = String.format("/repos/%s/%s/git/trees/%s", getOwnerName(), name, sha); + return root().createRequest().withUrlPath(url).fetch(GHTree.class).wrap(this); } /** - * Creates a commit status. + * Retrieves the tree for the current GitHub repository, recursively as described in here: + * https://developer.github.com/v3/git/trees/#get-a-tree-recursively * - * @param sha1 - * the sha 1 - * @param state - * the state - * @param targetUrl - * Optional parameter that points to the URL that has more details. - * @param description - * Optional short description. - * @param context - * Optional commit status context. - * @return the gh commit status + * @param sha + * sha number or branch name ex: "main" + * @param recursive + * use 1 + * @return the tree recursive * @throws IOException - * the io exception - */ - public GHCommitStatus createCommitStatus(String sha1, - GHCommitState state, - String targetUrl, - String description, - String context) throws IOException { - return root().createRequest() - .method("POST") - .with("state", state) - .with("target_url", targetUrl) - .with("description", description) - .with("context", context) - .withUrlPath(String.format("/repos/%s/%s/statuses/%s", getOwnerName(), this.name, sha1)) - .fetch(GHCommitStatus.class); + * on failure communicating with GitHub, potentially due to an invalid tree type being requested + */ + public GHTree getTreeRecursive(String sha, int recursive) throws IOException { + String url = String.format("/repos/%s/%s/git/trees/%s", getOwnerName(), name, sha); + return root().createRequest().with("recursive", recursive).withUrlPath(url).fetch(GHTree.class).wrap(this); } /** - * Create commit status gh commit status. + * Gets a repository variable. * - * @param sha1 - * the sha 1 - * @param state - * the state - * @param targetUrl - * the target url - * @param description - * the description - * @return the gh commit status + * @param name + * the variable name (e.g. test-variable) + * @return the variable * @throws IOException * the io exception - * @see #createCommitStatus(String, GHCommitState, String, String, String) #createCommitStatus(String, - * GHCommitState,String,String,String) */ - public GHCommitStatus createCommitStatus(String sha1, GHCommitState state, String targetUrl, String description) - throws IOException { - return createCommitStatus(sha1, state, targetUrl, description, null); + public GHRepositoryVariable getVariable(String name) throws IOException { + return GHRepositoryVariable.read(this, name); } /** - * Creates a check run for a commit. + * https://developer.github.com/v3/repos/traffic/#views * - * @param name - * an identifier for the run - * @param headSHA - * the commit hash - * @return a builder which you should customize, then call {@link GHCheckRunBuilder#create} + * @return the view traffic + * @throws IOException + * the io exception */ - public @NonNull GHCheckRunBuilder createCheckRun(@NonNull String name, @NonNull String headSHA) { - return new GHCheckRunBuilder(this, name, headSHA); + public GHRepositoryViewTraffic getViewTraffic() throws IOException { + return root().createRequest().withUrlPath(getApiTailUrl("/traffic/views")).fetch(GHRepositoryViewTraffic.class); } /** - * Updates an existing check run. + * Gets the visibility of the repository. * - * @param checkId - * the existing checkId - * @return a builder which you should customize, then call {@link GHCheckRunBuilder#create} + * @return the visibility */ - public @NonNull GHCheckRunBuilder updateCheckRun(long checkId) { - return new GHCheckRunBuilder(this, checkId); + public Visibility getVisibility() { + if (visibility == null) { + try { + populate(); + } catch (final IOException e) { + // Convert this to a runtime exception to avoid messy method signature + throw new GHException("Could not populate the visibility of the repository", e); + } + } + return Visibility.from(visibility); } /** - * Lists repository events. + * Gets the count of watchers. * - * @return the paged iterable + * @return the watchers */ - public PagedIterable listEvents() { - return root().createRequest() - .withUrlPath(String.format("/repos/%s/%s/events", getOwnerName(), name)) - .toIterable(GHEventInfo[].class, null); + public int getWatchersCount() { + return watchersCount; } /** - * Lists labels in this repository. - *

- * https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository + * Gets a workflow by name of the file. * - * @return the paged iterable + * @param nameOrId + * either the name of the file (e.g. my-workflow.yml) or the id as a string + * @return the workflow run + * @throws IOException + * the io exception */ - public PagedIterable listLabels() { - return GHLabel.readAll(this); + public GHWorkflow getWorkflow(String nameOrId) throws IOException { + return root().createRequest() + .withUrlPath(getApiTailUrl("actions/workflows"), nameOrId) + .fetch(GHWorkflow.class) + .wrapUp(this); } /** - * Gets label. + * Gets a workflow by id. * - * @param name - * the name - * @return the label + * @param id + * the id of the workflow run + * @return the workflow run * @throws IOException * the io exception */ - public GHLabel getLabel(String name) throws IOException { - return GHLabel.read(this, name); + public GHWorkflow getWorkflow(long id) throws IOException { + return getWorkflow(String.valueOf(id)); } /** - * Create label gh label. + * Gets a job from a workflow run by id. * - * @param name - * the name - * @param color - * the color - * @return the gh label + * @param id + * the id of the job + * @return the job * @throws IOException * the io exception */ - public GHLabel createLabel(String name, String color) throws IOException { - return GHLabel.create(this).name(name).color(color).description("").done(); + public GHWorkflowJob getWorkflowJob(long id) throws IOException { + return root().createRequest() + .withUrlPath(getApiTailUrl("/actions/jobs"), String.valueOf(id)) + .fetch(GHWorkflowJob.class) + .wrapUp(this); } /** - * Description is still in preview. + * Gets a workflow run. * - * @param name - * the name - * @param color - * the color - * @param description - * the description - * @return gh label + * @param id + * the id of the workflow run + * @return the workflow run * @throws IOException * the io exception */ - public GHLabel createLabel(String name, String color, String description) throws IOException { - return GHLabel.create(this).name(name).color(color).description(description).done(); + public GHWorkflowRun getWorkflowRun(long id) throws IOException { + return root().createRequest() + .withUrlPath(getApiTailUrl("actions/runs"), String.valueOf(id)) + .fetch(GHWorkflowRun.class) + .wrapUp(this); } /** - * Lists all the invitations. + * Has admin access boolean. * - * @return the paged iterable + * @return the boolean */ - public PagedIterable listInvitations() { - return root().createRequest() - .withUrlPath(String.format("/repos/%s/%s/invitations", getOwnerName(), name)) - .toIterable(GHInvitation[].class, null); + public boolean hasAdminAccess() { + return permissions != null && permissions.admin; } /** - * Lists all the subscribers (aka watchers.) - *

- * https://developer.github.com/v3/activity/watching/ + * Checks if the given user is an assignee for this repository. * - * @return the paged iterable + * @param u + * the u + * @return the boolean + * @throws IOException + * the io exception */ - public PagedIterable listSubscribers() { - return listUsers("subscribers"); + public boolean hasAssignee(GHUser u) throws IOException { + return root().createRequest().withUrlPath(getApiTailUrl("assignees/" + u.getLogin())).fetchHttpStatusCode() + / 100 == 2; } /** - * Lists all the users who have starred this repo based on new version of the API, having extended information like - * the time when the repository was starred. + * Has downloads boolean. * - * @return the paged iterable - * @deprecated Use {@link #listStargazers()} + * @return the boolean */ - @Deprecated - public PagedIterable listStargazers2() { - return listStargazers(); + public boolean hasDownloads() { + return hasDownloads; } /** - * Lists all the users who have starred this repo based on new version of the API, having extended information like - * the time when the repository was starred. + * Has issues boolean. * - * @return the paged iterable + * @return the boolean */ - public PagedIterable listStargazers() { - return root().createRequest() - .withAccept("application/vnd.github.star+json") - .withUrlPath(getApiTailUrl("stargazers")) - .toIterable(GHStargazer[].class, item -> item.wrapUp(this)); - } - - private PagedIterable listUsers(final String suffix) { - return listUsers(root().createRequest(), suffix); + public boolean hasIssues() { + return hasIssues; } - private PagedIterable listUsers(Requester requester, final String suffix) { - return requester.withUrlPath(getApiTailUrl(suffix)).toIterable(GHUser[].class, null); + /** + * Has pages boolean. + * + * @return the boolean + */ + public boolean hasPages() { + return hasPages; } /** - * See https://api.github.com/hooks for possible names and their configuration scheme. TODO: produce type-safe - * binding + * Check if a user has at least the given permission in this repository. * - * @param name - * Type of the hook to be created. See https://api.github.com/hooks for possible names. - * @param config - * The configuration hash. - * @param events - * Can be null. Types of events to hook into. - * @param active - * the active - * @return the gh hook + * @param user + * the user + * @param permission + * the permission to check + * @return true if the user has at least this permission level * @throws IOException * the io exception */ - public GHHook createHook(String name, Map config, Collection events, boolean active) - throws IOException { - return GHHooks.repoContext(this, owner).createHook(name, config, events, active); + public boolean hasPermission(GHUser user, GHPermissionType permission) throws IOException { + return hasPermission(user.getLogin(), permission); } /** - * Create web hook gh hook. + * Check if a user has at least the given permission in this repository. * - * @param url - * the url - * @param events - * the events - * @return the gh hook + * @param user + * a {@link GHUser#getLogin} + * @param permission + * the permission to check + * @return true if the user has at least this permission level * @throws IOException * the io exception */ - public GHHook createWebHook(URL url, Collection events) throws IOException { - return createHook("web", Collections.singletonMap("url", url.toExternalForm()), events, true); + public boolean hasPermission(String user, GHPermissionType permission) throws IOException { + return getPermission(user).implies(permission); } /** - * Create web hook gh hook. + * Has projects boolean. * - * @param url - * the url - * @return the gh hook - * @throws IOException - * the io exception + * @return the boolean */ - public GHHook createWebHook(URL url) throws IOException { - return createWebHook(url, null); + public boolean hasProjects() { + return hasProjects; + } + + /** + * Has pull access boolean. + * + * @return the boolean + */ + public boolean hasPullAccess() { + return permissions != null && permissions.pull; + } + + /** + * Has push access boolean. + * + * @return the boolean + */ + public boolean hasPushAccess() { + return permissions != null && permissions.push; } /** - * Gets branches by {@linkplain GHBranch#getName() their names}. + * Has wiki boolean. * - * @return the branches - * @throws IOException - * the io exception + * @return the boolean */ - public Map getBranches() throws IOException { - Map r = new TreeMap(); - for (GHBranch p : root().createRequest() - .withUrlPath(getApiTailUrl("branches")) - .toIterable(GHBranch[].class, item -> item.wrap(this)) - .toArray()) { - r.put(p.getName(), p); - } - return r; + public boolean hasWiki() { + return hasWiki; } /** - * Gets branch. + * Hash code. * - * @param name - * the name - * @return the branch - * @throws IOException - * the io exception + * @return the int */ - public GHBranch getBranch(String name) throws IOException { - return root().createRequest().withUrlPath(getApiTailUrl("branches/" + name)).fetch(GHBranch.class).wrap(this); + @Override + public int hashCode() { + return ("Repository:" + getOwnerName() + ":" + name).hashCode(); } /** - * Lists up all the milestones in this repository. + * Is allow private forks * - * @param state - * the state - * @return the paged iterable + * @return the boolean */ - public PagedIterable listMilestones(final GHIssueState state) { - return root().createRequest() - .with("state", state) - .withUrlPath(getApiTailUrl("milestones")) - .toIterable(GHMilestone[].class, item -> item.lateBind(this)); + public boolean isAllowForking() { + return allowForking; } /** - * Gets milestone. + * Is allow merge commit boolean. * - * @param number - * the number - * @return the milestone - * @throws IOException - * the io exception + * @return the boolean */ - public GHMilestone getMilestone(int number) throws IOException { - GHMilestone m = milestones.get(number); - if (m == null) { - m = root().createRequest().withUrlPath(getApiTailUrl("milestones/" + number)).fetch(GHMilestone.class); - m.owner = this; - milestones.put(m.getNumber(), m); - } - return m; + public boolean isAllowMergeCommit() { + return allowMergeCommit; } /** - * Gets file content. + * Is allow rebase merge boolean. * - * @param path - * the path - * @return the file content - * @throws IOException - * the io exception + * @return the boolean */ - public GHContent getFileContent(String path) throws IOException { - return getFileContent(path, null); + public boolean isAllowRebaseMerge() { + return allowRebaseMerge; } /** - * Gets file content. + * Is allow squash merge boolean. * - * @param path - * the path - * @param ref - * the ref - * @return the file content - * @throws IOException - * the io exception + * @return the boolean */ - public GHContent getFileContent(String path, String ref) throws IOException { - Requester requester = root().createRequest(); - String target = getApiTailUrl("contents/" + path); - - return requester.with("ref", ref).withUrlPath(target).fetch(GHContent.class).wrap(this); + public boolean isAllowSquashMerge() { + return allowSquashMerge; } /** - * Gets directory content. + * Is archived boolean. * - * @param path - * the path - * @return the directory content - * @throws IOException - * the io exception + * @return the boolean */ - public List getDirectoryContent(String path) throws IOException { - return getDirectoryContent(path, null); + public boolean isArchived() { + return archived; } /** - * Gets directory content. + * Checks if the given user is a collaborator for this repository. * - * @param path - * the path - * @param ref - * the ref - * @return the directory content + * @param user + * a {@link GHUser} + * @return true if the user is a collaborator for this repository * @throws IOException * the io exception */ - public List getDirectoryContent(String path, String ref) throws IOException { - Requester requester = root().createRequest(); - while (path.endsWith("/")) { - path = path.substring(0, path.length() - 1); - } - String target = getApiTailUrl("contents/" + path); + public boolean isCollaborator(GHUser user) throws IOException { + return root().createRequest() + .withUrlPath(getApiTailUrl("collaborators/" + user.getLogin())) + .fetchHttpStatusCode() == 204; + } - return requester.with("ref", ref) - .withUrlPath(target) - .toIterable(GHContent[].class, item -> item.wrap(this)) - .toList(); + /** + * Automatically deleting head branches when pull requests are merged. + * + * @return the boolean + */ + public boolean isDeleteBranchOnMerge() { + return deleteBranchOnMerge; } /** - * https://developer.github.com/v3/repos/contents/#get-the-readme + * Is disabled boolean. * - * @return the readme - * @throws IOException - * the io exception + * @return the boolean */ - public GHContent getReadme() throws IOException { - Requester requester = root().createRequest(); - return requester.withUrlPath(getApiTailUrl("readme")).fetch(GHContent.class).wrap(this); + public boolean isDisabled() { + return disabled; } /** - * Create a repository variable. + * Is fork boolean. * - * @param name - * the variable name (e.g. test-variable) - * @param value - * the value - * @throws IOException - * the io exception + * @return the boolean */ - public void createVariable(String name, String value) throws IOException { - GHRepositoryVariable.create(this).name(name).value(value).done(); + public boolean isFork() { + return fork; } /** - * Gets a repository variable. + * Is private boolean. * - * @param name - * the variable name (e.g. test-variable) - * @return the variable - * @throws IOException - * the io exception + * @return the boolean */ - public GHRepositoryVariable getVariable(String name) throws IOException { - return GHRepositoryVariable.read(this, name); + public boolean isPrivate() { + return isPrivate; } /** - * Creates a new content, or update an existing content. + * Is template boolean. * - * @return the gh content builder + * @return the boolean */ - public GHContentBuilder createContent() { - return new GHContentBuilder(this); + public boolean isTemplate() { + if (isTemplate == null) { + try { + populate(); + } catch (IOException e) { + // Convert this to a runtime exception to avoid messy method signature + throw new GHException("Could not populate the template setting of the repository", e); + } + // if this somehow is not populated, set it to false; + isTemplate = Boolean.TRUE.equals(isTemplate); + } + return isTemplate; } /** - * Create milestone gh milestone. + * Check, if vulnerability alerts are enabled for this repository + * (https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#check-if-vulnerability-alerts-are-enabled-for-a-repository). * - * @param title - * the title - * @param description - * the description - * @return the gh milestone + * @return true, if vulnerability alerts are enabled * @throws IOException * the io exception */ - public GHMilestone createMilestone(String title, String description) throws IOException { + public boolean isVulnerabilityAlertsEnabled() throws IOException { return root().createRequest() - .method("POST") - .with("title", title) - .with("description", description) - .withUrlPath(getApiTailUrl("milestones")) - .fetch(GHMilestone.class) - .lateBind(this); + .method("GET") + .withUrlPath(getApiTailUrl("/vulnerability-alerts")) + .fetchHttpStatusCode() == 204; } /** - * Add deploy key gh deploy key. + * Lists all the artifacts of this repository. * - * @param title - * the title - * @param key - * the key - * @return the gh deploy key - * @throws IOException - * the io exception + * @return the paged iterable */ - public GHDeployKey addDeployKey(String title, String key) throws IOException { - return addDeployKey(title, key, false); + public PagedIterable listArtifacts() { + return new GHArtifactsIterable(this, root().createRequest().withUrlPath(getApiTailUrl("actions/artifacts"))); } /** - * Add deploy key gh deploy key. + * Lists all + * the + * available assignees to which issues may be assigned. * - * @param title - * the title - * @param key - * the key - * @param readOnly - * read-only ability of the key - * @return the gh deploy key - * @throws IOException - * the io exception + * @return the paged iterable */ - public GHDeployKey addDeployKey(String title, String key, boolean readOnly) throws IOException { + public PagedIterable listAssignees() { + return listUsers("assignees"); + } + + /** + * List all autolinks of a repo (admin only). + * (https://docs.github.com/en/rest/repos/autolinks?apiVersion=2022-11-28#get-all-autolinks-of-a-repository) + * + * @return all autolinks in the repo + */ + public PagedIterable listAutolinks() { return root().createRequest() - .method("POST") - .with("title", title) - .with("key", key) - .with("read_only", readOnly) - .withUrlPath(getApiTailUrl("keys")) - .fetch(GHDeployKey.class) - .lateBind(this); + .withHeader("Accept", "application/vnd.github+json") + .withUrlPath(String.format("/repos/%s/%s/autolinks", getOwnerName(), getName())) + .toIterable(GHAutolink[].class, item -> item.lateBind(this)); } /** - * Gets deploy keys. + * List errors in the {@code CODEOWNERS} file. Note that GitHub skips lines with incorrect syntax; these are + * reported in the web interface, but not in the API call which this library uses. * - * @return the deploy keys + * @return the list of errors * @throws IOException * the io exception */ - public List getDeployKeys() throws IOException { + public List listCodeownersErrors() throws IOException { return root().createRequest() - .withUrlPath(getApiTailUrl("keys")) - .toIterable(GHDeployKey[].class, item -> item.lateBind(this)) - .toList(); + .withUrlPath(getApiTailUrl("codeowners/errors")) + .fetch(GHCodeownersErrors.class).errors; } /** - * Forked repositories have a 'source' attribute that specifies the ultimate source of the forking chain. + * Lists up the collaborators on this repository. * - * @return {@link GHRepository} that points to the root repository where this repository is forked (indirectly or - * directly) from. Otherwise null. - * @throws IOException - * the io exception - * @see #getParent() #getParent() + * @return Users paged iterable */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHRepository getSource() throws IOException { - if (fork && source == null) { - populate(); - } - if (source == null) { - return null; - } - - return source; + public PagedIterable listCollaborators() { + return listUsers("collaborators"); } /** - * Forked repositories have a 'parent' attribute that specifies the repository this repository is directly forked - * from. If we keep traversing {@link #getParent()} until it returns null, that is {@link #getSource()}. + * Lists up the collaborators on this repository. * - * @return {@link GHRepository} that points to the repository where this repository is forked directly from. - * Otherwise null. - * @throws IOException - * the io exception - * @see #getSource() #getSource() + * @param affiliation + * Filter users by affiliation + * @return Users paged iterable */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHRepository getParent() throws IOException { - if (fork && parent == null) { - populate(); - } - - if (parent == null) { - return null; - } - return parent; + public PagedIterable listCollaborators(CollaboratorAffiliation affiliation) { + return listUsers(root().createRequest().with("affiliation", affiliation), "collaborators"); } /** - * Subscribes to this repository to get notifications. + * Lists up all the commit comments in this repository. * - * @param subscribed - * the subscribed - * @param ignored - * the ignored - * @return the gh subscription - * @throws IOException - * the io exception + * @return the paged iterable */ - public GHSubscription subscribe(boolean subscribed, boolean ignored) throws IOException { + public PagedIterable listCommitComments() { return root().createRequest() - .method("PUT") - .with("subscribed", subscribed) - .with("ignored", ignored) - .withUrlPath(getApiTailUrl("subscription")) - .fetch(GHSubscription.class) - .wrapUp(this); + .withUrlPath(String.format("/repos/%s/%s/comments", getOwnerName(), name)) + .toIterable(GHCommitComment[].class, item -> item.wrap(this)); } /** - * Returns the current subscription. + * Lists all comments on a specific commit. * - * @return null if no subscription exists. - * @throws IOException - * the io exception + * @param commitSha + * the hash of the commit + * + * @return the paged iterable */ - public GHSubscription getSubscription() throws IOException { - try { - return root().createRequest() - .withUrlPath(getApiTailUrl("subscription")) - .fetch(GHSubscription.class) - .wrapUp(this); - } catch (FileNotFoundException e) { - return null; - } + public PagedIterable listCommitComments(String commitSha) { + return root().createRequest() + .withUrlPath(String.format("/repos/%s/%s/commits/%s/comments", getOwnerName(), name, commitSha)) + .toIterable(GHCommitComment[].class, item -> item.wrap(this)); } - // Only used within listCodeownersErrors(). - private static class GHCodeownersErrors { - public List errors; + /** + * /** Lists all the commit statuses attached to the given commit, newer ones first. + * + * @param sha1 + * the sha 1 + * @return the paged iterable + */ + public PagedIterable listCommitStatuses(final String sha1) { + return root().createRequest() + .withUrlPath(String.format("/repos/%s/%s/statuses/%s", getOwnerName(), name, sha1)) + .toIterable(GHCommitStatus[].class, null); } /** - * List errors in the {@code CODEOWNERS} file. Note that GitHub skips lines with incorrect syntax; these are - * reported in the web interface, but not in the API call which this library uses. + * Lists all the commits. * - * @return the list of errors - * @throws IOException - * the io exception + * @return the paged iterable */ - public List listCodeownersErrors() throws IOException { + public PagedIterable listCommits() { return root().createRequest() - .withUrlPath(getApiTailUrl("codeowners/errors")) - .fetch(GHCodeownersErrors.class).errors; + .withUrlPath(String.format("/repos/%s/%s/commits", getOwnerName(), name)) + .toIterable(GHCommit[].class, item -> item.wrapUp(this)); } /** @@ -2691,96 +2702,151 @@ public PagedIterable listContributors(Boolean includeAnonymous) { } /** - * The type Contributor. + * List deployments paged iterable. + * + * @param sha + * the sha + * @param ref + * the ref + * @param task + * the task + * @param environment + * the environment + * @return the paged iterable */ - public static class Contributor extends GHUser { + public PagedIterable listDeployments(String sha, String ref, String task, String environment) { + return root().createRequest() + .with("sha", sha) + .with("ref", ref) + .with("task", task) + .with("environment", environment) + .withUrlPath(getApiTailUrl("deployments")) + .toIterable(GHDeployment[].class, item -> item.wrap(this)); + } - /** - * Create default Contributor instance - */ - public Contributor() { - } + /** + * Lists repository events. + * + * @return the paged iterable + */ + public PagedIterable listEvents() { + return root().createRequest() + .withUrlPath(String.format("/repos/%s/%s/events", getOwnerName(), name)) + .toIterable(GHEventInfo[].class, null); + } - private int contributions; + /** + * Lists all the direct forks of this repository, sorted by github api default, currently {@link ForkSort#NEWEST + * ForkSort.NEWEST}*. + * + * @return the paged iterable + */ + public PagedIterable listForks() { + return listForks(null); + } - /** - * Gets contributions. - * - * @return the contributions - */ - public int getContributions() { - return contributions; - } + /** + * Lists all the direct forks of this repository, sorted by the given sort order. + * + * @param sort + * the sort order. If null, defaults to github api default, currently {@link ForkSort#NEWEST + * ForkSort.NEWEST}. + * @return the paged iterable + */ + public PagedIterable listForks(final ForkSort sort) { + return root().createRequest() + .with("sort", sort) + .withUrlPath(getApiTailUrl("forks")) + .toIterable(GHRepository[].class, null); + } - /** - * Hash code. - * - * @return the int - */ - @Override - public int hashCode() { - // We ignore contributions in the calculation - return super.hashCode(); - } + /** + * Lists all the invitations. + * + * @return the paged iterable + */ + public PagedIterable listInvitations() { + return root().createRequest() + .withUrlPath(String.format("/repos/%s/%s/invitations", getOwnerName(), name)) + .toIterable(GHInvitation[].class, null); + } - /** - * Equals. - * - * @param obj - * the obj - * @return true, if successful - */ - @Override - public boolean equals(Object obj) { - // We ignore contributions in the calculation - return super.equals(obj); - } + /** + * Get all issue events for this repository. See + * https://developer.github.com/v3/issues/events/#list-events-for-a-repository + * + * @return the paged iterable + */ + public PagedIterable listIssueEvents() { + return root().createRequest() + .withUrlPath(getApiTailUrl("issues/events")) + .toIterable(GHIssueEvent[].class, null); + } + + /** + * Lists labels in this repository. + *

+ * https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository + * + * @return the paged iterable + */ + public PagedIterable listLabels() { + return GHLabel.readAll(this); + } + + /** + * List languages for the specified repository. The value on the right of a language is the number of bytes of code + * written in that language. { "C": 78769, "Python": 7769 } + * + * @return the map + * @throws IOException + * the io exception + */ + public Map listLanguages() throws IOException { + HashMap result = new HashMap<>(); + root().createRequest().withUrlPath(getApiTailUrl("languages")).fetch(HashMap.class).forEach((key, value) -> { + Long addValue = -1L; + if (value instanceof Integer) { + addValue = Long.valueOf((Integer) value); + } + result.put(key.toString(), addValue); + }); + return result; } /** - * Returns the statistics for this repository. + * Retrieves all refs that match the given prefix using the matching-refs endpoint. This is useful to avoid fetching + * all available refs. * - * @return the statistics + * @param refPrefix + * the ref prefix to match e.g. heads/main or tags/v1 + * @return paged iterable of all refs matching the specified prefix */ - public GHRepositoryStatistics getStatistics() { - // TODO: Use static object and introduce refresh() method, - // instead of returning new object each time. - return new GHRepositoryStatistics(this); + public PagedIterable listMatchingRefs(String refPrefix) { + return GHRef.readMatchingRefs(this, refPrefix); } /** - * Create a project for this repository. + * Lists up all the milestones in this repository. * - * @param name - * the name - * @param body - * the body - * @return the gh project - * @throws IOException - * the io exception + * @param state + * the state + * @return the paged iterable */ - public GHProject createProject(String name, String body) throws IOException { + public PagedIterable listMilestones(final GHIssueState state) { return root().createRequest() - .method("POST") - .with("name", name) - .with("body", body) - .withUrlPath(getApiTailUrl("projects")) - .fetch(GHProject.class) - .lateBind(this); + .with("state", state) + .withUrlPath(getApiTailUrl("milestones")) + .toIterable(GHMilestone[].class, item -> item.lateBind(this)); } /** - * Returns the projects for this repository. + * List all the notifications in a repository for the current user. * - * @param status - * The status filter (all, open or closed). - * @return the paged iterable + * @return the gh notification stream */ - public PagedIterable listProjects(final GHProject.ProjectStateFilter status) { - return root().createRequest() - .with("state", status) - .withUrlPath(getApiTailUrl("projects")) - .toIterable(GHProject[].class, item -> item.lateBind(this)); + public GHNotificationStream listNotifications() { + return new GHNotificationStream(root(), getApiTailUrl("/notifications")); } /** @@ -2795,129 +2861,123 @@ public PagedIterable listProjects() throws IOException { } /** - * Render a Markdown document. - *

- * In {@linkplain MarkdownMode#GFM GFM mode}, issue numbers and user mentions are linked accordingly. + * Returns the projects for this repository. * - * @param text - * the text - * @param mode - * the mode - * @return the reader - * @throws IOException - * the io exception - * @see GitHub#renderMarkdown(String) GitHub#renderMarkdown(String) + * @param status + * The status filter (all, open or closed). + * @return the paged iterable */ - public Reader renderMarkdown(String text, MarkdownMode mode) throws IOException { - return new InputStreamReader( - root().createRequest() - .method("POST") - .with("text", text) - .with("mode", mode == null ? null : mode.toString()) - .with("context", getFullName()) - .withUrlPath("/markdown") - .fetchStream(Requester::copyInputStream), - "UTF-8"); + public PagedIterable listProjects(final GHProject.ProjectStateFilter status) { + return root().createRequest() + .with("state", status) + .withUrlPath(getApiTailUrl("projects")) + .toIterable(GHProject[].class, item -> item.lateBind(this)); } /** - * List all the notifications in a repository for the current user. + * Retrieves all refs for the github repository. * - * @return the gh notification stream + * @return paged iterable of all refs */ - public GHNotificationStream listNotifications() { - return new GHNotificationStream(root(), getApiTailUrl("/notifications")); + public PagedIterable listRefs() { + return listRefs(""); } /** - * https://developer.github.com/v3/repos/traffic/#views + * Retrieves all refs of the given type for the current GitHub repository. * - * @return the view traffic - * @throws IOException - * the io exception + * @param refType + * the type of reg to search for e.g. tags or commits + * @return paged iterable of all refs of the specified type */ - public GHRepositoryViewTraffic getViewTraffic() throws IOException { - return root().createRequest().withUrlPath(getApiTailUrl("/traffic/views")).fetch(GHRepositoryViewTraffic.class); + public PagedIterable listRefs(String refType) { + return GHRef.readMatching(this, refType); } /** - * https://developer.github.com/v3/repos/traffic/#clones + * List releases paged iterable. * - * @return the clone traffic - * @throws IOException - * the io exception + * @return the paged iterable */ - public GHRepositoryCloneTraffic getCloneTraffic() throws IOException { + public PagedIterable listReleases() { return root().createRequest() - .withUrlPath(getApiTailUrl("/traffic/clones")) - .fetch(GHRepositoryCloneTraffic.class); + .withUrlPath(getApiTailUrl("releases")) + .toIterable(GHRelease[].class, item -> item.wrap(this)); } /** - * Hash code. + * Get all active rules that apply to the specified branch + * (https://docs.github.com/en/rest/repos/rules?apiVersion=2022-11-28#get-rules-for-a-branch). * - * @return the int + * @param branch + * the branch + * @return the rules for branch */ - @Override - public int hashCode() { - return ("Repository:" + getOwnerName() + ":" + name).hashCode(); + public PagedIterable listRulesForBranch(String branch) { + return root().createRequest() + .method("GET") + .withUrlPath(getApiTailUrl("/rules/branches/" + branch)) + .toIterable(GHRepositoryRule[].class, null); } /** - * Equals. + * Lists all the users who have starred this repo based on new version of the API, having extended information like + * the time when the repository was starred. * - * @param obj - * the obj - * @return true, if successful + * @return the paged iterable */ - @Override - public boolean equals(Object obj) { - if (obj instanceof GHRepository) { - GHRepository that = (GHRepository) obj; - return this.getOwnerName().equals(that.getOwnerName()) && this.name.equals(that.name); - } - return false; + public PagedIterable listStargazers() { + return root().createRequest() + .withAccept("application/vnd.github.star+json") + .withUrlPath(getApiTailUrl("stargazers")) + .toIterable(GHStargazer[].class, item -> item.wrapUp(this)); } /** - * Gets the api tail url. + * Lists all the users who have starred this repo based on new version of the API, having extended information like + * the time when the repository was starred. * - * @param tail - * the tail - * @return the api tail url + * @return the paged iterable + * @deprecated Use {@link #listStargazers()} */ - String getApiTailUrl(String tail) { - if (tail.length() > 0 && !tail.startsWith("/")) { - tail = '/' + tail; - } - return "/repos/" + full_name + tail; + @Deprecated + public PagedIterable listStargazers2() { + return listStargazers(); } /** - * Get all issue events for this repository. See - * https://developer.github.com/v3/issues/events/#list-events-for-a-repository + * Lists all the subscribers (aka watchers.) + *

+ * https://developer.github.com/v3/activity/watching/ * * @return the paged iterable */ - public PagedIterable listIssueEvents() { + public PagedIterable listSubscribers() { + return listUsers("subscribers"); + } + + /** + * List tags paged iterable. + * + * @return the paged iterable + */ + public PagedIterable listTags() { return root().createRequest() - .withUrlPath(getApiTailUrl("issues/events")) - .toIterable(GHIssueEvent[].class, null); + .withUrlPath(getApiTailUrl("tags")) + .toIterable(GHTag[].class, item -> item.wrap(this)); } /** - * Get a single issue event. See https://developer.github.com/v3/issues/events/#get-a-single-event + * Return the topics for this repository. See + * https://developer.github.com/v3/repos/#list-all-topics-for-a-repository * - * @param id - * the id - * @return the issue event + * @return the list * @throws IOException * the io exception */ - public GHIssueEvent getIssueEvent(long id) throws IOException { - return root().createRequest().withUrlPath(getApiTailUrl("issues/events/" + id)).fetch(GHIssueEvent.class); + public List listTopics() throws IOException { + Topics topics = root().createRequest().withUrlPath(getApiTailUrl("topics")).fetch(Topics.class); + return topics.names; } /** @@ -2930,32 +2990,30 @@ public PagedIterable listWorkflows() { } /** - * Gets a workflow by id. + * Search commits by specifying filters through a builder pattern. * - * @param id - * the id of the workflow run - * @return the workflow run - * @throws IOException - * the io exception + * @return the gh commit query builder */ - public GHWorkflow getWorkflow(long id) throws IOException { - return getWorkflow(String.valueOf(id)); + public GHCommitQueryBuilder queryCommits() { + return new GHCommitQueryBuilder(this); } /** - * Gets a workflow by name of the file. + * Retrieves issues. * - * @param nameOrId - * either the name of the file (e.g. my-workflow.yml) or the id as a string - * @return the workflow run - * @throws IOException - * the io exception + * @return the gh issue query builder */ - public GHWorkflow getWorkflow(String nameOrId) throws IOException { - return root().createRequest() - .withUrlPath(getApiTailUrl("actions/workflows"), nameOrId) - .fetch(GHWorkflow.class) - .wrapUp(this); + public GHIssueQueryBuilder.ForRepository queryIssues() { + return new GHIssueQueryBuilder.ForRepository(this); + } + + /** + * Retrieves pull requests. + * + * @return the gh pull request query builder + */ + public GHPullRequestQueryBuilder queryPullRequests() { + return new GHPullRequestQueryBuilder(this); } /** @@ -2968,437 +3026,427 @@ public GHWorkflowRunQueryBuilder queryWorkflowRuns() { } /** - * Gets a workflow run. + * Read an autolink by ID. + * (https://docs.github.com/en/rest/repos/autolinks?apiVersion=2022-11-28#get-an-autolink-reference-of-a-repository) * - * @param id - * the id of the workflow run - * @return the workflow run + * @param autolinkId + * the autolink id + * @return the autolink * @throws IOException * the io exception */ - public GHWorkflowRun getWorkflowRun(long id) throws IOException { + public GHAutolink readAutolink(int autolinkId) throws IOException { + return root().createRequest() + .withHeader("Accept", "application/vnd.github+json") + .withUrlPath(String.format("/repos/%s/%s/autolinks/%d", getOwnerName(), getName(), autolinkId)) + .fetch(GHAutolink.class) + .lateBind(this); + } + + /** + * Reads the content of a blob as a stream for better efficiency. + * + * @param blobSha + * the blob sha + * @return the input stream + * @throws IOException + * the io exception + * @see Get a blob + * @see #getBlob(String) #getBlob(String) + */ + public InputStream readBlob(String blobSha) throws IOException { + String target = getApiTailUrl("git/blobs/" + blobSha); + + // https://developer.github.com/v3/media/ describes this media type return root().createRequest() - .withUrlPath(getApiTailUrl("actions/runs"), String.valueOf(id)) - .fetch(GHWorkflowRun.class) - .wrapUp(this); + .withHeader("Accept", "application/vnd.github.raw") + .withUrlPath(target) + .fetchStream(Requester::copyInputStream); } /** - * Lists all the artifacts of this repository. + * Streams a tar archive of the repository, optionally at a given ref. * - * @return the paged iterable + * @param + * the type of result + * @param streamFunction + * The {@link InputStreamFunction} that will process the stream + * @param ref + * if null the repository's default branch, usually main, + * @return the result of reading the stream. + * @throws IOException + * The IO exception. */ - public PagedIterable listArtifacts() { - return new GHArtifactsIterable(this, root().createRequest().withUrlPath(getApiTailUrl("actions/artifacts"))); + public T readTar(InputStreamFunction streamFunction, String ref) throws IOException { + return downloadArchive("tar", ref, streamFunction); } /** - * Gets an artifact by id. + * Streams a zip archive of the repository, optionally at a given ref. * - * @param id - * the id of the artifact - * @return the artifact + * @param + * the type of result + * @param streamFunction + * The {@link InputStreamFunction} that will process the stream + * @param ref + * if null the repository's default branch, usually main, + * @return the result of reading the stream. * @throws IOException - * the io exception + * The IO exception. */ - public GHArtifact getArtifact(long id) throws IOException { - return root().createRequest() - .withUrlPath(getApiTailUrl("actions/artifacts"), String.valueOf(id)) - .fetch(GHArtifact.class) - .wrapUp(this); + public T readZip(InputStreamFunction streamFunction, String ref) throws IOException { + return downloadArchive("zip", ref, streamFunction); } /** - * Gets a job from a workflow run by id. + * Remove collaborators. * - * @param id - * the id of the job - * @return the job + * @param users + * the users * @throws IOException * the io exception */ - public GHWorkflowJob getWorkflowJob(long id) throws IOException { - return root().createRequest() - .withUrlPath(getApiTailUrl("/actions/jobs"), String.valueOf(id)) - .fetch(GHWorkflowJob.class) - .wrapUp(this); + public void removeCollaborators(Collection users) throws IOException { + modifyCollaborators(users, "DELETE", null); } /** - * Gets the public key for the given repo. + * Remove collaborators. * - * @return the public key + * @param users + * the users * @throws IOException * the io exception */ - public GHRepositoryPublicKey getPublicKey() throws IOException { - return root().createRequest() - .withUrlPath(getApiTailUrl("/actions/secrets/public-key")) - .fetch(GHRepositoryPublicKey.class) - .wrapUp(this); - } - - // Only used within listTopics(). - private static class Topics { - public List names; + public void removeCollaborators(GHUser... users) throws IOException { + removeCollaborators(asList(users)); } /** - * Return the topics for this repository. See - * https://developer.github.com/v3/repos/#list-all-topics-for-a-repository + * Rename this repository. * - * @return the list + * @param name + * the name * @throws IOException * the io exception */ - public List listTopics() throws IOException { - Topics topics = root().createRequest().withUrlPath(getApiTailUrl("topics")).fetch(Topics.class); - return topics.names; + public void renameTo(String name) throws IOException { + set().name(name); } /** - * Set the topics for this repository. See - * https://developer.github.com/v3/repos/#replace-all-topics-for-a-repository + * Render a Markdown document. + *

+ * In {@linkplain MarkdownMode#GFM GFM mode}, issue numbers and user mentions are linked accordingly. * - * @param topics - * the topics + * @param text + * the text + * @param mode + * the mode + * @return the reader * @throws IOException * the io exception + * @see GitHub#renderMarkdown(String) GitHub#renderMarkdown(String) */ - public void setTopics(List topics) throws IOException { - root().createRequest().method("PUT").with("names", topics).withUrlPath(getApiTailUrl("topics")).send(); + public Reader renderMarkdown(String text, MarkdownMode mode) throws IOException { + return new InputStreamReader( + root().createRequest() + .method("POST") + .with("text", text) + .with("mode", mode == null ? null : mode.toString()) + .with("context", getFullName()) + .withUrlPath("/markdown") + .fetchStream(Requester::copyInputStream), + "UTF-8"); } /** - * Set/Update a repository secret - * "https://docs.github.com/rest/reference/actions#create-or-update-a-repository-secret" + * Retrieves pull requests according to search terms. * - * @param secretName - * the name of the secret - * @param encryptedValue - * The encrypted value for this secret - * @param publicKeyId - * The id of the Public Key used to encrypt this secret - * @throws IOException - * the io exception + * @return gh pull request search builder for current repository */ - public void createSecret(String secretName, String encryptedValue, String publicKeyId) throws IOException { - root().createRequest() - .method("PUT") - .with("encrypted_value", encryptedValue) - .with("key_id", publicKeyId) - .withUrlPath(getApiTailUrl("actions/secrets") + "/" + secretName) - .send(); + public GHPullRequestSearchBuilder searchPullRequests() { + return new GHPullRequestSearchBuilder(this.root()).repo(this); } /** - * Create a tag. See https://developer.github.com/v3/git/tags/#create-a-tag-object + * Creates a builder that can be used to bulk update repository settings. * - * @param tag - * The tag's name. - * @param message - * The tag message. - * @param object - * The SHA of the git object this is tagging. - * @param type - * The type of the object we're tagging: "commit", "tree" or "blob". - * @return The newly created tag. - * @throws IOException - * Signals that an I/O exception has occurred. + * @return the repository updater */ - public GHTagObject createTag(String tag, String message, String object, String type) throws IOException { - return root().createRequest() - .method("POST") - .with("tag", tag) - .with("message", message) - .with("object", object) - .with("type", type) - .withUrlPath(getApiTailUrl("git/tags")) - .fetch(GHTagObject.class) - .wrap(this); + public Setter set() { + return new Setter(this); } /** - * Streams a zip archive of the repository, optionally at a given ref. + * Sets {@link #getCompare(String, String)} to return a {@link GHCompare} that uses a paginated commit list instead + * of limiting to 250 results. * - * @param - * the type of result - * @param streamFunction - * The {@link InputStreamFunction} that will process the stream - * @param ref - * if null the repository's default branch, usually main, - * @return the result of reading the stream. - * @throws IOException - * The IO exception. + * By default, {@link GHCompare} returns all commits in the comparison as part of the request, limited to 250 + * results. More recently GitHub added the ability to return the commits as a paginated query allowing for more than + * 250 results. + * + * @param value + * true if you want commits returned in paginated form. */ - public T readZip(InputStreamFunction streamFunction, String ref) throws IOException { - return downloadArchive("zip", ref, streamFunction); + public void setCompareUsePaginatedCommits(boolean value) { + compareUsePaginatedCommits = value; } /** - * Streams a tar archive of the repository, optionally at a given ref. + * Sets default branch. * - * @param - * the type of result - * @param streamFunction - * The {@link InputStreamFunction} that will process the stream - * @param ref - * if null the repository's default branch, usually main, - * @return the result of reading the stream. + * @param value + * the value * @throws IOException - * The IO exception. + * the io exception */ - public T readTar(InputStreamFunction streamFunction, String ref) throws IOException { - return downloadArchive("tar", ref, streamFunction); + public void setDefaultBranch(String value) throws IOException { + set().defaultBranch(value); } /** - * Create a repository dispatch event, which can be used to start a workflow/action from outside github, as - * described on https://docs.github.com/en/rest/reference/repos#create-a-repository-dispatch-event + * Sets description. * - * @param - * type of client payload - * @param eventType - * the eventType - * @param clientPayload - * a custom payload , can be nullable + * @param value + * the value * @throws IOException * the io exception */ - public void dispatch(String eventType, @Nullable T clientPayload) throws IOException { - root().createRequest() - .method("POST") - .withUrlPath(getApiTailUrl("dispatches")) - .with("event_type", eventType) - .with("client_payload", clientPayload) - .send(); - } - - private T downloadArchive(@Nonnull String type, - @CheckForNull String ref, - @Nonnull InputStreamFunction streamFunction) throws IOException { - requireNonNull(streamFunction, "Sink must not be null"); - String tailUrl = getApiTailUrl(type + "ball"); - if (ref != null) { - tailUrl += "/" + ref; - } - final Requester builder = root().createRequest().method("GET").withUrlPath(tailUrl); - return builder.fetchStream(streamFunction); + public void setDescription(String value) throws IOException { + set().description(value); } /** - * Populate this object. + * Sets email service hook. * + * @param address + * the address * @throws IOException - * Signals that an I/O exception has occurred. + * the io exception */ - void populate() throws IOException { - if (isOffline()) { - return; // can't populate if the root is offline - } - - // We don't use the URL provided in the JSON because it is not reliable: - // 1. There is bug in Push event payloads that returns the wrong url. - // For Push event repository records, they take the form - // "https://github.com/{fullName}". - // All other occurrences of "url" take the form "https://api.github.com/...". - // 2. For Installation event payloads, the URL is not provided at all. - root().createRequest().withUrlPath(getApiTailUrl("")).fetchInto(this); + public void setEmailServiceHook(String address) throws IOException { + Map config = new HashMap<>(); + config.put("address", address); + root().createRequest() + .method("POST") + .with("name", "email") + .with("config", config) + .with("active", true) + .withUrlPath(getApiTailUrl("hooks")) + .send(); } /** - * A {@link GHRepositoryBuilder} that allows multiple properties to be updated per request. + * Sets homepage. * - * Consumer must call {@link #done()} to commit changes. + * @param value + * the value + * @throws IOException + * the io exception */ - @BetaApi - public static class Updater extends GHRepositoryBuilder { - - /** - * Instantiates a new updater. - * - * @param repository - * the repository - */ - protected Updater(@Nonnull GHRepository repository) { - super(Updater.class, repository.root(), null); - // even when we don't change the name, we need to send it in - // this requirement may be out-of-date, but we do not want to break it - requester.with("name", repository.name); - - requester.method("PATCH").withUrlPath(repository.getApiTailUrl("")); - } + public void setHomepage(String value) throws IOException { + set().homepage(value); } /** - * Star a repository. + * Sets private. * + * @param value + * the value * @throws IOException * the io exception */ - public void star() throws IOException { - root().createRequest().method("PUT").withUrlPath(String.format("/user/starred/%s", full_name)).send(); + public void setPrivate(boolean value) throws IOException { + set().private_(value); } /** - * Unstar a repository. + * Set the topics for this repository. See + * https://developer.github.com/v3/repos/#replace-all-topics-for-a-repository * + * @param topics + * the topics * @throws IOException * the io exception */ - public void unstar() throws IOException { - root().createRequest().method("DELETE").withUrlPath(String.format("/user/starred/%s", full_name)).send(); + public void setTopics(List topics) throws IOException { + root().createRequest().method("PUT").with("names", topics).withUrlPath(getApiTailUrl("topics")).send(); } /** - * Get the top 10 popular contents over the last 14 days as described on - * https://docs.github.com/en/rest/metrics/traffic?apiVersion=2022-11-28#get-top-referral-paths + * Sets visibility. * - * @return list of top referral paths + * @param value + * the value * @throws IOException * the io exception */ - public List getTopReferralPaths() throws IOException { - return Arrays.asList(root().createRequest() - .method("GET") - .withUrlPath(getApiTailUrl("/traffic/popular/paths")) - .fetch(GHRepositoryTrafficTopReferralPath[].class)); + public void setVisibility(final Visibility value) throws IOException { + root().createRequest() + .method("PATCH") + .with("name", name) + .with("visibility", value) + .withUrlPath(getApiTailUrl("")) + .send(); } /** - * Get the top 10 referrers over the last 14 days as described on - * https://docs.github.com/en/rest/metrics/traffic?apiVersion=2022-11-28#get-top-referral-sources + * Star a repository. * - * @return list of top referrers * @throws IOException * the io exception */ - public List getTopReferralSources() throws IOException { - return Arrays.asList(root().createRequest() - .method("GET") - .withUrlPath(getApiTailUrl("/traffic/popular/referrers")) - .fetch(GHRepositoryTrafficTopReferralSources[].class)); + public void star() throws IOException { + root().createRequest().method("PUT").withUrlPath(String.format("/user/starred/%s", fullName)).send(); } /** - * Get all active rules that apply to the specified branch - * (https://docs.github.com/en/rest/repos/rules?apiVersion=2022-11-28#get-rules-for-a-branch). + * Subscribes to this repository to get notifications. * - * @param branch - * the branch - * @return the rules for branch + * @param subscribed + * the subscribed + * @param ignored + * the ignored + * @return the gh subscription + * @throws IOException + * the io exception */ - public PagedIterable listRulesForBranch(String branch) { + public GHSubscription subscribe(boolean subscribed, boolean ignored) throws IOException { return root().createRequest() - .method("GET") - .withUrlPath(getApiTailUrl("/rules/branches/" + branch)) - .toIterable(GHRepositoryRule[].class, null); + .method("PUT") + .with("subscribed", subscribed) + .with("ignored", ignored) + .withUrlPath(getApiTailUrl("subscription")) + .fetch(GHSubscription.class) + .wrapUp(this); } /** - * Check, if vulnerability alerts are enabled for this repository - * (https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#check-if-vulnerability-alerts-are-enabled-for-a-repository). + * Sync this repository fork branch * - * @return true, if vulnerability alerts are enabled + * @param branch + * the branch to sync + * @return The current repository * @throws IOException * the io exception */ - public boolean isVulnerabilityAlertsEnabled() throws IOException { + public GHBranchSync sync(String branch) throws IOException { return root().createRequest() - .method("GET") - .withUrlPath(getApiTailUrl("/vulnerability-alerts")) - .fetchHttpStatusCode() == 204; + .method("POST") + .with("branch", branch) + .withUrlPath(getApiTailUrl("merge-upstream")) + .fetch(GHBranchSync.class) + .wrap(this); } /** - * A {@link GHRepositoryBuilder} that allows multiple properties to be updated per request. + * Unstar a repository. * - * Consumer must call {@link #done()} to commit changes. + * @throws IOException + * the io exception */ - @BetaApi - public static class Setter extends GHRepositoryBuilder { - - /** - * Instantiates a new setter. - * - * @param repository - * the repository - */ - protected Setter(@Nonnull GHRepository repository) { - super(GHRepository.class, repository.root(), null); - // even when we don't change the name, we need to send it in - // this requirement may be out-of-date, but we do not want to break it - requester.with("name", repository.name); - - requester.method("PATCH").withUrlPath(repository.getApiTailUrl("")); - } + public void unstar() throws IOException { + root().createRequest().method("DELETE").withUrlPath(String.format("/user/starred/%s", fullName)).send(); } /** - * Create an autolink gh autolink builder. + * Creates a builder that can be used to bulk update repository settings. * - * @return the gh autolink builder + * @return the repository updater */ - public GHAutolinkBuilder createAutolink() { - return new GHAutolinkBuilder(this); + public Updater update() { + return new Updater(this); } /** - * List all autolinks of a repo (admin only). - * (https://docs.github.com/en/rest/repos/autolinks?apiVersion=2022-11-28#get-all-autolinks-of-a-repository) + * Updates an existing check run. * - * @return all autolinks in the repo + * @param checkId + * the existing checkId + * @return a builder which you should customize, then call {@link GHCheckRunBuilder#create} */ - public PagedIterable listAutolinks() { - return root().createRequest() - .withHeader("Accept", "application/vnd.github+json") - .withUrlPath(String.format("/repos/%s/%s/autolinks", getOwnerName(), getName())) - .toIterable(GHAutolink[].class, item -> item.lateBind(this)); + public @NonNull GHCheckRunBuilder updateCheckRun(long checkId) { + return new GHCheckRunBuilder(this, checkId); } - /** - * Read an autolink by ID. - * (https://docs.github.com/en/rest/repos/autolinks?apiVersion=2022-11-28#get-an-autolink-reference-of-a-repository) - * - * @param autolinkId - * the autolink id - * @return the autolink - * @throws IOException - * the io exception - */ - public GHAutolink readAutolink(int autolinkId) throws IOException { - return root().createRequest() - .withHeader("Accept", "application/vnd.github+json") - .withUrlPath(String.format("/repos/%s/%s/autolinks/%d", getOwnerName(), getName(), autolinkId)) - .fetch(GHAutolink.class) - .lateBind(this); + private T downloadArchive(@Nonnull String type, + @CheckForNull String ref, + @Nonnull InputStreamFunction streamFunction) throws IOException { + requireNonNull(streamFunction, "Sink must not be null"); + String tailUrl = getApiTailUrl(type + "ball"); + if (ref != null) { + tailUrl += "/" + ref; + } + final Requester builder = root().createRequest().method("GET").withUrlPath(tailUrl); + return builder.fetchStream(streamFunction); + } + + private GHContentWithLicense getLicenseContent_() throws IOException { + try { + return root().createRequest() + .withUrlPath(getApiTailUrl("license")) + .fetch(GHContentWithLicense.class) + .wrap(this); + } catch (FileNotFoundException e) { + return null; + } + } + + private PagedIterable listUsers(Requester requester, final String suffix) { + return requester.withUrlPath(getApiTailUrl(suffix)).toIterable(GHUser[].class, null); + } + + private PagedIterable listUsers(final String suffix) { + return listUsers(root().createRequest(), suffix); + } + + private void modifyCollaborators(@NonNull Collection users, + @NonNull String method, + @CheckForNull GHOrganization.RepositoryRole permission) throws IOException { + Requester requester = root().createRequest().method(method); + if (permission != null) { + requester = requester.with("permission", permission.toString()).inBody(); + } + + // Make sure that the users collection doesn't have any duplicates + for (GHUser user : new LinkedHashSet<>(users)) { + requester.withUrlPath(getApiTailUrl("collaborators/" + user.getLogin())).send(); + } } /** - * Delete autolink. - * (https://docs.github.com/en/rest/repos/autolinks?apiVersion=2022-11-28#delete-an-autolink-reference-from-a-repository) + * Gets the api tail url. * - * @param autolinkId - * the autolink id - * @throws IOException - * the io exception + * @param tail + * the tail + * @return the api tail url */ - public void deleteAutolink(int autolinkId) throws IOException { - root().createRequest() - .method("DELETE") - .withHeader("Accept", "application/vnd.github+json") - .withUrlPath(String.format("/repos/%s/%s/autolinks/%d", getOwnerName(), getName(), autolinkId)) - .send(); + String getApiTailUrl(String tail) { + if (!tail.isEmpty() && !tail.startsWith("/")) { + tail = '/' + tail; + } + return "/repos/" + fullName + tail; } /** - * Create fork gh repository fork builder. - * (https://docs.github.com/en/rest/repos/forks?apiVersion=2022-11-28#create-a-fork) + * Populate this object. * - * @return the gh repository fork builder + * @throws IOException + * Signals that an I/O exception has occurred. */ - public GHRepositoryForkBuilder createFork() { - return new GHRepositoryForkBuilder(this); + void populate() throws IOException { + if (isOffline()) { + return; // can't populate if the root is offline + } + + // We don't use the URL provided in the JSON because it is not reliable: + // 1. There is bug in Push event payloads that returns the wrong url. + // For Push event repository records, they take the form + // "https://github.com/{fullName}". + // All other occurrences of "url" take the form "https://api.github.com/...". + // 2. For Installation event payloads, the URL is not provided at all. + root().createRequest().withUrlPath(getApiTailUrl("")).fetchInto(this); } } diff --git a/src/main/java/org/kohsuke/github/GHRepositoryBuilder.java b/src/main/java/org/kohsuke/github/GHRepositoryBuilder.java index d32dbd2563..02bcba2d1d 100644 --- a/src/main/java/org/kohsuke/github/GHRepositoryBuilder.java +++ b/src/main/java/org/kohsuke/github/GHRepositoryBuilder.java @@ -29,18 +29,16 @@ protected GHRepositoryBuilder(Class intermediateReturnType, GitHub root, GHRe } /** - * Allow or disallow squash-merging pull requests. + * Allow or disallow private forks * * @param enabled * true if enabled - * * @return a builder to continue with building - * * @throws IOException * In case of any networking error or error from the server. */ - public S allowSquashMerge(boolean enabled) throws IOException { - return with("allow_squash_merge", enabled); + public S allowForking(boolean enabled) throws IOException { + return with("allow_forking", enabled); } /** @@ -74,44 +72,46 @@ public S allowRebaseMerge(boolean enabled) throws IOException { } /** - * Allow or disallow private forks + * Allow or disallow squash-merging pull requests. * * @param enabled * true if enabled + * * @return a builder to continue with building + * * @throws IOException * In case of any networking error or error from the server. */ - public S allowForking(boolean enabled) throws IOException { - return with("allow_forking", enabled); + public S allowSquashMerge(boolean enabled) throws IOException { + return with("allow_squash_merge", enabled); } /** - * After pull requests are merged, you can have head branches deleted automatically. - * - * @param enabled - * true if enabled + * Default repository branch. * + * @param branch + * branch name * @return a builder to continue with building - * * @throws IOException * In case of any networking error or error from the server. */ - public S deleteBranchOnMerge(boolean enabled) throws IOException { - return with("delete_branch_on_merge", enabled); + public S defaultBranch(String branch) throws IOException { + return with("default_branch", branch); } /** - * Default repository branch. + * After pull requests are merged, you can have head branches deleted automatically. + * + * @param enabled + * true if enabled * - * @param branch - * branch name * @return a builder to continue with building + * * @throws IOException * In case of any networking error or error from the server. */ - public S defaultBranch(String branch) throws IOException { - return with("default_branch", branch); + public S deleteBranchOnMerge(boolean enabled) throws IOException { + return with("delete_branch_on_merge", enabled); } /** @@ -128,16 +128,28 @@ public S description(String description) throws IOException { } /** - * Homepage for repository. + * Done. * - * @param homepage - * homepage of repository + * @return the GH repository + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Override + public GHRepository done() throws IOException { + return super.done(); + } + + /** + * Enables downloads. + * + * @param enabled + * true if enabled * @return a builder to continue with building * @throws IOException * In case of any networking error or error from the server. */ - public S homepage(URL homepage) throws IOException { - return homepage(homepage.toExternalForm()); + public S downloads(boolean enabled) throws IOException { + return with("has_downloads", enabled); } /** @@ -154,29 +166,29 @@ public S homepage(String homepage) throws IOException { } /** - * Sets the repository to private. + * Homepage for repository. * - * @param enabled - * private if true + * @param homepage + * homepage of repository * @return a builder to continue with building * @throws IOException * In case of any networking error or error from the server. */ - public S private_(boolean enabled) throws IOException { - return with("private", enabled); + public S homepage(URL homepage) throws IOException { + return homepage(homepage.toExternalForm()); } /** - * Sets the repository visibility. + * Specifies whether the repository is a template. * - * @param visibility - * visibility of repository + * @param enabled + * true if enabled * @return a builder to continue with building * @throws IOException * In case of any networking error or error from the server. */ - public S visibility(final Visibility visibility) throws IOException { - return with("visibility", visibility.toString()); + public S isTemplate(boolean enabled) throws IOException { + return with("is_template", enabled); } /** @@ -193,20 +205,20 @@ public S issues(boolean enabled) throws IOException { } /** - * Enables projects. + * Sets the repository to private. * * @param enabled - * true if enabled + * private if true * @return a builder to continue with building * @throws IOException * In case of any networking error or error from the server. */ - public S projects(boolean enabled) throws IOException { - return with("has_projects", enabled); + public S private_(boolean enabled) throws IOException { + return with("private", enabled); } /** - * Enables wiki. + * Enables projects. * * @param enabled * true if enabled @@ -214,25 +226,25 @@ public S projects(boolean enabled) throws IOException { * @throws IOException * In case of any networking error or error from the server. */ - public S wiki(boolean enabled) throws IOException { - return with("has_wiki", enabled); + public S projects(boolean enabled) throws IOException { + return with("has_projects", enabled); } /** - * Enables downloads. + * Sets the repository visibility. * - * @param enabled - * true if enabled + * @param visibility + * visibility of repository * @return a builder to continue with building * @throws IOException * In case of any networking error or error from the server. */ - public S downloads(boolean enabled) throws IOException { - return with("has_downloads", enabled); + public S visibility(final Visibility visibility) throws IOException { + return with("visibility", visibility.toString()); } /** - * Specifies whether the repository is a template. + * Enables wiki. * * @param enabled * true if enabled @@ -240,20 +252,8 @@ public S downloads(boolean enabled) throws IOException { * @throws IOException * In case of any networking error or error from the server. */ - public S isTemplate(boolean enabled) throws IOException { - return with("is_template", enabled); - } - - /** - * Done. - * - * @return the GH repository - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Override - public GHRepository done() throws IOException { - return super.done(); + public S wiki(boolean enabled) throws IOException { + return with("has_wiki", enabled); } /** diff --git a/src/main/java/org/kohsuke/github/GHRepositoryChanges.java b/src/main/java/org/kohsuke/github/GHRepositoryChanges.java index c3792f1819..c640ba2dc7 100644 --- a/src/main/java/org/kohsuke/github/GHRepositoryChanges.java +++ b/src/main/java/org/kohsuke/github/GHRepositoryChanges.java @@ -9,42 +9,24 @@ public class GHRepositoryChanges { /** - * Create default GHRepositoryChanges instance - */ - public GHRepositoryChanges() { - } - - private FromRepository repository; - private Owner owner; - - /** - * Get outer owner object. - * - * @return Owner + * Repository name that was changed. */ - public Owner getOwner() { - return owner; - } + public static class FromName { - /** - * Outer object of owner from whom this repository was transferred. - */ - public static class Owner { + private String from; /** - * Create default Owner instance + * Create default FromName instance */ - public Owner() { + public FromName() { } - private FromOwner from; - /** - * Get in owner object. + * Get previous name of the repository before rename. * - * @return FromOwner + * @return String */ - public FromOwner getFrom() { + public String getFrom() { return from; } } @@ -54,58 +36,48 @@ public FromOwner getFrom() { */ public static class FromOwner { + private GHOrganization organization; + + private GHUser user; /** * Create default FromOwner instance */ public FromOwner() { } - private GHUser user; - private GHOrganization organization; - /** - * Get user from which this repository was transferred. + * Get organization from which this repository was transferred. * - * @return user + * @return GHOrganization */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHUser getUser() { - return user; + public GHOrganization getOrganization() { + return organization; } /** - * Get organization from which this repository was transferred. + * Get user from which this repository was transferred. * - * @return GHOrganization + * @return user */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHOrganization getOrganization() { - return organization; + public GHUser getUser() { + return user; } } - - /** - * Get repository. - * - * @return FromRepository - */ - public FromRepository getRepository() { - return repository; - } - /** * Repository object from which the name was changed. */ public static class FromRepository { + private FromName name; + /** * Create default FromRepository instance */ public FromRepository() { } - private FromName name; - /** * Get top level object for the previous name of the repository. * @@ -117,25 +89,53 @@ public FromName getName() { } /** - * Repository name that was changed. + * Outer object of owner from whom this repository was transferred. */ - public static class FromName { + public static class Owner { + + private FromOwner from; /** - * Create default FromName instance + * Create default Owner instance */ - public FromName() { + public Owner() { } - private String from; - /** - * Get previous name of the repository before rename. + * Get in owner object. * - * @return String + * @return FromOwner */ - public String getFrom() { + public FromOwner getFrom() { return from; } } + + private Owner owner; + + private FromRepository repository; + + /** + * Create default GHRepositoryChanges instance + */ + public GHRepositoryChanges() { + } + + /** + * Get outer owner object. + * + * @return Owner + */ + public Owner getOwner() { + return owner; + } + + /** + * Get repository. + * + * @return FromRepository + */ + public FromRepository getRepository() { + return repository; + } } diff --git a/src/main/java/org/kohsuke/github/GHRepositoryCloneTraffic.java b/src/main/java/org/kohsuke/github/GHRepositoryCloneTraffic.java index 6d7bf15140..356a6667b7 100644 --- a/src/main/java/org/kohsuke/github/GHRepositoryCloneTraffic.java +++ b/src/main/java/org/kohsuke/github/GHRepositoryCloneTraffic.java @@ -10,6 +10,32 @@ * @see GHRepository#getCloneTraffic() GHRepository#getCloneTraffic() */ public class GHRepositoryCloneTraffic extends GHRepositoryTraffic { + /** + * The type DailyInfo. + */ + public static class DailyInfo extends GHRepositoryTraffic.DailyInfo { + + /** + * Instantiates a new daily info. + */ + DailyInfo() { + } + + /** + * Instantiates a new daily info. + * + * @param timestamp + * the timestamp + * @param count + * the count + * @param uniques + * the uniques + */ + DailyInfo(String timestamp, int count, int uniques) { + super(timestamp, count, uniques); + } + } + private List clones; /** @@ -50,30 +76,4 @@ public List getClones() { public List getDailyInfo() { return getClones(); } - - /** - * The type DailyInfo. - */ - public static class DailyInfo extends GHRepositoryTraffic.DailyInfo { - - /** - * Instantiates a new daily info. - */ - DailyInfo() { - } - - /** - * Instantiates a new daily info. - * - * @param timestamp - * the timestamp - * @param count - * the count - * @param uniques - * the uniques - */ - DailyInfo(String timestamp, int count, int uniques) { - super(timestamp, count, uniques); - } - } } diff --git a/src/main/java/org/kohsuke/github/GHRepositoryDiscussion.java b/src/main/java/org/kohsuke/github/GHRepositoryDiscussion.java index a04f234497..f319d1f7cd 100644 --- a/src/main/java/org/kohsuke/github/GHRepositoryDiscussion.java +++ b/src/main/java/org/kohsuke/github/GHRepositoryDiscussion.java @@ -1,8 +1,10 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import org.kohsuke.github.internal.EnumUtils; import java.net.URL; +import java.time.Instant; import java.util.Date; // TODO: Auto-generated Javadoc @@ -22,46 +24,172 @@ public class GHRepositoryDiscussion extends GHObject { /** - * Create default GHRepositoryDiscussion instance + * Category of a discussion. + *

+ * Note that while it is relatively close to the GraphQL objects, some of the fields such as the id are handled + * differently. + * + * @see The + * GraphQL API for Discussions */ - public GHRepositoryDiscussion() { + public static class Category extends GitHubBridgeAdapterObject { + + private String createdAt; + + private String description; + private String emoji; + private long id; + private boolean isAnswerable; + private String name; + private String nodeId; + private long repositoryId; + private String slug; + private String updatedAt; + /** + * Create default Category instance + */ + public Category() { + } + + /** + * Gets the created at. + * + * @return the created at + */ + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getCreatedAt() { + return GitHubClient.parseInstant(createdAt); + } + + /** + * Gets the description. + * + * @return the description + */ + public String getDescription() { + return description; + } + + /** + * Gets the emoji. + * + * @return the emoji + */ + public String getEmoji() { + return emoji; + } + + /** + * Gets the id. + * + * @return the id + */ + public long getId() { + return id; + } + + /** + * Gets the name. + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * Gets the node id. + * + * @return the node id + */ + public String getNodeId() { + return nodeId; + } + + /** + * Gets the repository id. + * + * @return the repository id + */ + public long getRepositoryId() { + return repositoryId; + } + + /** + * Gets the slug. + * + * @return the slug + */ + public String getSlug() { + return slug; + } + + /** + * Gets the updated at. + * + * @return the updated at + */ + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getUpdatedAt() { + return GitHubClient.parseInstant(updatedAt); + } + + /** + * Checks if is answerable. + * + * @return true, if is answerable + */ + public boolean isAnswerable() { + return isAnswerable; + } } - private Category category; + /** + * The Enum State. + */ + public enum State { - private String answerHtmlUrl; + /** The locked. */ + LOCKED, + /** The open. */ + OPEN, + /** The unknown. */ + UNKNOWN; + } + + private String activeLockReason; private String answerChosenAt; private GHUser answerChosenBy; - private String htmlUrl; + private String answerHtmlUrl; - private int number; - private String title; - private GHUser user; - private String state; - private boolean locked; - private int comments; private GHCommentAuthorAssociation authorAssociation; - private String activeLockReason; private String body; + private Category category; + private int comments; + private String htmlUrl; + private boolean locked; + private int number; + private String state; private String timelineUrl; + private String title; + + private GHUser user; /** - * Gets the category. - * - * @return the category + * Create default GHRepositoryDiscussion instance */ - public Category getCategory() { - return category; + public GHRepositoryDiscussion() { } /** - * Gets the answer html url. + * Gets the active lock reason. * - * @return the answer html url + * @return the active lock reason */ - public URL getAnswerHtmlUrl() { - return GitHubClient.parseURL(answerHtmlUrl); + public String getActiveLockReason() { + return activeLockReason; } /** @@ -69,8 +197,9 @@ public URL getAnswerHtmlUrl() { * * @return the answer chosen at */ - public Date getAnswerChosenAt() { - return GitHubClient.parseDate(answerChosenAt); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getAnswerChosenAt() { + return GitHubClient.parseInstant(answerChosenAt); } /** @@ -83,57 +212,39 @@ public GHUser getAnswerChosenBy() { } /** - * Gets the html url. - * - * @return the html url - */ - public URL getHtmlUrl() { - return GitHubClient.parseURL(htmlUrl); - } - - /** - * Gets the number. - * - * @return the number - */ - public int getNumber() { - return number; - } - - /** - * Gets the title. + * Gets the answer html url. * - * @return the title + * @return the answer html url */ - public String getTitle() { - return title; + public URL getAnswerHtmlUrl() { + return GitHubClient.parseURL(answerHtmlUrl); } /** - * Gets the user. + * Gets the author association. * - * @return the user + * @return the author association */ - public GHUser getUser() { - return root().intern(user); + public GHCommentAuthorAssociation getAuthorAssociation() { + return authorAssociation; } /** - * Gets the state. + * Gets the body. * - * @return the state + * @return the body */ - public State getState() { - return EnumUtils.getEnumOrDefault(State.class, state, State.UNKNOWN); + public String getBody() { + return body; } /** - * Checks if is locked. + * Gets the category. * - * @return true, if is locked + * @return the category */ - public boolean isLocked() { - return locked; + public Category getCategory() { + return category; } /** @@ -146,30 +257,30 @@ public int getComments() { } /** - * Gets the author association. + * Gets the html url. * - * @return the author association + * @return the html url */ - public GHCommentAuthorAssociation getAuthorAssociation() { - return authorAssociation; + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); } /** - * Gets the active lock reason. + * Gets the number. * - * @return the active lock reason + * @return the number */ - public String getActiveLockReason() { - return activeLockReason; + public int getNumber() { + return number; } /** - * Gets the body. + * Gets the state. * - * @return the body + * @return the state */ - public String getBody() { - return body; + public State getState() { + return EnumUtils.getEnumOrDefault(State.class, state, State.UNKNOWN); } /** @@ -182,135 +293,29 @@ public String getTimelineUrl() { } /** - * Category of a discussion. - *

- * Note that while it is relatively close to the GraphQL objects, some of the fields such as the id are handled - * differently. + * Gets the title. * - * @see The - * GraphQL API for Discussions + * @return the title */ - public static class Category { - - /** - * Create default Category instance - */ - public Category() { - } - - private long id; - private String nodeId; - private long repositoryId; - private String emoji; - private String name; - private String description; - private String createdAt; - private String updatedAt; - private String slug; - private boolean isAnswerable; - - /** - * Gets the id. - * - * @return the id - */ - public long getId() { - return id; - } - - /** - * Gets the node id. - * - * @return the node id - */ - public String getNodeId() { - return nodeId; - } - - /** - * Gets the repository id. - * - * @return the repository id - */ - public long getRepositoryId() { - return repositoryId; - } - - /** - * Gets the emoji. - * - * @return the emoji - */ - public String getEmoji() { - return emoji; - } - - /** - * Gets the name. - * - * @return the name - */ - public String getName() { - return name; - } - - /** - * Gets the description. - * - * @return the description - */ - public String getDescription() { - return description; - } - - /** - * Gets the created at. - * - * @return the created at - */ - public Date getCreatedAt() { - return GitHubClient.parseDate(createdAt); - } - - /** - * Gets the updated at. - * - * @return the updated at - */ - public Date getUpdatedAt() { - return GitHubClient.parseDate(updatedAt); - } - - /** - * Gets the slug. - * - * @return the slug - */ - public String getSlug() { - return slug; - } - - /** - * Checks if is answerable. - * - * @return true, if is answerable - */ - public boolean isAnswerable() { - return isAnswerable; - } + public String getTitle() { + return title; } /** - * The Enum State. + * Gets the user. + * + * @return the user */ - public enum State { + public GHUser getUser() { + return root().intern(user); + } - /** The open. */ - OPEN, - /** The locked. */ - LOCKED, - /** The unknown. */ - UNKNOWN; + /** + * Checks if is locked. + * + * @return true, if is locked + */ + public boolean isLocked() { + return locked; } } diff --git a/src/main/java/org/kohsuke/github/GHRepositoryDiscussionComment.java b/src/main/java/org/kohsuke/github/GHRepositoryDiscussionComment.java index 327eb036ca..b951491149 100644 --- a/src/main/java/org/kohsuke/github/GHRepositoryDiscussionComment.java +++ b/src/main/java/org/kohsuke/github/GHRepositoryDiscussionComment.java @@ -17,37 +17,37 @@ */ public class GHRepositoryDiscussionComment extends GHObject { - /** - * Create default GHRepositoryDiscussionComment instance - */ - public GHRepositoryDiscussionComment() { - } + private GHCommentAuthorAssociation authorAssociation; - private String htmlUrl; + private String body; - private Long parentId; private int childCommentCount; + private String htmlUrl; + private Long parentId; private GHUser user; - private GHCommentAuthorAssociation authorAssociation; - private String body; + /** + * Create default GHRepositoryDiscussionComment instance + */ + public GHRepositoryDiscussionComment() { + } /** - * Gets the html url. + * Gets the author association. * - * @return the html url + * @return the author association */ - public URL getHtmlUrl() { - return GitHubClient.parseURL(htmlUrl); + public GHCommentAuthorAssociation getAuthorAssociation() { + return authorAssociation; } /** - * Gets the parent comment id. + * Gets the body. * - * @return the parent comment id + * @return the body */ - public Long getParentId() { - return parentId; + public String getBody() { + return body; } /** @@ -60,29 +60,29 @@ public int getChildCommentCount() { } /** - * Gets the user. + * Gets the html url. * - * @return the user + * @return the html url */ - public GHUser getUser() { - return root().intern(user); + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); } /** - * Gets the author association. + * Gets the parent comment id. * - * @return the author association + * @return the parent comment id */ - public GHCommentAuthorAssociation getAuthorAssociation() { - return authorAssociation; + public Long getParentId() { + return parentId; } /** - * Gets the body. + * Gets the user. * - * @return the body + * @return the user */ - public String getBody() { - return body; + public GHUser getUser() { + return root().intern(user); } } diff --git a/src/main/java/org/kohsuke/github/GHRepositoryForkBuilder.java b/src/main/java/org/kohsuke/github/GHRepositoryForkBuilder.java index cb75c0561d..8d8d5db4c5 100644 --- a/src/main/java/org/kohsuke/github/GHRepositoryForkBuilder.java +++ b/src/main/java/org/kohsuke/github/GHRepositoryForkBuilder.java @@ -10,13 +10,13 @@ * @see Repository fork API */ public class GHRepositoryForkBuilder { - private final GHRepository repo; - private final Requester req; - private String organization; - private String name; + static int FORK_RETRY_INTERVAL = 3000; private Boolean defaultBranchOnly; + private String name; + private String organization; + private final GHRepository repo; - static int FORK_RETRY_INTERVAL = 3000; + private final Requester req; /** * Instantiates a new Gh repository fork builder. @@ -29,42 +29,6 @@ public class GHRepositoryForkBuilder { this.req = repo.root().createRequest(); } - /** - * Sets whether to fork only the default branch. - * - * @param defaultBranchOnly - * the default branch only - * @return the gh repository fork builder - */ - public GHRepositoryForkBuilder defaultBranchOnly(boolean defaultBranchOnly) { - this.defaultBranchOnly = defaultBranchOnly; - return this; - } - - /** - * Specifies the target organization for the fork. - * - * @param organization - * the organization - * @return the gh repository fork builder - */ - public GHRepositoryForkBuilder organization(GHOrganization organization) { - this.organization = organization.getLogin(); - return this; - } - - /** - * Sets a custom name for the forked repository. - * - * @param name - * the desired repository name - * @return the builder - */ - public GHRepositoryForkBuilder name(String name) { - this.name = name; - return this; - } - /** * Creates the fork with the specified parameters. * @@ -96,29 +60,49 @@ public GHRepository create() throws IOException { throw new IOException(createTimeoutMessage()); } - private GHRepository lookupForkedRepository() throws IOException { - String repoName = name != null ? name : repo.getName(); + /** + * Sets whether to fork only the default branch. + * + * @param defaultBranchOnly + * the default branch only + * @return the gh repository fork builder + */ + public GHRepositoryForkBuilder defaultBranchOnly(boolean defaultBranchOnly) { + this.defaultBranchOnly = defaultBranchOnly; + return this; + } - if (organization != null) { - return repo.root().getOrganization(organization).getRepository(repoName); - } - return repo.root().getMyself().getRepository(repoName); + /** + * Sets a custom name for the forked repository. + * + * @param name + * the desired repository name + * @return the builder + */ + public GHRepositoryForkBuilder name(String name) { + this.name = name; + return this; } /** - * Sleep. + * Specifies the target organization for the fork. * - * @param millis - * the millis - * @throws IOException - * the io exception + * @param organization + * the organization + * @return the gh repository fork builder */ - void sleep(int millis) throws IOException { - try { - Thread.sleep(millis); - } catch (InterruptedException e) { - throw (IOException) new InterruptedIOException().initCause(e); + public GHRepositoryForkBuilder organization(GHOrganization organization) { + this.organization = organization.getLogin(); + return this; + } + + private GHRepository lookupForkedRepository() throws IOException { + String repoName = name != null ? name : repo.getName(); + + if (organization != null) { + return repo.root().getOrganization(organization).getRepository(repoName); } + return repo.root().getMyself().getRepository(repoName); } /** @@ -141,4 +125,20 @@ String createTimeoutMessage() { message.append(" but can't find the new repository"); return message.toString(); } + + /** + * Sleep. + * + * @param millis + * the millis + * @throws IOException + * the io exception + */ + void sleep(int millis) throws IOException { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + throw (IOException) new InterruptedIOException().initCause(e); + } + } } diff --git a/src/main/java/org/kohsuke/github/GHRepositoryPublicKey.java b/src/main/java/org/kohsuke/github/GHRepositoryPublicKey.java index 843e67e872..a788907a53 100644 --- a/src/main/java/org/kohsuke/github/GHRepositoryPublicKey.java +++ b/src/main/java/org/kohsuke/github/GHRepositoryPublicKey.java @@ -10,26 +10,17 @@ */ public class GHRepositoryPublicKey extends GHObject { - /** - * Create default GHRepositoryPublicKey instance - */ - public GHRepositoryPublicKey() { - } + private String key; + + private String keyId; // Not provided by the API. @JsonIgnore private GHRepository owner; - - private String keyId; - private String key; - /** - * Gets the key id. - * - * @return the key id + * Create default GHRepositoryPublicKey instance */ - public String getKeyId() { - return keyId; + public GHRepositoryPublicKey() { } /** @@ -41,6 +32,15 @@ public String getKey() { return key; } + /** + * Gets the key id. + * + * @return the key id + */ + public String getKeyId() { + return keyId; + } + /** * Wrap up. * diff --git a/src/main/java/org/kohsuke/github/GHRepositoryRule.java b/src/main/java/org/kohsuke/github/GHRepositoryRule.java index 2db05c7057..d856b82b79 100644 --- a/src/main/java/org/kohsuke/github/GHRepositoryRule.java +++ b/src/main/java/org/kohsuke/github/GHRepositoryRule.java @@ -1,7 +1,7 @@ package org.kohsuke.github; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectReader; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.kohsuke.github.internal.EnumUtils; @@ -13,188 +13,226 @@ /** * Represents a repository rule. */ -@SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, +@SuppressFBWarnings( + value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD", + "CT_CONSTRUCTOR_THROW" }, justification = "JSON API") public class GHRepositoryRule extends GitHubInteractiveObject { /** - * Create default GHRepositoryRule instance - */ - public GHRepositoryRule() { - } - - private String type; - private String rulesetSourceType; - private String rulesetSource; - private long rulesetId; - private Map parameters; - - /** - * Gets the type. - * - * @return the type - */ - public Type getType() { - return EnumUtils.getEnumOrDefault(Type.class, this.type, Type.UNKNOWN); - } - - /** - * Gets the ruleset source type. - * - * @return the ruleset source type - */ - public RulesetSourceType getRulesetSourceType() { - return EnumUtils.getEnumOrDefault(RulesetSourceType.class, this.rulesetSourceType, RulesetSourceType.UNKNOWN); - } - - /** - * Gets the ruleset source. - * - * @return the ruleset source - */ - public String getRulesetSource() { - return this.rulesetSource; - } - - /** - * Gets the ruleset id. - * - * @return the ruleset id - */ - public long getRulesetId() { - return this.rulesetId; - } - - /** - * Gets a parameter. ({@link GHRepositoryRule.Parameters Parameters} provides a list of available parameters.) - * - * @param parameter - * the parameter - * @param - * the type of the parameter - * @return the parameters - * @throws IOException - * if an I/O error occurs - */ - public Optional getParameter(Parameter parameter) throws IOException { - if (this.parameters == null) { - return Optional.empty(); - } - JsonNode jsonNode = this.parameters.get(parameter.getKey()); - if (jsonNode == null) { - return Optional.empty(); - } - return Optional.ofNullable(parameter.apply(jsonNode, root())); - } - - /** - * The type of the ruleset. + * Alerts threshold parameter. */ - public static enum Type { + public static enum AlertsThreshold { /** - * unknown + * all */ - UNKNOWN, + ALL, /** - * creation + * errors */ - CREATION, + ERRORS, /** - * update + * errors_and_warnings */ - UPDATE, + ERRORS_AND_WARNINGS, /** - * deletion + * none */ - DELETION, + NONE + } + /** + * Boolean parameter for a ruleset. + */ + public static class BooleanParameter extends Parameter { /** - * required_linear_history + * Instantiates a new boolean parameter. + * + * @param key + * the key */ - REQUIRED_LINEAR_HISTORY, + public BooleanParameter(String key) { + super(key, Boolean.class); + } + } + /** + * Code scanning tool parameter. + */ + public static class CodeScanningTool { - /** - * required_deployments - */ - REQUIRED_DEPLOYMENTS, + private AlertsThreshold alertsThreshold; + private SecurityAlertsThreshold securityAlertsThreshold; + private String tool; /** - * required_signatures + * Create default CodeScanningTool instance */ - REQUIRED_SIGNATURES, + public CodeScanningTool() { + } /** - * pull_request + * Gets the alerts threshold. + * + * @return the alerts threshold */ - PULL_REQUEST, + public AlertsThreshold getAlertsThreshold() { + return this.alertsThreshold; + } /** - * required_status_checks + * Gets the security alerts threshold. + * + * @return the security alerts threshold */ - REQUIRED_STATUS_CHECKS, + public SecurityAlertsThreshold getSecurityAlertsThreshold() { + return this.securityAlertsThreshold; + } /** - * non_fast_forward + * Gets the tool. + * + * @return the tool */ - NON_FAST_FORWARD, - + public String getTool() { + return this.tool; + } + } + /** + * Integer parameter for a ruleset. + */ + public static class IntegerParameter extends Parameter { /** - * commit_message_pattern + * Instantiates a new integer parameter. + * + * @param key + * the key */ - COMMIT_MESSAGE_PATTERN, + public IntegerParameter(String key) { + super(key, Integer.class); + } + } + /** + * List parameter for a ruleset. + * + * @param + * the type of the items in the list + */ + public abstract static class ListParameter extends Parameter> { + + private final Class itemClass; /** - * commit_author_email_pattern + * Instantiates a new list parameter. + * + * @param key + * the key */ - COMMIT_AUTHOR_EMAIL_PATTERN, + public ListParameter(String key) { + super(key, null); + throw new GHException("This constructor should not have been public."); + } /** - * committer_email_pattern + * Instantiates a new list parameter. + * + * @param key + * the key + * @param itemClass + * the class of items in the list parameter */ - COMMITTER_EMAIL_PATTERN, + ListParameter(String key, Class itemClass) { + super(key, null); + this.itemClass = itemClass; + } + @Override + List apply(String value, GitHub root) throws IOException { + if (value == null) { + return null; + } + ObjectReader objectReader = GitHubClient.getMappingObjectReader(root); + JavaType javaType = objectReader.getTypeFactory().constructParametricType(List.class, itemClass); + return objectReader.forType(javaType).readValue(value); + } + } + /** + * Operator parameter. + */ + public static enum Operator { /** - * branch_name_pattern + * contains */ - BRANCH_NAME_PATTERN, + CONTAINS, /** - * tag_name_pattern + * ends_with */ - TAG_NAME_PATTERN, + ENDS_WITH, /** - * workflows + * regex */ - WORKFLOWS, + REGEX, /** - * code_scanning + * starts_with */ - CODE_SCANNING + STARTS_WITH } /** - * The source of the ruleset type. + * Basic parameter for a ruleset. + * + * @param + * the type of the parameter */ - public enum RulesetSourceType { + public abstract static class Parameter { + + private final Class clazz; + private final String key; + /** - * unknown + * Instantiates a new parameter. + * + * @param key + * the key */ - UNKNOWN, + protected Parameter(String key) { + throw new GHException("This constructor should not have been protected."); + } /** - * Repository + * Instantiates a new parameter. + * + * @param key + * the key + * @param clazz + * the class the the parameter */ - REPOSITORY, + Parameter(String key, Class clazz) { + this.key = key; + this.clazz = clazz; + } + + T apply(String value, GitHub root) throws IOException { + if (value == null) { + return null; + } + ObjectReader objectReader = GitHubClient.getMappingObjectReader(root); + return objectReader.forType(clazz).readValue(value); + } /** - * Organization + * Gets the key. + * + * @return the key */ - ORGANIZATION + String getKey() { + return this.key; + } } /** @@ -202,20 +240,11 @@ public enum RulesetSourceType { */ public interface Parameters { /** - * update_allows_fetch_and_merge parameter - */ - public static final BooleanParameter UPDATE_ALLOWS_FETCH_AND_MERGE = new BooleanParameter( - "update_allows_fetch_and_merge"); - /** - * required_deployment_environments parameter + * code_scanning_tools parameter */ - public static final ListParameter REQUIRED_DEPLOYMENT_ENVIRONMENTS = new ListParameter( - "required_deployment_environments") { - @Override - TypeReference> getType() { - return new TypeReference>() { - }; - } + public static final ListParameter CODE_SCANNING_TOOLS = new ListParameter( + "code_scanning_tools", + CodeScanningTool.class) { }; /** * dismiss_stale_reviews_on_push parameter @@ -223,20 +252,34 @@ TypeReference> getType() { public static final BooleanParameter DISMISS_STALE_REVIEWS_ON_PUSH = new BooleanParameter( "dismiss_stale_reviews_on_push"); /** - * require_code_owner_review parameter + * name parameter */ - public static final BooleanParameter REQUIRE_CODE_OWNER_REVIEW = new BooleanParameter( - "require_code_owner_review"); + public static final StringParameter NAME = new StringParameter("name"); /** - * require_last_push_approval parameter + * negate parameter */ - public static final BooleanParameter REQUIRE_LAST_PUSH_APPROVAL = new BooleanParameter( - "require_last_push_approval"); + public static final BooleanParameter NEGATE = new BooleanParameter("negate"); + /** + * operator parameter + */ + public static final Parameter OPERATOR = new Parameter("operator", Operator.class) { + }; + /** + * regex parameter + */ + public static final StringParameter REGEX = new StringParameter("regex"); /** * required_approving_review_count parameter */ public static final IntegerParameter REQUIRED_APPROVING_REVIEW_COUNT = new IntegerParameter( "required_approving_review_count"); + /** + * required_deployment_environments parameter + */ + public static final ListParameter REQUIRED_DEPLOYMENT_ENVIRONMENTS = new ListParameter( + "required_deployment_environments", + String.class) { + }; /** * required_review_thread_resolution parameter */ @@ -246,185 +289,86 @@ TypeReference> getType() { * required_status_checks parameter */ public static final ListParameter REQUIRED_STATUS_CHECKS = new ListParameter( - "required_status_checks") { - @Override - TypeReference> getType() { - return new TypeReference>() { - }; - } + "required_status_checks", + StatusCheckConfiguration.class) { }; /** - * strict_required_status_checks_policy parameter - */ - public static final BooleanParameter STRICT_REQUIRED_STATUS_CHECKS_POLICY = new BooleanParameter( - "strict_required_status_checks_policy"); - /** - * name parameter + * require_code_owner_review parameter */ - public static final StringParameter NAME = new StringParameter("name"); + public static final BooleanParameter REQUIRE_CODE_OWNER_REVIEW = new BooleanParameter( + "require_code_owner_review"); /** - * negate parameter + * require_last_push_approval parameter */ - public static final BooleanParameter NEGATE = new BooleanParameter("negate"); + public static final BooleanParameter REQUIRE_LAST_PUSH_APPROVAL = new BooleanParameter( + "require_last_push_approval"); /** - * operator parameter + * strict_required_status_checks_policy parameter */ - public static final Parameter OPERATOR = new Parameter("operator") { - @Override - TypeReference getType() { - return new TypeReference() { - }; - } - }; + public static final BooleanParameter STRICT_REQUIRED_STATUS_CHECKS_POLICY = new BooleanParameter( + "strict_required_status_checks_policy"); /** - * regex parameter + * update_allows_fetch_and_merge parameter */ - public static final StringParameter REGEX = new StringParameter("regex"); + public static final BooleanParameter UPDATE_ALLOWS_FETCH_AND_MERGE = new BooleanParameter( + "update_allows_fetch_and_merge"); /** * workflows parameter */ public static final ListParameter WORKFLOWS = new ListParameter( - "workflows") { - @Override - TypeReference> getType() { - return new TypeReference>() { - }; - } - }; - /** - * code_scanning_tools parameter - */ - public static final ListParameter CODE_SCANNING_TOOLS = new ListParameter( - "code_scanning_tools") { - @Override - TypeReference> getType() { - return new TypeReference>() { - }; - } + "workflows", + WorkflowFileReference.class) { }; } /** - * Basic parameter for a ruleset. - * - * @param - * the type of the parameter + * The source of the ruleset type. */ - public abstract static class Parameter { - - private final String key; - + public enum RulesetSourceType { /** - * Get the parameter type reference for type mapping. + * Organization */ - abstract TypeReference getType(); + ORGANIZATION, /** - * Instantiates a new parameter. - * - * @param key - * the key + * Repository */ - protected Parameter(String key) { - this.key = key; - } + REPOSITORY, /** - * Gets the key. - * - * @return the key + * unknown */ - String getKey() { - return this.key; - } - - T apply(JsonNode jsonNode, GitHub root) throws IOException { - if (jsonNode == null) { - return null; - } - return GitHubClient.getMappingObjectReader(root).forType(this.getType()).readValue(jsonNode); - } + UNKNOWN } /** - * String parameter for a ruleset. + * Security alerts threshold parameter. */ - public static class StringParameter extends Parameter { + public static enum SecurityAlertsThreshold { /** - * Instantiates a new string parameter. - * - * @param key - * the key + * all */ - public StringParameter(String key) { - super(key); - } - - @Override - TypeReference getType() { - return new TypeReference() { - }; - } - } + ALL, - /** - * Boolean parameter for a ruleset. - */ - public static class BooleanParameter extends Parameter { /** - * Instantiates a new boolean parameter. - * - * @param key - * the key + * critical */ - public BooleanParameter(String key) { - super(key); - } - - @Override - TypeReference getType() { - return new TypeReference() { - }; - } - } + CRITICAL, - /** - * Integer parameter for a ruleset. - */ - public static class IntegerParameter extends Parameter { /** - * Instantiates a new integer parameter. - * - * @param key - * the key + * high_or_higher */ - public IntegerParameter(String key) { - super(key); - } - - @Override - TypeReference getType() { - return new TypeReference() { - }; - } - } + HIGH_OR_HIGHER, - /** - * List parameter for a ruleset. - * - * @param - * the type of the list - */ - public abstract static class ListParameter extends Parameter> { /** - * Instantiates a new list parameter. - * - * @param key - * the key + * medium_or_higher */ - public ListParameter(String key) { - super(key); - } + MEDIUM_OR_HIGHER, + + /** + * none + */ + NONE } /** @@ -432,15 +376,15 @@ public ListParameter(String key) { */ public static class StatusCheckConfiguration { + private String context; + + private Integer integrationId; /** * Create default StatusCheckConfiguration instance */ public StatusCheckConfiguration() { } - private String context; - private Integer integrationId; - /** * Gets the context. * @@ -461,28 +405,108 @@ public Integer getIntegrationId() { } /** - * Operator parameter. + * String parameter for a ruleset. */ - public static enum Operator { + public static class StringParameter extends Parameter { /** - * starts_with + * Instantiates a new string parameter. + * + * @param key + * the key */ - STARTS_WITH, + public StringParameter(String key) { + super(key, String.class); + } + } + /** + * The type of the ruleset. + */ + public static enum Type { /** - * ends_with + * branch_name_pattern */ - ENDS_WITH, + BRANCH_NAME_PATTERN, /** - * contains + * code_scanning */ - CONTAINS, + CODE_SCANNING, /** - * regex + * committer_email_pattern + */ + COMMITTER_EMAIL_PATTERN, + + /** + * commit_author_email_pattern + */ + COMMIT_AUTHOR_EMAIL_PATTERN, + + /** + * commit_message_pattern + */ + COMMIT_MESSAGE_PATTERN, + + /** + * creation + */ + CREATION, + + /** + * deletion + */ + DELETION, + + /** + * non_fast_forward + */ + NON_FAST_FORWARD, + + /** + * pull_request + */ + PULL_REQUEST, + + /** + * required_deployments + */ + REQUIRED_DEPLOYMENTS, + + /** + * required_linear_history + */ + REQUIRED_LINEAR_HISTORY, + + /** + * required_signatures + */ + REQUIRED_SIGNATURES, + + /** + * required_status_checks + */ + REQUIRED_STATUS_CHECKS, + + /** + * tag_name_pattern + */ + TAG_NAME_PATTERN, + + /** + * unknown + */ + UNKNOWN, + + /** + * update + */ + UPDATE, + + /** + * workflows */ - REGEX + WORKFLOWS } /** @@ -490,17 +514,17 @@ public static enum Operator { */ public static class WorkflowFileReference { + private String path; + + private String ref; + private long repositoryId; + private String sha; /** * Create default WorkflowFileReference instance */ public WorkflowFileReference() { } - private String path; - private String ref; - private long repositoryId; - private String sha; - /** * Gets the path. * @@ -538,101 +562,78 @@ public String getSha() { } } - /** - * Code scanning tool parameter. - */ - public static class CodeScanningTool { + private Map parameters; - /** - * Create default CodeScanningTool instance - */ - public CodeScanningTool() { - } + private long rulesetId; - private AlertsThreshold alertsThreshold; - private SecurityAlertsThreshold securityAlertsThreshold; - private String tool; + private String rulesetSource; - /** - * Gets the alerts threshold. - * - * @return the alerts threshold - */ - public AlertsThreshold getAlertsThreshold() { - return this.alertsThreshold; - } + private String rulesetSourceType; - /** - * Gets the security alerts threshold. - * - * @return the security alerts threshold - */ - public SecurityAlertsThreshold getSecurityAlertsThreshold() { - return this.securityAlertsThreshold; - } + private String type; - /** - * Gets the tool. - * - * @return the tool - */ - public String getTool() { - return this.tool; - } + /** + * Create default GHRepositoryRule instance + */ + public GHRepositoryRule() { } /** - * Alerts threshold parameter. + * Gets a parameter. ({@link GHRepositoryRule.Parameters Parameters} provides a list of available parameters.) + * + * @param parameter + * the parameter + * @param + * the type of the parameter + * @return the parameters + * @throws IOException + * if an I/O error occurs */ - public static enum AlertsThreshold { - /** - * none - */ - NONE, - - /** - * errors - */ - ERRORS, - - /** - * errors_and_warnings - */ - ERRORS_AND_WARNINGS, - - /** - * all - */ - ALL + public Optional getParameter(Parameter parameter) throws IOException { + if (this.parameters == null) { + return Optional.empty(); + } + Object value = this.parameters.get(parameter.getKey()); + if (value == null) { + return Optional.empty(); + } + return Optional + .ofNullable(parameter.apply(GitHubClient.getMappingObjectWriter().writeValueAsString(value), root())); } /** - * Security alerts threshold parameter. + * Gets the ruleset id. + * + * @return the ruleset id */ - public static enum SecurityAlertsThreshold { - /** - * none - */ - NONE, - - /** - * critical - */ - CRITICAL, + public long getRulesetId() { + return this.rulesetId; + } - /** - * high_or_higher - */ - HIGH_OR_HIGHER, + /** + * Gets the ruleset source. + * + * @return the ruleset source + */ + public String getRulesetSource() { + return this.rulesetSource; + } - /** - * medium_or_higher - */ - MEDIUM_OR_HIGHER, + /** + * Gets the ruleset source type. + * + * @return the ruleset source type + */ + public RulesetSourceType getRulesetSourceType() { + return EnumUtils.getEnumOrDefault(RulesetSourceType.class, this.rulesetSourceType, RulesetSourceType.UNKNOWN); + } - /** - * all - */ - ALL + /** + * Gets the type. + * + * @return the type + */ + public Type getType() { + return EnumUtils.getEnumOrDefault(Type.class, this.type, Type.UNKNOWN); } } diff --git a/src/main/java/org/kohsuke/github/GHRepositorySearchBuilder.java b/src/main/java/org/kohsuke/github/GHRepositorySearchBuilder.java index c0e3220911..9e600ec927 100644 --- a/src/main/java/org/kohsuke/github/GHRepositorySearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHRepositorySearchBuilder.java @@ -12,53 +12,51 @@ public class GHRepositorySearchBuilder extends GHSearchBuilder { /** - * Instantiates a new GH repository search builder. - * - * @param root - * the root + * The enum Sort. */ - GHRepositorySearchBuilder(GitHub root) { - super(root, RepositorySearchResult.class); - } + public enum Sort { - /** - * {@inheritDoc} - */ - @Override - public GHRepositorySearchBuilder q(String term) { - super.q(term); - return this; + /** The forks. */ + FORKS, + /** The stars. */ + STARS, + /** The updated. */ + UPDATED } - /** - * {@inheritDoc} - */ - @Override - GHRepositorySearchBuilder q(String qualifier, String value) { - super.q(qualifier, value); - return this; + @SuppressFBWarnings( + value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, + justification = "JSON API") + private static class RepositorySearchResult extends SearchResult { + private GHRepository[] items; + + @Override + GHRepository[] getItems(GitHub root) { + for (GHRepository item : items) { + } + return items; + } } /** - * In gh repository search builder. + * Instantiates a new GH repository search builder. * - * @param v - * the v - * @return the gh repository search builder + * @param root + * the root */ - public GHRepositorySearchBuilder in(String v) { - return q("in:" + v); + GHRepositorySearchBuilder(GitHub root) { + super(root, RepositorySearchResult.class); } /** - * Size gh repository search builder. + * Created gh repository search builder. * * @param v * the v * @return the gh repository search builder */ - public GHRepositorySearchBuilder size(String v) { - return q("size:" + v); + public GHRepositorySearchBuilder created(String v) { + return q("created:" + v); } /** @@ -90,164 +88,157 @@ public GHRepositorySearchBuilder fork(GHFork fork) { } /** - * Search by repository visibility. + * In gh repository search builder. * - * @param visibility - * repository visibility + * @param v + * the v * @return the gh repository search builder - * @throws GHException - * if {@link GHRepository.Visibility#UNKNOWN} is passed. UNKNOWN is a placeholder for unexpected values - * encountered when reading data. - * @see Search - * by repository visibility */ - public GHRepositorySearchBuilder visibility(GHRepository.Visibility visibility) { - if (visibility == GHRepository.Visibility.UNKNOWN) { - throw new GHException( - "UNKNOWN is a placeholder for unexpected values encountered when reading data. It cannot be passed as a search parameter."); - } - - return q("is:" + visibility); + public GHRepositorySearchBuilder in(String v) { + return q("in:" + v); } /** - * Created gh repository search builder. + * Language gh repository search builder. * * @param v * the v * @return the gh repository search builder */ - public GHRepositorySearchBuilder created(String v) { - return q("created:" + v); + public GHRepositorySearchBuilder language(String v) { + return q("language:" + v); } /** - * Pushed gh repository search builder. + * Order gh repository search builder. * * @param v * the v * @return the gh repository search builder */ - public GHRepositorySearchBuilder pushed(String v) { - return q("pushed:" + v); + public GHRepositorySearchBuilder order(GHDirection v) { + req.with("order", v); + return this; } /** - * User gh repository search builder. + * Org gh repository search builder. * * @param v * the v * @return the gh repository search builder */ - public GHRepositorySearchBuilder user(String v) { - return q("user:" + v); + public GHRepositorySearchBuilder org(String v) { + return q("org:" + v); } /** - * Repo gh repository search builder. + * Pushed gh repository search builder. * * @param v * the v * @return the gh repository search builder */ - public GHRepositorySearchBuilder repo(String v) { - return q("repo:" + v); + public GHRepositorySearchBuilder pushed(String v) { + return q("pushed:" + v); } /** - * Language gh repository search builder. + * {@inheritDoc} + */ + @Override + public GHRepositorySearchBuilder q(String term) { + super.q(term); + return this; + } + + /** + * Repo gh repository search builder. * * @param v * the v * @return the gh repository search builder */ - public GHRepositorySearchBuilder language(String v) { - return q("language:" + v); + public GHRepositorySearchBuilder repo(String v) { + return q("repo:" + v); } /** - * Stars gh repository search builder. + * Size gh repository search builder. * * @param v * the v * @return the gh repository search builder */ - public GHRepositorySearchBuilder stars(String v) { - return q("stars:" + v); + public GHRepositorySearchBuilder size(String v) { + return q("size:" + v); } /** - * Topic gh repository search builder. + * Sort gh repository search builder. * - * @param v - * the v + * @param sort + * the sort * @return the gh repository search builder */ - public GHRepositorySearchBuilder topic(String v) { - return q("topic:" + v); + public GHRepositorySearchBuilder sort(Sort sort) { + req.with("sort", sort); + return this; } /** - * Org gh repository search builder. + * Stars gh repository search builder. * * @param v * the v * @return the gh repository search builder */ - public GHRepositorySearchBuilder org(String v) { - return q("org:" + v); + public GHRepositorySearchBuilder stars(String v) { + return q("stars:" + v); } /** - * Order gh repository search builder. + * Topic gh repository search builder. * * @param v * the v * @return the gh repository search builder */ - public GHRepositorySearchBuilder order(GHDirection v) { - req.with("order", v); - return this; + public GHRepositorySearchBuilder topic(String v) { + return q("topic:" + v); } /** - * Sort gh repository search builder. + * User gh repository search builder. * - * @param sort - * the sort + * @param v + * the v * @return the gh repository search builder */ - public GHRepositorySearchBuilder sort(Sort sort) { - req.with("sort", sort); - return this; + public GHRepositorySearchBuilder user(String v) { + return q("user:" + v); } /** - * The enum Sort. + * Search by repository visibility. + * + * @param visibility + * repository visibility + * @return the gh repository search builder + * @throws GHException + * if {@link GHRepository.Visibility#UNKNOWN} is passed. UNKNOWN is a placeholder for unexpected values + * encountered when reading data. + * @see Search + * by repository visibility */ - public enum Sort { - - /** The stars. */ - STARS, - /** The forks. */ - FORKS, - /** The updated. */ - UPDATED - } - - @SuppressFBWarnings( - value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, - justification = "JSON API") - private static class RepositorySearchResult extends SearchResult { - private GHRepository[] items; - - @Override - GHRepository[] getItems(GitHub root) { - for (GHRepository item : items) { - } - return items; + public GHRepositorySearchBuilder visibility(GHRepository.Visibility visibility) { + if (visibility == GHRepository.Visibility.UNKNOWN) { + throw new GHException( + "UNKNOWN is a placeholder for unexpected values encountered when reading data. It cannot be passed as a search parameter."); } + + return q("is:" + visibility); } /** @@ -259,4 +250,13 @@ GHRepository[] getItems(GitHub root) { protected String getApiUrl() { return "/search/repositories"; } + + /** + * {@inheritDoc} + */ + @Override + GHRepositorySearchBuilder q(String qualifier, String value) { + super.q(qualifier, value); + return this; + } } diff --git a/src/main/java/org/kohsuke/github/GHRepositorySelection.java b/src/main/java/org/kohsuke/github/GHRepositorySelection.java index 2833eeabcd..ff11023483 100644 --- a/src/main/java/org/kohsuke/github/GHRepositorySelection.java +++ b/src/main/java/org/kohsuke/github/GHRepositorySelection.java @@ -11,10 +11,10 @@ */ public enum GHRepositorySelection { - /** The selected. */ - SELECTED, /** The all. */ - ALL; + ALL, + /** The selected. */ + SELECTED; /** * Returns GitHub's internal representation of this event. diff --git a/src/main/java/org/kohsuke/github/GHRepositoryStatistics.java b/src/main/java/org/kohsuke/github/GHRepositoryStatistics.java index 1b075861f3..ece1e9d87d 100644 --- a/src/main/java/org/kohsuke/github/GHRepositoryStatistics.java +++ b/src/main/java/org/kohsuke/github/GHRepositoryStatistics.java @@ -18,151 +18,116 @@ */ public class GHRepositoryStatistics extends GitHubInteractiveObject { - private final GHRepository repo; - - private static final int MAX_WAIT_ITERATIONS = 3; - private static final int WAIT_SLEEP_INTERVAL = 5000; - /** - * Instantiates a new Gh repository statistics. - * - * @param repo - * the repo + * The type CodeFrequency. */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Acceptable risk") - public GHRepositoryStatistics(GHRepository repo) { - super(repo.root()); - this.repo = repo; - } + public static class CodeFrequency { - /** - * Get contributors list with additions, deletions, and commit count. See - * https://developer.github.com/v3/repos/statistics/#get-contributors-list-with-additions-deletions-and-commit-counts - * - * @return the contributor stats - * @throws InterruptedException - * the interrupted exception - */ - public PagedIterable getContributorStats() throws InterruptedException { - return getContributorStats(true); - } + private final int additions; + private final int deletions; + private final int week; - /** - * Gets contributor stats. - * - * @param waitTillReady - * Whether to sleep the thread if necessary until the statistics are ready. This is true by default. - * @return the contributor stats - * @throws InterruptedException - * the interrupted exception - */ - @BetaApi - @SuppressWarnings("SleepWhileInLoop") - @SuppressFBWarnings(value = { "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE" }, justification = "JSON API") - public PagedIterable getContributorStats(boolean waitTillReady) throws InterruptedException { - PagedIterable stats = getContributorStatsImpl(); + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + private CodeFrequency(List item) { + week = item.get(0); + additions = item.get(1); + deletions = item.get(2); + } - if (stats == null && waitTillReady) { - for (int i = 0; i < MAX_WAIT_ITERATIONS; i += 1) { - // Wait a few seconds and try again. - Thread.sleep(WAIT_SLEEP_INTERVAL); - stats = getContributorStatsImpl(); - if (stats != null) { - break; - } - } + /** + * Gets additions. + * + * @return The number of additions for the week. + */ + public long getAdditions() { + return additions; } - return stats; - } + /** + * Gets deletions. + * + * @return The number of deletions for the week. NOTE: This will be a NEGATIVE number. + */ + public long getDeletions() { + // TODO: Perhaps return Math.abs(deletions), + // since most developers may not expect a negative number. + return deletions; + } - /** - * This gets the actual statistics from the server. Returns null if they are still being cached. - */ - private PagedIterable getContributorStatsImpl() { - return root().createRequest() - .withUrlPath(getApiTailUrl("contributors")) - .toIterable(ContributorStats[].class, null); + /** + * Gets week timestamp. + * + * @return The start of the week as a UNIX timestamp. + */ + public int getWeekTimestamp() { + return week; + } + + /** + * To string. + * + * @return the string + */ + @Override + public String toString() { + return "Week starting " + getWeekTimestamp() + " has " + getAdditions() + " additions and " + + Math.abs(getDeletions()) + " deletions"; + } } /** - * The type ContributorStats. + * The type CommitActivity. */ @SuppressFBWarnings( - value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD", - "URF_UNREAD_FIELD" }, + value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, justification = "JSON API") - public static class ContributorStats extends GHObject { + public static class CommitActivity extends GHObject { + + private List days; + private int total; + private long week; /** - * Create default ContributorStats instance + * Create default CommitActivity instance */ - public ContributorStats() { + public CommitActivity() { } - private GHUser author; - private int total; - private List weeks; - /** - * Gets author. + * Gets days. * - * @return The author described by these statistics. + * @return The number of commits for each day of the week. 0 = Sunday, 1 = Monday, etc. */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHUser getAuthor() { - return author; + public List getDays() { + return Collections.unmodifiableList(days); } /** * Gets total. * - * @return The total number of commits authored by the contributor. + * @return The total number of commits for the week. */ public int getTotal() { return total; } /** - * Convenience method to look up week with particular timestamp. - * - * @param timestamp - * The timestamp to look for. - * @return The week starting with the given timestamp. Throws an exception if it is not found. - * @throws NoSuchElementException - * the no such element exception - */ - public Week getWeek(long timestamp) throws NoSuchElementException { - // maybe store the weeks in a map to make this more efficient? - for (Week week : weeks) { - if (week.getWeekTimestamp() == timestamp) { - return week; - } - } - - // this is safer than returning null - throw new NoSuchElementException(); - } - - /** - * Gets weeks. - * - * @return The total number of commits authored by the contributor. - */ - public List getWeeks() { - return Collections.unmodifiableList(weeks); - } - - /** - * To string. + * Gets week. * - * @return the string + * @return The start of the week as a UNIX timestamp. */ - @Override - public String toString() { - return author.getLogin() + " made " + String.valueOf(total) + " contributions over " - + String.valueOf(weeks.size()) + " weeks"; + public long getWeek() { + return week; } + } + /** + * The type ContributorStats. + */ + @SuppressFBWarnings( + value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD", + "URF_UNREAD_FIELD" }, + justification = "JSON API") + public static class ContributorStats extends GHObject { /** * The type Week. @@ -173,33 +138,33 @@ public String toString() { justification = "JSON API") public static class Week { + private int a; + + private int c; + private int d; + private long w; /** * Create default Week instance */ public Week() { } - private long w; - private int a; - private int d; - private int c; - /** - * Gets week timestamp. + * Gets number of additions. * - * @return Start of the week, as a UNIX timestamp. + * @return The number of additions for the week. */ - public long getWeekTimestamp() { - return w; + public int getNumberOfAdditions() { + return a; } /** - * Gets number of additions. + * Gets number of commits. * - * @return The number of additions for the week. + * @return The number of commits for the week. */ - public int getNumberOfAdditions() { - return a; + public int getNumberOfCommits() { + return c; } /** @@ -212,12 +177,12 @@ public int getNumberOfDeletions() { } /** - * Gets number of commits. + * Gets week timestamp. * - * @return The number of commits for the week. + * @return Start of the week, as a UNIX timestamp. */ - public int getNumberOfCommits() { - return c; + public long getWeekTimestamp() { + return w; } /** @@ -230,132 +195,64 @@ public String toString() { return String.format("Week starting %d - Additions: %d, Deletions: %d, Commits: %d", w, a, d, c); } } - } - - /** - * Get the last year of commit activity data. See - * https://developer.github.com/v3/repos/statistics/#get-the-last-year-of-commit-activity-data - * - * @return the commit activity - */ - public PagedIterable getCommitActivity() { - return root().createRequest() - .withUrlPath(getApiTailUrl("commit_activity")) - .toIterable(CommitActivity[].class, null); - } - /** - * The type CommitActivity. - */ - @SuppressFBWarnings( - value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, - justification = "JSON API") - public static class CommitActivity extends GHObject { + private GHUser author; + private int total; + private List weeks; /** - * Create default CommitActivity instance + * Create default ContributorStats instance */ - public CommitActivity() { + public ContributorStats() { } - private List days; - private int total; - private long week; - /** - * Gets days. + * Gets author. * - * @return The number of commits for each day of the week. 0 = Sunday, 1 = Monday, etc. + * @return The author described by these statistics. */ - public List getDays() { - return Collections.unmodifiableList(days); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHUser getAuthor() { + return author; } /** * Gets total. * - * @return The total number of commits for the week. + * @return The total number of commits authored by the contributor. */ public int getTotal() { return total; } /** - * Gets week. - * - * @return The start of the week as a UNIX timestamp. - */ - public long getWeek() { - return week; - } - } - - /** - * Get the number of additions and deletions per week. See - * https://developer.github.com/v3/repos/statistics/#get-the-number-of-additions-and-deletions-per-week - * - * @return the code frequency - * @throws IOException - * the io exception - */ - public List getCodeFrequency() throws IOException { - try { - CodeFrequency[] list = root().createRequest() - .withUrlPath(getApiTailUrl("code_frequency")) - .fetch(CodeFrequency[].class); - - return Arrays.asList(list); - } catch (MismatchedInputException e) { - // This sometimes happens when retrieving code frequency statistics - // for a repository for the first time. It is probably still being - // generated, so return null. - return null; - } - } - - /** - * The type CodeFrequency. - */ - public static class CodeFrequency { - - private final int week; - private final int additions; - private final int deletions; - - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - private CodeFrequency(List item) { - week = item.get(0); - additions = item.get(1); - deletions = item.get(2); - } - - /** - * Gets week timestamp. + * Convenience method to look up week with particular timestamp. * - * @return The start of the week as a UNIX timestamp. + * @param timestamp + * The timestamp to look for. + * @return The week starting with the given timestamp. Throws an exception if it is not found. + * @throws NoSuchElementException + * the no such element exception */ - public int getWeekTimestamp() { - return week; - } + public Week getWeek(long timestamp) throws NoSuchElementException { + // maybe store the weeks in a map to make this more efficient? + for (Week week : weeks) { + if (week.getWeekTimestamp() == timestamp) { + return week; + } + } - /** - * Gets additions. - * - * @return The number of additions for the week. - */ - public long getAdditions() { - return additions; + // this is safer than returning null + throw new NoSuchElementException(); } /** - * Gets deletions. + * Gets weeks. * - * @return The number of deletions for the week. NOTE: This will be a NEGATIVE number. + * @return The total number of commits authored by the contributor. */ - public long getDeletions() { - // TODO: Perhaps return Math.abs(deletions), - // since most developers may not expect a negative number. - return deletions; + public List getWeeks() { + return Collections.unmodifiableList(weeks); } /** @@ -365,37 +262,25 @@ public long getDeletions() { */ @Override public String toString() { - return "Week starting " + getWeekTimestamp() + " has " + getAdditions() + " additions and " - + Math.abs(getDeletions()) + " deletions"; + return author.getLogin() + " made " + String.valueOf(total) + " contributions over " + + String.valueOf(weeks.size()) + " weeks"; } } - /** - * Get the weekly commit count for the repository owner and everyone else. See - * https://developer.github.com/v3/repos/statistics/#get-the-weekly-commit-count-for-the-repository-owner-and-everyone-else - * - * @return the participation - * @throws IOException - * the io exception - */ - public Participation getParticipation() throws IOException { - return root().createRequest().withUrlPath(getApiTailUrl("participation")).fetch(Participation.class); - } - /** * The type Participation. */ public static class Participation extends GHObject { + private List all; + + private List owner; /** * Create default Participation instance */ public Participation() { } - private List all; - private List owner; - /** * Gets all commits. * @@ -415,21 +300,6 @@ public List getOwnerCommits() { } } - /** - * Get the number of commits per hour in each day. See - * https://developer.github.com/v3/repos/statistics/#get-the-number-of-commits-per-hour-in-each-day - * - * @return the punch card - * @throws IOException - * the io exception - */ - public List getPunchCard() throws IOException { - PunchCardItem[] list = root().createRequest() - .withUrlPath(getApiTailUrl("punch_card")) - .fetch(PunchCardItem[].class); - return Arrays.asList(list); - } - /** * The type PunchCardItem. */ @@ -483,6 +353,136 @@ public String toString() { } } + private static final int MAX_WAIT_ITERATIONS = 3; + + private static final int WAIT_SLEEP_INTERVAL = 5000; + + private final GHRepository repo; + + /** + * Instantiates a new Gh repository statistics. + * + * @param repo + * the repo + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Acceptable risk") + public GHRepositoryStatistics(GHRepository repo) { + super(repo.root()); + this.repo = repo; + } + + /** + * Get the number of additions and deletions per week. See + * https://developer.github.com/v3/repos/statistics/#get-the-number-of-additions-and-deletions-per-week + * + * @return the code frequency + * @throws IOException + * the io exception + */ + public List getCodeFrequency() throws IOException { + try { + CodeFrequency[] list = root().createRequest() + .withUrlPath(getApiTailUrl("code_frequency")) + .fetch(CodeFrequency[].class); + + return Arrays.asList(list); + } catch (MismatchedInputException e) { + // This sometimes happens when retrieving code frequency statistics + // for a repository for the first time. It is probably still being + // generated, so return null. + return null; + } + } + + /** + * Get the last year of commit activity data. See + * https://developer.github.com/v3/repos/statistics/#get-the-last-year-of-commit-activity-data + * + * @return the commit activity + */ + public PagedIterable getCommitActivity() { + return root().createRequest() + .withUrlPath(getApiTailUrl("commit_activity")) + .toIterable(CommitActivity[].class, null); + } + + /** + * Get contributors list with additions, deletions, and commit count. See + * https://developer.github.com/v3/repos/statistics/#get-contributors-list-with-additions-deletions-and-commit-counts + * + * @return the contributor stats + * @throws InterruptedException + * the interrupted exception + */ + public PagedIterable getContributorStats() throws InterruptedException { + return getContributorStats(true); + } + + /** + * Gets contributor stats. + * + * @param waitTillReady + * Whether to sleep the thread if necessary until the statistics are ready. This is true by default. + * @return the contributor stats + * @throws InterruptedException + * the interrupted exception + */ + @BetaApi + @SuppressWarnings("SleepWhileInLoop") + @SuppressFBWarnings(value = { "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE" }, justification = "JSON API") + public PagedIterable getContributorStats(boolean waitTillReady) throws InterruptedException { + PagedIterable stats = getContributorStatsImpl(); + + if (stats == null && waitTillReady) { + for (int i = 0; i < MAX_WAIT_ITERATIONS; i += 1) { + // Wait a few seconds and try again. + Thread.sleep(WAIT_SLEEP_INTERVAL); + stats = getContributorStatsImpl(); + if (stats != null) { + break; + } + } + } + + return stats; + } + + /** + * Get the weekly commit count for the repository owner and everyone else. See + * https://developer.github.com/v3/repos/statistics/#get-the-weekly-commit-count-for-the-repository-owner-and-everyone-else + * + * @return the participation + * @throws IOException + * the io exception + */ + public Participation getParticipation() throws IOException { + return root().createRequest().withUrlPath(getApiTailUrl("participation")).fetch(Participation.class); + } + + /** + * Get the number of commits per hour in each day. See + * https://developer.github.com/v3/repos/statistics/#get-the-number-of-commits-per-hour-in-each-day + * + * @return the punch card + * @throws IOException + * the io exception + */ + public List getPunchCard() throws IOException { + PunchCardItem[] list = root().createRequest() + .withUrlPath(getApiTailUrl("punch_card")) + .fetch(PunchCardItem[].class); + return Arrays.asList(list); + } + + /** + * This gets the actual statistics from the server. Returns null if they are still being cached. + */ + private PagedIterable getContributorStatsImpl() { + return root().createRequest() + .withUrlPath(getApiTailUrl("contributors")) + .toIterable(ContributorStats[].class, null); + } + /** * Gets the api tail url. * diff --git a/src/main/java/org/kohsuke/github/GHRepositoryTraffic.java b/src/main/java/org/kohsuke/github/GHRepositoryTraffic.java index 49738fceff..924bb3e1ab 100644 --- a/src/main/java/org/kohsuke/github/GHRepositoryTraffic.java +++ b/src/main/java/org/kohsuke/github/GHRepositoryTraffic.java @@ -1,5 +1,8 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; + +import java.time.Instant; import java.util.Date; import java.util.List; @@ -7,8 +10,67 @@ /** * The type GHRepositoryTraffic. */ -public abstract class GHRepositoryTraffic implements TrafficInfo { +public abstract class GHRepositoryTraffic extends GitHubBridgeAdapterObject implements TrafficInfo { + /** + * The type DailyInfo. + */ + public static abstract class DailyInfo implements TrafficInfo { + private int count; + private String timestamp; + private int uniques; + + /** + * Instantiates a new daily info. + */ + DailyInfo() { + } + + /** + * Instantiates a new daily info. + * + * @param timestamp + * the timestamp + * @param count + * the count + * @param uniques + * the uniques + */ + DailyInfo(String timestamp, Integer count, Integer uniques) { + this.timestamp = timestamp; + this.count = count; + this.uniques = uniques; + } + + /** + * Gets the count. + * + * @return the count + */ + public int getCount() { + return count; + } + + /** + * Gets timestamp. + * + * @return the timestamp + */ + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getTimestamp() { + return GitHubClient.parseInstant(timestamp); + } + + /** + * Gets the uniques. + * + * @return the uniques + */ + public int getUniques() { + return uniques; + } + } private int count; + private int uniques; /** @@ -39,15 +101,6 @@ public int getCount() { return count; } - /** - * Gets the uniques. - * - * @return the uniques - */ - public int getUniques() { - return uniques; - } - /** * Gets daily info. * @@ -56,60 +109,11 @@ public int getUniques() { public abstract List getDailyInfo(); /** - * The type DailyInfo. + * Gets the uniques. + * + * @return the uniques */ - public static abstract class DailyInfo implements TrafficInfo { - private String timestamp; - private int count; - private int uniques; - - /** - * Gets timestamp. - * - * @return the timestamp - */ - public Date getTimestamp() { - return GitHubClient.parseDate(timestamp); - } - - /** - * Gets the count. - * - * @return the count - */ - public int getCount() { - return count; - } - - /** - * Gets the uniques. - * - * @return the uniques - */ - public int getUniques() { - return uniques; - } - - /** - * Instantiates a new daily info. - */ - DailyInfo() { - } - - /** - * Instantiates a new daily info. - * - * @param timestamp - * the timestamp - * @param count - * the count - * @param uniques - * the uniques - */ - DailyInfo(String timestamp, Integer count, Integer uniques) { - this.timestamp = timestamp; - this.count = count; - this.uniques = uniques; - } + public int getUniques() { + return uniques; } } diff --git a/src/main/java/org/kohsuke/github/GHRepositoryVariable.java b/src/main/java/org/kohsuke/github/GHRepositoryVariable.java index cbafab9005..22b1556578 100644 --- a/src/main/java/org/kohsuke/github/GHRepositoryVariable.java +++ b/src/main/java/org/kohsuke/github/GHRepositoryVariable.java @@ -13,78 +13,46 @@ public class GHRepositoryVariable extends GitHubInteractiveObject { /** - * Create default GHRepositoryVariable instance - */ - public GHRepositoryVariable() { - } - - private static final String SLASH = "/"; - - private static final String VARIABLE_NAMESPACE = "actions/variables"; - - private String name; - private String value; - - private String url; - private String createdAt; - private String updatedAt; - - /** - * Gets url. - * - * @return the url - */ - @Nonnull - public String getUrl() { - return url; - } - - /** - * Gets name. - * - * @return the name - */ - public String getName() { - return name; - } - - /** - * Sets name. - * - * @param name - * the name + * A {@link GHRepositoryVariableBuilder} that creates a new {@link GHRepositoryVariable} + *

+ * Consumer must call {@link #done()} to create the new instance. */ - public void setName(String name) { - this.name = name; + @BetaApi + public static class Creator extends GHRepositoryVariableBuilder { + private Creator(@Nonnull GHRepository repository) { + super(GHRepositoryVariable.Creator.class, repository.root(), null); + requester.method("POST").withUrlPath(repository.getApiTailUrl(VARIABLE_NAMESPACE)); + } } /** - * Gets value. - * - * @return the value + * A {@link GHRepositoryVariableBuilder} that updates a single property per request + *

+ * {@link #done()} is called automatically after the property is set. */ - public String getValue() { - return value; + @BetaApi + public static class Setter extends GHRepositoryVariableBuilder { + private Setter(@Nonnull GHRepositoryVariable base) { + super(GHRepositoryVariable.class, base.getApiRoot(), base); + requester.method("PATCH").withUrlPath(base.getUrl().concat(SLASH).concat(base.getName())); + } } - /** - * Sets value. - * - * @param value - * the value - */ - public void setValue(String value) { - this.value = value; - } + private static final String SLASH = "/"; + private static final String VARIABLE_NAMESPACE = "actions/variables"; /** - * Gets the api root. + * Begins the creation of a new instance. + *

+ * Consumer must call {@link GHRepositoryVariable.Creator#done()} to commit changes. * - * @return the api root + * @param repository + * the repository in which the variable will be created. + * @return a {@link GHRepositoryVariable.Creator} */ - @Nonnull - GitHub getApiRoot() { - return Objects.requireNonNull(root()); + @BetaApi + static GHRepositoryVariable.Creator create(GHRepository repository) { + return new GHRepositoryVariable.Creator(repository); } /** @@ -106,19 +74,19 @@ static GHRepositoryVariable read(@Nonnull GHRepository repository, @Nonnull Stri variable.url = repository.getApiTailUrl("actions/variables"); return variable; } + private String createdAt; + private String name; + + private String updatedAt; + + private String url; + + private String value; /** - * Begins the creation of a new instance. - *

- * Consumer must call {@link GHRepositoryVariable.Creator#done()} to commit changes. - * - * @param repository - * the repository in which the variable will be created. - * @return a {@link GHRepositoryVariable.Creator} + * Create default GHRepositoryVariable instance */ - @BetaApi - static GHRepositoryVariable.Creator create(GHRepository repository) { - return new GHRepositoryVariable.Creator(repository); + public GHRepositoryVariable() { } /** @@ -131,6 +99,34 @@ public void delete() throws IOException { root().createRequest().method("DELETE").withUrlPath(getUrl().concat(SLASH).concat(name)).send(); } + /** + * Gets name. + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * Gets url. + * + * @return the url + */ + @Nonnull + public String getUrl() { + return url; + } + + /** + * Gets value. + * + * @return the value + */ + public String getValue() { + return value; + } + /** * Begins a single property update. * @@ -142,29 +138,33 @@ public GHRepositoryVariable.Setter set() { } /** - * A {@link GHRepositoryVariableBuilder} that updates a single property per request - *

- * {@link #done()} is called automatically after the property is set. + * Sets name. + * + * @param name + * the name */ - @BetaApi - public static class Setter extends GHRepositoryVariableBuilder { - private Setter(@Nonnull GHRepositoryVariable base) { - super(GHRepositoryVariable.class, base.getApiRoot(), base); - requester.method("PATCH").withUrlPath(base.getUrl().concat(SLASH).concat(base.getName())); - } + public void setName(String name) { + this.name = name; } /** - * A {@link GHRepositoryVariableBuilder} that creates a new {@link GHRepositoryVariable} - *

- * Consumer must call {@link #done()} to create the new instance. + * Sets value. + * + * @param value + * the value */ - @BetaApi - public static class Creator extends GHRepositoryVariableBuilder { - private Creator(@Nonnull GHRepository repository) { - super(GHRepositoryVariable.Creator.class, repository.root(), null); - requester.method("POST").withUrlPath(repository.getApiTailUrl(VARIABLE_NAMESPACE)); - } + public void setValue(String value) { + this.value = value; + } + + /** + * Gets the api root. + * + * @return the api root + */ + @Nonnull + GitHub getApiRoot() { + return Objects.requireNonNull(root()); } } diff --git a/src/main/java/org/kohsuke/github/GHRepositoryViewTraffic.java b/src/main/java/org/kohsuke/github/GHRepositoryViewTraffic.java index 669b919fa2..809169504a 100644 --- a/src/main/java/org/kohsuke/github/GHRepositoryViewTraffic.java +++ b/src/main/java/org/kohsuke/github/GHRepositoryViewTraffic.java @@ -10,6 +10,32 @@ * @see GHRepository#getViewTraffic() GHRepository#getViewTraffic() */ public class GHRepositoryViewTraffic extends GHRepositoryTraffic { + /** + * The type DailyInfo. + */ + public static class DailyInfo extends GHRepositoryTraffic.DailyInfo { + + /** + * Instantiates a new daily info. + */ + DailyInfo() { + } + + /** + * Instantiates a new daily info. + * + * @param timestamp + * the timestamp + * @param count + * the count + * @param uniques + * the uniques + */ + DailyInfo(String timestamp, int count, int uniques) { + super(timestamp, count, uniques); + } + } + private List views; /** @@ -33,15 +59,6 @@ public class GHRepositoryViewTraffic extends GHRepositoryTraffic { this.views = views; } - /** - * Gets views. - * - * @return the views - */ - public List getViews() { - return Collections.unmodifiableList(views); - } - /** * Gets the daily info. * @@ -52,28 +69,11 @@ public List getDailyInfo() { } /** - * The type DailyInfo. + * Gets views. + * + * @return the views */ - public static class DailyInfo extends GHRepositoryTraffic.DailyInfo { - - /** - * Instantiates a new daily info. - */ - DailyInfo() { - } - - /** - * Instantiates a new daily info. - * - * @param timestamp - * the timestamp - * @param count - * the count - * @param uniques - * the uniques - */ - DailyInfo(String timestamp, int count, int uniques) { - super(timestamp, count, uniques); - } + public List getViews() { + return Collections.unmodifiableList(views); } } diff --git a/src/main/java/org/kohsuke/github/GHRequestedAction.java b/src/main/java/org/kohsuke/github/GHRequestedAction.java index cec1349731..de4c701ebe 100644 --- a/src/main/java/org/kohsuke/github/GHRequestedAction.java +++ b/src/main/java/org/kohsuke/github/GHRequestedAction.java @@ -10,27 +10,15 @@ justification = "JSON API") public class GHRequestedAction extends GHObject { - /** - * Create default GHRequestedAction instance - */ - public GHRequestedAction() { - } + private String description; - private GHRepository owner; private String identifier; private String label; - private String description; - + private GHRepository owner; /** - * Wrap. - * - * @param owner - * the owner - * @return the GH requested action + * Create default GHRequestedAction instance */ - GHRequestedAction wrap(GHRepository owner) { - this.owner = owner; - return this; + public GHRequestedAction() { } /** @@ -42,6 +30,15 @@ public String getIdentifier() { return identifier; } + /** + * Gets the description. + * + * @return the description + */ + String getDescription() { + return description; + } + /** * Gets the label. * @@ -52,12 +49,15 @@ String getLabel() { } /** - * Gets the description. + * Wrap. * - * @return the description + * @param owner + * the owner + * @return the GH requested action */ - String getDescription() { - return description; + GHRequestedAction wrap(GHRepository owner) { + this.owner = owner; + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHSBOM.java b/src/main/java/org/kohsuke/github/GHSBOM.java new file mode 100644 index 0000000000..22d4d2f356 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHSBOM.java @@ -0,0 +1,376 @@ +package org.kohsuke.github; + +import com.fasterxml.jackson.annotation.JsonProperty; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.util.Collections; +import java.util.List; + +import javax.annotation.CheckForNull; + +/** + * Represents an SPDX Software Bill of Materials (SBOM) for a repository. + * + * @see GHRepository#getSBOM() + * @see GitHub SBOM API + */ +@SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD" }, + justification = "JSON API") +public class GHSBOM { + + /** + * Represents the creation information for an SBOM. + */ + public static class CreationInfo { + + private String created; + private List creators; + + /** + * Create default CreationInfo instance. + */ + public CreationInfo() { + } + + /** + * Gets the creation timestamp. + * + * @return the creation timestamp in ISO 8601 format + */ + public String getCreated() { + return created; + } + + /** + * Gets the list of creators. + * + * @return the list of creators (e.g., "Tool: GitHub.com-Dependency-Graph") + */ + public List getCreators() { + return creators != null ? Collections.unmodifiableList(creators) : Collections.emptyList(); + } + } + + /** + * Represents an external reference for a package. + */ + public static class ExternalRef { + + @JsonProperty("referenceCategory") + private String referenceCategory; + @JsonProperty("referenceLocator") + private String referenceLocator; + @JsonProperty("referenceType") + private String referenceType; + + /** + * Create default ExternalRef instance. + */ + public ExternalRef() { + } + + /** + * Gets the reference category. + * + * @return the reference category (e.g., "PACKAGE-MANAGER") + */ + public String getReferenceCategory() { + return referenceCategory; + } + + /** + * Gets the reference locator. + * + * @return the reference locator in PURL format + */ + public String getReferenceLocator() { + return referenceLocator; + } + + /** + * Gets the reference type. + * + * @return the reference type (e.g., "purl") + */ + public String getReferenceType() { + return referenceType; + } + } + + /** + * Represents a package in the SBOM. + */ + public static class Package { + + @JsonProperty("copyrightText") + private String copyrightText; + @JsonProperty("downloadLocation") + private String downloadLocation; + @JsonProperty("externalRefs") + private List externalRefs; + @JsonProperty("filesAnalyzed") + private boolean filesAnalyzed; + @JsonProperty("licenseConcluded") + private String licenseConcluded; + @JsonProperty("licenseDeclared") + private String licenseDeclared; + private String name; + @JsonProperty("SPDXID") + private String spdxid; + private String supplier; + @JsonProperty("versionInfo") + private String versionInfo; + + /** + * Create default Package instance. + */ + public Package() { + } + + /** + * Gets the copyright text. + * + * @return the copyright text, or null if not specified + */ + @CheckForNull + public String getCopyrightText() { + return copyrightText; + } + + /** + * Gets the download location. + * + * @return the download location + */ + public String getDownloadLocation() { + return downloadLocation; + } + + /** + * Gets the external references. + * + * @return the external references + */ + public List getExternalRefs() { + return externalRefs != null ? Collections.unmodifiableList(externalRefs) : Collections.emptyList(); + } + + /** + * Gets the concluded license. + * + * @return the concluded license, or null if not specified + */ + @CheckForNull + public String getLicenseConcluded() { + return licenseConcluded; + } + + /** + * Gets the declared license. + * + * @return the declared license, or null if not specified + */ + @CheckForNull + public String getLicenseDeclared() { + return licenseDeclared; + } + + /** + * Gets the package name. + * + * @return the package name + */ + public String getName() { + return name; + } + + /** + * Gets the SPDX identifier. + * + * @return the SPDX identifier + */ + public String getSPDXID() { + return spdxid; + } + + /** + * Gets the supplier. + * + * @return the supplier, or null if not specified + */ + @CheckForNull + public String getSupplier() { + return supplier; + } + + /** + * Gets the version info. + * + * @return the version info, or null if not specified + */ + @CheckForNull + public String getVersionInfo() { + return versionInfo; + } + + /** + * Returns whether files were analyzed. + * + * @return true if files were analyzed + */ + public boolean isFilesAnalyzed() { + return filesAnalyzed; + } + } + + /** + * Represents a relationship between SPDX elements. + */ + public static class Relationship { + + @JsonProperty("relatedSpdxElement") + private String relatedSpdxElement; + @JsonProperty("relationshipType") + private String relationshipType; + @JsonProperty("spdxElementId") + private String spdxElementId; + + /** + * Create default Relationship instance. + */ + public Relationship() { + } + + /** + * Gets the related SPDX element. + * + * @return the related SPDX element ID + */ + public String getRelatedSpdxElement() { + return relatedSpdxElement; + } + + /** + * Gets the relationship type. + * + * @return the relationship type (e.g., "DEPENDS_ON") + */ + public String getRelationshipType() { + return relationshipType; + } + + /** + * Gets the SPDX element ID. + * + * @return the SPDX element ID + */ + public String getSpdxElementId() { + return spdxElementId; + } + } + + @JsonProperty("creationInfo") + private CreationInfo creationInfo; + @JsonProperty("dataLicense") + private String dataLicense; + @JsonProperty("documentDescribes") + private String documentDescribes; + @JsonProperty("documentNamespace") + private String documentNamespace; + private String name; + private List packages; + private List relationships; + @JsonProperty("spdxVersion") + private String spdxVersion; + @JsonProperty("SPDXID") + private String spdxid; + + /** + * Create default GHSBOM instance. + */ + public GHSBOM() { + } + + /** + * Gets the creation info. + * + * @return the creation info + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public CreationInfo getCreationInfo() { + return creationInfo; + } + + /** + * Gets the data license. + * + * @return the data license (typically "CC0-1.0") + */ + public String getDataLicense() { + return dataLicense; + } + + /** + * Gets the document describes field. + * + * @return the document describes field, or null if not specified + */ + @CheckForNull + public String getDocumentDescribes() { + return documentDescribes; + } + + /** + * Gets the document namespace. + * + * @return the document namespace URI + */ + public String getDocumentNamespace() { + return documentNamespace; + } + + /** + * Gets the document name. + * + * @return the document name + */ + public String getName() { + return name; + } + + /** + * Gets the list of packages. + * + * @return the list of packages + */ + public List getPackages() { + return packages != null ? Collections.unmodifiableList(packages) : Collections.emptyList(); + } + + /** + * Gets the relationships. + * + * @return the relationships between SPDX elements + */ + public List getRelationships() { + return relationships != null ? Collections.unmodifiableList(relationships) : Collections.emptyList(); + } + + /** + * Gets the SPDX identifier. + * + * @return the SPDX identifier (typically "SPDXRef-DOCUMENT") + */ + public String getSPDXID() { + return spdxid; + } + + /** + * Gets the SPDX version. + * + * @return the SPDX version (e.g., "SPDX-2.3") + */ + public String getSpdxVersion() { + return spdxVersion; + } +} diff --git a/src/main/java/org/kohsuke/github/GHSBOMExportResult.java b/src/main/java/org/kohsuke/github/GHSBOMExportResult.java new file mode 100644 index 0000000000..311a85825e --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHSBOMExportResult.java @@ -0,0 +1,32 @@ +package org.kohsuke.github; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * Represents the result of exporting an SBOM from a repository. + * + * @see GHRepository#getSBOM() + * @see GitHub SBOM API + */ +@SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD" }, + justification = "JSON API") +public class GHSBOMExportResult { + + private GHSBOM sbom; + + /** + * Create default GHSBOMExportResult instance. + */ + public GHSBOMExportResult() { + } + + /** + * Gets the SBOM. + * + * @return the SBOM + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHSBOM getSbom() { + return sbom; + } +} diff --git a/src/main/java/org/kohsuke/github/GHSearchBuilder.java b/src/main/java/org/kohsuke/github/GHSearchBuilder.java index d7a25353ee..ea6317426c 100644 --- a/src/main/java/org/kohsuke/github/GHSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHSearchBuilder.java @@ -18,14 +18,14 @@ */ public abstract class GHSearchBuilder extends GHQueryBuilder { - /** The terms. */ - protected final List terms = new ArrayList(); - /** * Data transfer object that receives the result of search. */ private final Class> receiverType; + /** The terms. */ + protected final List terms = new ArrayList(); + /** * Instantiates a new GH search builder. * @@ -41,6 +41,18 @@ public abstract class GHSearchBuilder extends GHQueryBuilder { req.rateLimit(RateLimitTarget.SEARCH); } + /** + * Performs the search. + * + * @return the paged search iterable + */ + @Override + public PagedSearchIterable list() { + + req.set("q", StringUtils.join(terms, " ")); + return new PagedSearchIterable<>(root(), req.build(), receiverType); + } + /** * Search terms. * @@ -53,6 +65,13 @@ public GHQueryBuilder q(String term) { return this; } + /** + * Gets api url. + * + * @return the api url + */ + protected abstract String getApiUrl(); + /** * Add a search term with qualifier. * @@ -76,23 +95,4 @@ GHQueryBuilder q(@Nonnull final String qualifier, @CheckForNull final String } return this; } - - /** - * Performs the search. - * - * @return the paged search iterable - */ - @Override - public PagedSearchIterable list() { - - req.set("q", StringUtils.join(terms, " ")); - return new PagedSearchIterable<>(root(), req.build(), receiverType); - } - - /** - * Gets api url. - * - * @return the api url - */ - protected abstract String getApiUrl(); } diff --git a/src/main/java/org/kohsuke/github/GHStargazer.java b/src/main/java/org/kohsuke/github/GHStargazer.java index 1b9862295a..7d14ba8efb 100644 --- a/src/main/java/org/kohsuke/github/GHStargazer.java +++ b/src/main/java/org/kohsuke/github/GHStargazer.java @@ -1,7 +1,9 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.time.Instant; import java.util.Date; // TODO: Auto-generated Javadoc @@ -11,18 +13,18 @@ * @author noctarius */ @SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, justification = "JSON API") -public class GHStargazer { +public class GHStargazer extends GitHubBridgeAdapterObject { + private GHRepository repository; + + private String starredAt; + private GHUser user; /** * Create default GHStargazer instance */ public GHStargazer() { } - private GHRepository repository; - private String starred_at; - private GHUser user; - /** * Gets the repository that is stargazed. * @@ -35,12 +37,13 @@ public GHRepository getRepository() { /** * Gets the date when the repository was starred, however old stars before August 2012, will all show the date the - * API was changed to support starred_at. + * API was changed to support starredAt. * * @return the date the stargazer was added */ - public Date getStarredAt() { - return GitHubClient.parseDate(starred_at); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getStarredAt() { + return GitHubClient.parseInstant(starredAt); } /** diff --git a/src/main/java/org/kohsuke/github/GHSubscription.java b/src/main/java/org/kohsuke/github/GHSubscription.java index 1066d51b7b..ad26bfb612 100644 --- a/src/main/java/org/kohsuke/github/GHSubscription.java +++ b/src/main/java/org/kohsuke/github/GHSubscription.java @@ -1,8 +1,10 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; +import java.time.Instant; import java.util.Date; // TODO: Auto-generated Javadoc @@ -15,60 +17,72 @@ */ public class GHSubscription extends GitHubInteractiveObject { + private String createdAt, url, repositoryUrl, reason; + + private GHRepository repo; + private boolean subscribed, ignored; + /** * Create default GHSubscription instance */ public GHSubscription() { } - private String created_at, url, repository_url, reason; - private boolean subscribed, ignored; - - private GHRepository repo; + /** + * Removes this subscription. + * + * @throws IOException + * the io exception + */ + public void delete() throws IOException { + root().createRequest().method("DELETE").withUrlPath(repo.getApiTailUrl("subscription")).send(); + } /** * Gets created at. * * @return the created at */ - public Date getCreatedAt() { - return GitHubClient.parseDate(created_at); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getCreatedAt() { + return GitHubClient.parseInstant(createdAt); } /** - * Gets url. + * Gets reason. * - * @return the url + * @return the reason */ - public String getUrl() { - return url; + public String getReason() { + return reason; } /** - * Gets repository url. + * Gets repository. * - * @return the repository url + * @return the repository */ - public String getRepositoryUrl() { - return repository_url; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHRepository getRepository() { + return repo; } /** - * Gets reason. + * Gets repository url. * - * @return the reason + * @return the repository url */ - public String getReason() { - return reason; + public String getRepositoryUrl() { + return repositoryUrl; } /** - * Is subscribed boolean. + * Gets url. * - * @return the boolean + * @return the url */ - public boolean isSubscribed() { - return subscribed; + public String getUrl() { + return url; } /** @@ -81,23 +95,12 @@ public boolean isIgnored() { } /** - * Gets repository. - * - * @return the repository - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHRepository getRepository() { - return repo; - } - - /** - * Removes this subscription. + * Is subscribed boolean. * - * @throws IOException - * the io exception + * @return the boolean */ - public void delete() throws IOException { - root().createRequest().method("DELETE").withUrlPath(repo.getApiTailUrl("subscription")).send(); + public boolean isSubscribed() { + return subscribed; } /** diff --git a/src/main/java/org/kohsuke/github/GHTag.java b/src/main/java/org/kohsuke/github/GHTag.java index 1487256d2c..0f44d1a427 100644 --- a/src/main/java/org/kohsuke/github/GHTag.java +++ b/src/main/java/org/kohsuke/github/GHTag.java @@ -12,39 +12,25 @@ justification = "JSON API") public class GHTag extends GitHubInteractiveObject { - /** - * Create default GHTag instance - */ - public GHTag() { - } - - private GHRepository owner; + private GHCommit commit; private String name; - private GHCommit commit; + private GHRepository owner; /** - * Wrap. - * - * @param owner - * the owner - * @return the GH tag + * Create default GHTag instance */ - GHTag wrap(GHRepository owner) { - this.owner = owner; - if (commit != null) - commit.wrapUp(owner); - return this; + public GHTag() { } /** - * Gets owner. + * Gets commit. * - * @return the owner + * @return the commit */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHRepository getOwner() { - return owner; + public GHCommit getCommit() { + return commit; } /** @@ -57,12 +43,26 @@ public String getName() { } /** - * Gets commit. + * Gets owner. * - * @return the commit + * @return the owner */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHCommit getCommit() { - return commit; + public GHRepository getOwner() { + return owner; + } + + /** + * Wrap. + * + * @param owner + * the owner + * @return the GH tag + */ + GHTag wrap(GHRepository owner) { + this.owner = owner; + if (commit != null) + commit.wrapUp(owner); + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHTagObject.java b/src/main/java/org/kohsuke/github/GHTagObject.java index fbf255e7c3..07f5d9e35e 100644 --- a/src/main/java/org/kohsuke/github/GHTagObject.java +++ b/src/main/java/org/kohsuke/github/GHTagObject.java @@ -12,32 +12,38 @@ justification = "JSON API") public class GHTagObject extends GitHubInteractiveObject { + private String message; + + private GHRef.GHObject object; + + private GHRepository owner; + private String sha; + private String tag; + private GitUser tagger; + private String url; + private GHVerification verification; /** * Create default GHTagObject instance */ public GHTagObject() { } - private GHRepository owner; - - private String tag; - private String sha; - private String url; - private String message; - private GitUser tagger; - private GHRef.GHObject object; - private GHVerification verification; + /** + * Gets message. + * + * @return the message + */ + public String getMessage() { + return message; + } /** - * Wrap. + * Gets object. * - * @param owner - * the owner - * @return the GH tag object + * @return the object */ - GHTagObject wrap(GHRepository owner) { - this.owner = owner; - return this; + public GHRef.GHObject getObject() { + return object; } /** @@ -50,15 +56,6 @@ public GHRepository getOwner() { return owner; } - /** - * Gets tag. - * - * @return the tag - */ - public String getTag() { - return tag; - } - /** * Gets sha. * @@ -69,21 +66,12 @@ public String getSha() { } /** - * Gets url. - * - * @return the url - */ - public String getUrl() { - return url; - } - - /** - * Gets message. + * Gets tag. * - * @return the message + * @return the tag */ - public String getMessage() { - return message; + public String getTag() { + return tag; } /** @@ -96,12 +84,12 @@ public GitUser getTagger() { } /** - * Gets object. + * Gets url. * - * @return the object + * @return the url */ - public GHRef.GHObject getObject() { - return object; + public String getUrl() { + return url; } /** @@ -112,4 +100,16 @@ public GHRef.GHObject getObject() { public GHVerification getVerification() { return verification; } + + /** + * Wrap. + * + * @param owner + * the owner + * @return the GH tag object + */ + GHTagObject wrap(GHRepository owner) { + this.owner = owner; + return this; + } } diff --git a/src/main/java/org/kohsuke/github/GHTargetType.java b/src/main/java/org/kohsuke/github/GHTargetType.java index 2701fa59b8..2a7792c952 100644 --- a/src/main/java/org/kohsuke/github/GHTargetType.java +++ b/src/main/java/org/kohsuke/github/GHTargetType.java @@ -13,6 +13,8 @@ */ public enum GHTargetType { + /** The enterprise. */ + ENTERPRISE, /** The organization. */ ORGANIZATION, /** The user. */ diff --git a/src/main/java/org/kohsuke/github/GHTeam.java b/src/main/java/org/kohsuke/github/GHTeam.java index d2c9af1eb7..49be640321 100644 --- a/src/main/java/org/kohsuke/github/GHTeam.java +++ b/src/main/java/org/kohsuke/github/GHTeam.java @@ -20,36 +20,16 @@ */ public class GHTeam extends GHObject implements Refreshable { - /** - * Create default GHTeam instance - */ - public GHTeam() { - } - - /** - * Path for external group-related operations - */ - private static final String EXTERNAL_GROUPS = "/external-groups"; - - private String html_url; - private String name; - private String permission; - private String slug; - private String description; - private String privacy; - - private GHOrganization organization; // populated by GET /user/teams where Teams+Orgs are returned together - /** * The Enum Privacy. */ public enum Privacy { - /** The secret. */ - SECRET, /** The closed. */ // only visible to organization owners and members of this team. - CLOSED, // visible to all members of this organization. + CLOSED, + /** The secret. */ + SECRET, // visible to all members of this organization. /** Unknown privacy value */ UNKNOWN } @@ -59,137 +39,207 @@ public enum Privacy { */ public enum Role { - /** A normal member of the team. */ - MEMBER, /** * Able to add/remove other team members, promote other team members to team maintainer, and edit the team's * name and description. */ - MAINTAINER + MAINTAINER, + /** A normal member of the team. */ + MEMBER } /** - * Wrap up. - * - * @param owner - * the owner - * @return the GH team + * Path for external group-related operations */ - GHTeam wrapUp(GHOrganization owner) { - this.organization = owner; - return this; + private static final String EXTERNAL_GROUPS = "/external-groups"; + private String description; + private String htmlUrl; + private String name; + private GHOrganization organization; // populated by GET /user/teams where Teams+Orgs are returned together + private String permission; + + private String privacy; + + private String slug; + + /** + * Create default GHTeam instance + */ + public GHTeam() { } /** - * Wrap up. + * Add. * - * @param root - * the root - * @return the GH team + * @param r + * the r + * @throws IOException + * the io exception */ - GHTeam wrapUp(GitHub root) { // auto-wrapUp when organization is known from GET /user/teams - return wrapUp(organization); + public void add(GHRepository r) throws IOException { + add(r, (GHOrganization.RepositoryRole) null); } /** - * Gets name. + * Add. * - * @return the name + * @param r + * the r + * @param permission + * the permission + * @throws IOException + * the io exception */ - public String getName() { - return name; + public void add(GHRepository r, GHOrganization.RepositoryRole permission) throws IOException { + root().createRequest() + .method("PUT") + .with("permission", + Optional.ofNullable(permission).map(GHOrganization.RepositoryRole::toString).orElse(null)) + .withUrlPath(api("/repos/" + r.getOwnerName() + '/' + r.getName())) + .send(); } /** - * Gets permission. + * Adds a member to the team. + *

+ * The user will be invited to the organization if required. * - * @return the permission + * @param u + * the u + * @throws IOException + * the io exception + * @since 1.59 */ - public String getPermission() { - return permission; + public void add(GHUser u) throws IOException { + root().createRequest().method("PUT").withUrlPath(api("/memberships/" + u.getLogin())).send(); } /** - * Gets slug. + * Adds a member to the team + *

+ * The user will be invited to the organization if required. * - * @return the slug + * @param user + * github user + * @param role + * role for the new member + * @throws IOException + * the io exception */ - public String getSlug() { - return slug; + public void add(GHUser user, Role role) throws IOException { + root().createRequest() + .method("PUT") + .with("role", role) + .withUrlPath(api("/memberships/" + user.getLogin())) + .send(); } /** - * Gets description. + * Connect an external group to the team * - * @return the description + * @param group + * the group to connect + * @return the external group + * @throws IOException + * in case of failure + * @see documentation */ - public String getDescription() { - return description; + public GHExternalGroup connectToExternalGroup(final GHExternalGroup group) throws IOException { + return connectToExternalGroup(group.getId()); } /** - * Gets the privacy state. + * Connect an external group to the team * - * @return the privacy state. + * @param group_id + * the identifier of the group to connect + * @return the external group + * @throws IOException + * in case of failure + * @see documentation */ - public Privacy getPrivacy() { - return EnumUtils.getNullableEnumOrDefault(Privacy.class, privacy, Privacy.UNKNOWN); + public GHExternalGroup connectToExternalGroup(final long group_id) throws IOException { + try { + return root().createRequest() + .method("PATCH") + .with("group_id", group_id) + .withUrlPath(publicApi(EXTERNAL_GROUPS)) + .fetch(GHExternalGroup.class) + .wrapUp(getOrganization()); + } catch (final HttpException e) { + throw EnterpriseManagedSupport.forOrganization(getOrganization()) + .filterException(e, "Could not connect team to external group") + .orElse(e); + } } /** - * Sets description. + * Begins the creation of a new instance. * - * @param description - * the description + * Consumer must call {@link GHDiscussion.Creator#done()} to commit changes. + * + * @param title + * title of the discussion to be created + * @return a {@link GHDiscussion.Creator} * @throws IOException * the io exception */ - public void setDescription(String description) throws IOException { - root().createRequest().method("PATCH").with("description", description).withUrlPath(api("")).send(); + public GHDiscussion.Creator createDiscussion(String title) throws IOException { + return GHDiscussion.create(this).title(title); } /** - * Updates the team's privacy setting. + * Deletes this team. * - * @param privacy - * the privacy * @throws IOException * the io exception */ - public void setPrivacy(Privacy privacy) throws IOException { - root().createRequest().method("PATCH").with("privacy", privacy).withUrlPath(api("")).send(); + public void delete() throws IOException { + root().createRequest().method("DELETE").withUrlPath(api("")).send(); } /** - * Retrieves the discussions. + * Remove the connection of the team to an external group * - * @return the paged iterable + * @throws IOException + * in case of failure + * @see documentation */ - @Nonnull - public PagedIterable listDiscussions() { - return GHDiscussion.readAll(this); + public void deleteExternalGroupConnection() throws IOException { + root().createRequest().method("DELETE").withUrlPath(publicApi(EXTERNAL_GROUPS)).send(); } /** - * List members with specified role paged iterable. + * Equals. * - * @param role - * the role - * @return the paged iterable + * @param o + * the o + * @return true, if successful */ - public PagedIterable listMembers(String role) { - return root().createRequest().withUrlPath(api("/members")).with("role", role).toIterable(GHUser[].class, null); + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + GHTeam ghTeam = (GHTeam) o; + return Objects.equals(name, ghTeam.name) && Objects.equals(getUrl(), ghTeam.getUrl()) + && Objects.equals(permission, ghTeam.permission) && Objects.equals(slug, ghTeam.slug) + && Objects.equals(description, ghTeam.description) && Objects.equals(privacy, ghTeam.privacy); } /** - * List members with specified role paged iterable. + * Gets description. * - * @param role - * the role - * @return the paged iterable + * @return the description */ - public PagedIterable listMembers(Role role) { - return listMembers(transformEnum(role)); + public String getDescription() { + return description; } /** @@ -208,23 +258,35 @@ public GHDiscussion getDiscussion(long discussionNumber) throws IOException { } /** - * Retrieves the current members. + * Get the external groups connected to the team * - * @return the paged iterable + * @return the external groups + * @throws IOException + * the io exception + * @see documentation */ - public PagedIterable listMembers() { - return listMembers("all"); + public List getExternalGroups() throws IOException { + try { + return Collections.unmodifiableList(Arrays.asList(root().createRequest() + .method("GET") + .withUrlPath(publicApi(EXTERNAL_GROUPS)) + .fetch(GHExternalGroupPage.class) + .getGroups())); + } catch (final HttpException e) { + throw EnterpriseManagedSupport.forOrganization(getOrganization()) + .filterException(e, "Could not retrieve team external groups") + .orElse(e); + } } /** - * Retrieves the teams that are children of this team. + * Gets the html url. * - * @return the paged iterable + * @return the html url */ - public PagedIterable listChildTeams() { - return root().createRequest() - .withUrlPath(api("/teams")) - .toIterable(GHTeam[].class, item -> item.wrapUp(this.organization)); + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); } /** @@ -239,19 +301,43 @@ public Set getMembers() throws IOException { } /** - * Checks if this team has the specified user as a member. + * Gets name. * - * @param user - * the user - * @return the boolean + * @return the name */ - public boolean hasMember(GHUser user) { - try { - root().createRequest().withUrlPath(api("/memberships/" + user.getLogin())).send(); - return true; - } catch (@SuppressWarnings("unused") IOException ignore) { - return false; - } + public String getName() { + return name; + } + + /** + * Gets organization. + * + * @return the organization + * @throws IOException + * the io exception + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHOrganization getOrganization() throws IOException { + refresh(organization); + return organization; + } + + /** + * Gets permission. + * + * @return the permission + */ + public String getPermission() { + return permission; + } + + /** + * Gets the privacy state. + * + * @return the privacy state. + */ + public Privacy getPrivacy() { + return EnumUtils.getNullableEnumOrDefault(Privacy.class, privacy, Privacy.UNKNOWN); } /** @@ -268,284 +354,198 @@ public Map getRepositories() { } /** - * List repositories paged iterable. + * Gets slug. * - * @return the paged iterable + * @return the slug */ - public PagedIterable listRepositories() { - return root().createRequest().withUrlPath(api("/repos")).toIterable(GHRepository[].class, null); + public String getSlug() { + return slug; } /** - * Adds a member to the team. - *

- * The user will be invited to the organization if required. + * Checks if this team has the specified user as a member. * - * @param u - * the u - * @throws IOException - * the io exception - * @since 1.59 + * @param user + * the user + * @return the boolean */ - public void add(GHUser u) throws IOException { - root().createRequest().method("PUT").withUrlPath(api("/memberships/" + u.getLogin())).send(); + public boolean hasMember(GHUser user) { + try { + root().createRequest().withUrlPath(api("/memberships/" + user.getLogin())).send(); + return true; + } catch (@SuppressWarnings("unused") IOException ignore) { + return false; + } } /** - * Adds a member to the team - *

- * The user will be invited to the organization if required. + * Hash code. * - * @param user - * github user - * @param role - * role for the new member - * @throws IOException - * the io exception + * @return the int */ - public void add(GHUser user, Role role) throws IOException { - root().createRequest() - .method("PUT") - .with("role", role) - .withUrlPath(api("/memberships/" + user.getLogin())) - .send(); + @Override + public int hashCode() { + return Objects.hash(name, getUrl(), permission, slug, description, privacy); } /** - * Removes a member to the team. + * Retrieves the teams that are children of this team. * - * @param u - * the u - * @throws IOException - * the io exception + * @return the paged iterable */ - public void remove(GHUser u) throws IOException { - root().createRequest().method("DELETE").withUrlPath(api("/memberships/" + u.getLogin())).send(); + public PagedIterable listChildTeams() { + return root().createRequest() + .withUrlPath(api("/teams")) + .toIterable(GHTeam[].class, item -> item.wrapUp(this.organization)); } /** - * Add. + * Retrieves the discussions. * - * @param r - * the r - * @throws IOException - * the io exception + * @return the paged iterable */ - public void add(GHRepository r) throws IOException { - add(r, (GHOrganization.RepositoryRole) null); + @Nonnull + public PagedIterable listDiscussions() { + return GHDiscussion.readAll(this); } /** - * Add. + * Retrieves the current members. * - * @param r - * the r - * @param permission - * the permission - * @throws IOException - * the io exception + * @return the paged iterable */ - public void add(GHRepository r, GHOrganization.RepositoryRole permission) throws IOException { - root().createRequest() - .method("PUT") - .with("permission", - Optional.ofNullable(permission).map(GHOrganization.RepositoryRole::toString).orElse(null)) - .withUrlPath(api("/repos/" + r.getOwnerName() + '/' + r.getName())) - .send(); + public PagedIterable listMembers() { + return listMembers("all"); } /** - * Remove. + * List members with specified role paged iterable. * - * @param r - * the r - * @throws IOException - * the io exception + * @param role + * the role + * @return the paged iterable */ - public void remove(GHRepository r) throws IOException { - root().createRequest() - .method("DELETE") - .withUrlPath(api("/repos/" + r.getOwnerName() + '/' + r.getName())) - .send(); + public PagedIterable listMembers(Role role) { + return listMembers(transformEnum(role)); } /** - * Deletes this team. + * List members with specified role paged iterable. * - * @throws IOException - * the io exception + * @param role + * the role + * @return the paged iterable */ - public void delete() throws IOException { - root().createRequest().method("DELETE").withUrlPath(api("")).send(); - } - - private String api(String tail) { - if (organization == null) { - // Teams returned from pull requests to do not have an organization. Attempt to use url. - final URL url = Objects.requireNonNull(getUrl(), "Missing instance URL!"); - return StringUtils.prependIfMissing(url.toString().replace(root().getApiUrl(), ""), "/") + tail; - } - - return "/organizations/" + organization.getId() + "/team/" + getId() + tail; - } - - private String publicApi(String tail) throws IOException { - return "/orgs/" + getOrganization().login + "/teams/" + getSlug() + tail; + public PagedIterable listMembers(String role) { + return root().createRequest().withUrlPath(api("/members")).with("role", role).toIterable(GHUser[].class, null); } /** - * Begins the creation of a new instance. - * - * Consumer must call {@link GHDiscussion.Creator#done()} to commit changes. + * List repositories paged iterable. * - * @param title - * title of the discussion to be created - * @return a {@link GHDiscussion.Creator} - * @throws IOException - * the io exception + * @return the paged iterable */ - public GHDiscussion.Creator createDiscussion(String title) throws IOException { - return GHDiscussion.create(this).title(title); + public PagedIterable listRepositories() { + return root().createRequest().withUrlPath(api("/repos")).toIterable(GHRepository[].class, null); } /** - * Get the external groups connected to the team + * Refresh. * - * @return the external groups * @throws IOException - * the io exception - * @see documentation + * Signals that an I/O exception has occurred. */ - public List getExternalGroups() throws IOException { - try { - return Collections.unmodifiableList(Arrays.asList(root().createRequest() - .method("GET") - .withUrlPath(publicApi(EXTERNAL_GROUPS)) - .fetch(GHExternalGroupPage.class) - .getGroups())); - } catch (final HttpException e) { - throw EnterpriseManagedSupport.forOrganization(getOrganization()) - .filterException(e, "Could not retrieve team external groups") - .orElse(e); - } + @Override + public void refresh() throws IOException { + root().createRequest().withUrlPath(api("")).fetchInto(this).wrapUp(root()); } /** - * Connect an external group to the team + * Remove. * - * @param group - * the group to connect - * @return the external group + * @param r + * the r * @throws IOException - * in case of failure - * @see documentation + * the io exception */ - public GHExternalGroup connectToExternalGroup(final GHExternalGroup group) throws IOException { - return connectToExternalGroup(group.getId()); + public void remove(GHRepository r) throws IOException { + root().createRequest() + .method("DELETE") + .withUrlPath(api("/repos/" + r.getOwnerName() + '/' + r.getName())) + .send(); } /** - * Connect an external group to the team + * Removes a member to the team. * - * @param group_id - * the identifier of the group to connect - * @return the external group + * @param u + * the u * @throws IOException - * in case of failure - * @see documentation + * the io exception */ - public GHExternalGroup connectToExternalGroup(final long group_id) throws IOException { - try { - return root().createRequest() - .method("PATCH") - .with("group_id", group_id) - .withUrlPath(publicApi(EXTERNAL_GROUPS)) - .fetch(GHExternalGroup.class) - .wrapUp(getOrganization()); - } catch (final HttpException e) { - throw EnterpriseManagedSupport.forOrganization(getOrganization()) - .filterException(e, "Could not connect team to external group") - .orElse(e); - } + public void remove(GHUser u) throws IOException { + root().createRequest().method("DELETE").withUrlPath(api("/memberships/" + u.getLogin())).send(); } /** - * Remove the connection of the team to an external group + * Sets description. * + * @param description + * the description * @throws IOException - * in case of failure - * @see documentation + * the io exception */ - public void deleteExternalGroupConnection() throws IOException { - root().createRequest().method("DELETE").withUrlPath(publicApi(EXTERNAL_GROUPS)).send(); + public void setDescription(String description) throws IOException { + root().createRequest().method("PATCH").with("description", description).withUrlPath(api("")).send(); } /** - * Gets organization. + * Updates the team's privacy setting. * - * @return the organization + * @param privacy + * the privacy * @throws IOException * the io exception */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHOrganization getOrganization() throws IOException { - refresh(organization); - return organization; + public void setPrivacy(Privacy privacy) throws IOException { + root().createRequest().method("PATCH").with("privacy", privacy).withUrlPath(api("")).send(); } - /** - * Refresh. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Override - public void refresh() throws IOException { - root().createRequest().withUrlPath(api("")).fetchInto(this).wrapUp(root()); + private String api(String tail) { + if (organization == null) { + // Teams returned from pull requests to do not have an organization. Attempt to use url. + final URL url = Objects.requireNonNull(getUrl(), "Missing instance URL!"); + return StringUtils.prependIfMissing(url.toString().replace(root().getApiUrl(), ""), "/") + tail; + } + + return "/organizations/" + organization.getId() + "/team/" + getId() + tail; } - /** - * Gets the html url. - * - * @return the html url - */ - public URL getHtmlUrl() { - return GitHubClient.parseURL(html_url); + private String publicApi(String tail) throws IOException { + return "/orgs/" + getOrganization().login + "/teams/" + getSlug() + tail; } /** - * Equals. + * Wrap up. * - * @param o - * the o - * @return true, if successful + * @param owner + * the owner + * @return the GH team */ - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - GHTeam ghTeam = (GHTeam) o; - return Objects.equals(name, ghTeam.name) && Objects.equals(getUrl(), ghTeam.getUrl()) - && Objects.equals(permission, ghTeam.permission) && Objects.equals(slug, ghTeam.slug) - && Objects.equals(description, ghTeam.description) && Objects.equals(privacy, ghTeam.privacy); + GHTeam wrapUp(GHOrganization owner) { + this.organization = owner; + return this; } /** - * Hash code. + * Wrap up. * - * @return the int + * @param root + * the root + * @return the GH team */ - @Override - public int hashCode() { - return Objects.hash(name, getUrl(), permission, slug, description, privacy); + GHTeam wrapUp(GitHub root) { // auto-wrapUp when organization is known from GET /user/teams + return wrapUp(organization); } } diff --git a/src/main/java/org/kohsuke/github/GHTeamBuilder.java b/src/main/java/org/kohsuke/github/GHTeamBuilder.java index 6db44008af..12d077d974 100644 --- a/src/main/java/org/kohsuke/github/GHTeamBuilder.java +++ b/src/main/java/org/kohsuke/github/GHTeamBuilder.java @@ -12,9 +12,9 @@ */ public class GHTeamBuilder extends GitHubInteractiveObject { + private final String orgName; /** The builder. */ protected final Requester builder; - private final String orgName; /** * Instantiates a new GH team builder. @@ -34,6 +34,17 @@ public GHTeamBuilder(GitHub root, String orgName, String name) { this.builder.with("name", name); } + /** + * Creates a team with all the parameters. + * + * @return the gh team + * @throws IOException + * if team cannot be created + */ + public GHTeam create() throws IOException { + return builder.method("POST").withUrlPath("/orgs/" + orgName + "/teams").fetch(GHTeam.class).wrapUp(root()); + } + /** * Description for this team. * @@ -59,14 +70,14 @@ public GHTeamBuilder maintainers(String... maintainers) { } /** - * Repository names to add this team to. + * Parent team id for this team. * - * @param repoNames - * repoNames to add team to + * @param parentTeamId + * parentTeamId of team * @return a builder to continue with building */ - public GHTeamBuilder repositories(String... repoNames) { - this.builder.with("repo_names", repoNames); + public GHTeamBuilder parentTeamId(long parentTeamId) { + this.builder.with("parent_team_id", parentTeamId); return this; } @@ -98,25 +109,14 @@ public GHTeamBuilder privacy(GHTeam.Privacy privacy) { } /** - * Parent team id for this team. + * Repository names to add this team to. * - * @param parentTeamId - * parentTeamId of team + * @param repoNames + * repoNames to add team to * @return a builder to continue with building */ - public GHTeamBuilder parentTeamId(long parentTeamId) { - this.builder.with("parent_team_id", parentTeamId); + public GHTeamBuilder repositories(String... repoNames) { + this.builder.with("repo_names", repoNames); return this; } - - /** - * Creates a team with all the parameters. - * - * @return the gh team - * @throws IOException - * if team cannot be created - */ - public GHTeam create() throws IOException { - return builder.method("POST").withUrlPath("/orgs/" + orgName + "/teams").fetch(GHTeam.class).wrapUp(root()); - } } diff --git a/src/main/java/org/kohsuke/github/GHTeamChanges.java b/src/main/java/org/kohsuke/github/GHTeamChanges.java index 30962bd3bf..8aafac1e4e 100644 --- a/src/main/java/org/kohsuke/github/GHTeamChanges.java +++ b/src/main/java/org/kohsuke/github/GHTeamChanges.java @@ -14,89 +14,19 @@ @SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD" }, justification = "JSON API") public class GHTeamChanges { - /** - * Create default GHTeamChanges instance - */ - public GHTeamChanges() { - } - - private FromString description; - private FromString name; - private FromPrivacy privacy; - private FromRepository repository; - - /** - * Gets changes to description. - * - * @return changes to description. - */ - public FromString getDescription() { - return description; - } - - /** - * Gets changes to name. - * - * @return changes to name. - */ - public FromString getName() { - return name; - } - - /** - * Gets changes to privacy. - * - * @return changes to privacy. - */ - public FromPrivacy getPrivacy() { - return privacy; - } - - /** - * Gets changes for repository events. - * - * @return changes for repository events. - */ - public FromRepository getRepository() { - return repository; - } - - /** - * Changes made to a string value. - */ - public static class FromString { - - /** - * Create default FromString instance - */ - public FromString() { - } - - private String from; - - /** - * Gets the from. - * - * @return the from - */ - public String getFrom() { - return from; - } - } - /** * Changes made to privacy. */ public static class FromPrivacy { + private String from; + /** * Create default FromPrivacy instance */ public FromPrivacy() { } - private String from; - /** * Gets the from. * @@ -112,14 +42,14 @@ public Privacy getFrom() { */ public static class FromRepository { + private FromRepositoryPermissions permissions; + /** * Create default FromRepository instance */ public FromRepository() { } - private FromRepositoryPermissions permissions; - /** * Gets the changes to permissions. * @@ -129,19 +59,27 @@ public FromRepositoryPermissions getPermissions() { return permissions; } } - /** * Changes made to permissions. */ public static class FromRepositoryPermissions { + private GHRepoPermission from; + /** * Create default FromRepositoryPermissions instance */ public FromRepositoryPermissions() { } - private GHRepoPermission from; + /** + * Has admin access boolean. + * + * @return the boolean + */ + public boolean hadAdminAccess() { + return from != null && from.admin; + } /** * Has pull access boolean. @@ -160,14 +98,76 @@ public boolean hadPullAccess() { public boolean hadPushAccess() { return from != null && from.push; } + } + /** + * Changes made to a string value. + */ + public static class FromString { + + private String from; /** - * Has admin access boolean. + * Create default FromString instance + */ + public FromString() { + } + + /** + * Gets the from. * - * @return the boolean + * @return the from */ - public boolean hadAdminAccess() { - return from != null && from.admin; + public String getFrom() { + return from; } } + private FromString description; + + private FromString name; + + private FromPrivacy privacy; + + private FromRepository repository; + + /** + * Create default GHTeamChanges instance + */ + public GHTeamChanges() { + } + + /** + * Gets changes to description. + * + * @return changes to description. + */ + public FromString getDescription() { + return description; + } + + /** + * Gets changes to name. + * + * @return changes to name. + */ + public FromString getName() { + return name; + } + + /** + * Gets changes to privacy. + * + * @return changes to privacy. + */ + public FromPrivacy getPrivacy() { + return privacy; + } + + /** + * Gets changes for repository events. + * + * @return changes for repository events. + */ + public FromRepository getRepository() { + return repository; + } } diff --git a/src/main/java/org/kohsuke/github/GHThread.java b/src/main/java/org/kohsuke/github/GHThread.java index 2bafc28307..5e577aa75b 100644 --- a/src/main/java/org/kohsuke/github/GHThread.java +++ b/src/main/java/org/kohsuke/github/GHThread.java @@ -1,9 +1,11 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.FileNotFoundException; import java.io.IOException; +import java.time.Instant; import java.util.Date; // TODO: Auto-generated Javadoc @@ -17,137 +19,153 @@ @SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, justification = "JSON API") public class GHThread extends GHObject { - private GHRepository repository; - private Subject subject; - private String reason; - private boolean unread; - private String last_read_at; - private String url, subscription_url; - /** * The Class Subject. */ - static class Subject { + static class Subject extends GitHubBridgeAdapterObject { + + /** The latest comment url. */ + String latestCommentUrl; /** The title. */ String title; - /** The url. */ - String url; - - /** The latest comment url. */ - String latest_comment_url; - /** The type. */ String type; + + /** The url. */ + String url; } + private String lastReadAt; + private String reason; + private GHRepository repository; + private Subject subject; + private boolean unread; + + private String url, subscriptionUrl; private GHThread() {// no external construction allowed } /** - * Returns null if the entire thread has never been read. + * If this thread is about a commit, return that commit. * - * @return the last read at + * @return null if this thread is not about a commit. + * @throws IOException + * the io exception */ - public Date getLastReadAt() { - return GitHubClient.parseDate(last_read_at); + public GHCommit getBoundCommit() throws IOException { + if (!"Commit".equals(subject.type)) + return null; + return repository.getCommit(subject.url.substring(subject.url.lastIndexOf('/') + 1)); } /** - * Gets reason. + * If this thread is about an issue, return that issue. * - * @return the reason + * @return null if this thread is not about an issue. + * @throws IOException + * the io exception */ - public String getReason() { - return reason; + public GHIssue getBoundIssue() throws IOException { + if (!"Issue".equals(subject.type) && "PullRequest".equals(subject.type)) + return null; + return repository.getIssue(Integer.parseInt(subject.url.substring(subject.url.lastIndexOf('/') + 1))); } /** - * Gets repository. + * If this thread is about a pull request, return that pull request. * - * @return the repository + * @return null if this thread is not about a pull request. + * @throws IOException + * the io exception */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHRepository getRepository() { - return repository; + public GHPullRequest getBoundPullRequest() throws IOException { + if (!"PullRequest".equals(subject.type)) + return null; + return repository.getPullRequest(Integer.parseInt(subject.url.substring(subject.url.lastIndexOf('/') + 1))); } // TODO: how to expose the subject? /** - * Is read boolean. + * Gets last comment url. * - * @return the boolean + * @return the last comment url */ - public boolean isRead() { - return !unread; + public String getLastCommentUrl() { + return subject.latestCommentUrl; } /** - * Gets title. + * Returns null if the entire thread has never been read. * - * @return the title + * @return the last read at */ - public String getTitle() { - return subject.title; + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getLastReadAt() { + return GitHubClient.parseInstant(lastReadAt); } /** - * Gets type. + * Gets reason. * - * @return the type + * @return the reason */ - public String getType() { - return subject.type; + public String getReason() { + return reason; } /** - * Gets last comment url. + * Gets repository. * - * @return the last comment url + * @return the repository */ - public String getLastCommentUrl() { - return subject.latest_comment_url; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHRepository getRepository() { + return repository; } /** - * If this thread is about an issue, return that issue. + * Returns the current subscription for this thread. * - * @return null if this thread is not about an issue. + * @return null if no subscription exists. * @throws IOException * the io exception */ - public GHIssue getBoundIssue() throws IOException { - if (!"Issue".equals(subject.type) && "PullRequest".equals(subject.type)) + public GHSubscription getSubscription() throws IOException { + try { + return root().createRequest().method("POST").withUrlPath(subscriptionUrl).fetch(GHSubscription.class); + } catch (FileNotFoundException e) { return null; - return repository.getIssue(Integer.parseInt(subject.url.substring(subject.url.lastIndexOf('/') + 1))); + } } /** - * If this thread is about a pull request, return that pull request. + * Gets title. * - * @return null if this thread is not about a pull request. - * @throws IOException - * the io exception + * @return the title */ - public GHPullRequest getBoundPullRequest() throws IOException { - if (!"PullRequest".equals(subject.type)) - return null; - return repository.getPullRequest(Integer.parseInt(subject.url.substring(subject.url.lastIndexOf('/') + 1))); + public String getTitle() { + return subject.title; } /** - * If this thread is about a commit, return that commit. + * Gets type. * - * @return null if this thread is not about a commit. - * @throws IOException - * the io exception + * @return the type */ - public GHCommit getBoundCommit() throws IOException { - if (!"Commit".equals(subject.type)) - return null; - return repository.getCommit(subject.url.substring(subject.url.lastIndexOf('/') + 1)); + public String getType() { + return subject.type; + } + + /** + * Is read boolean. + * + * @return the boolean + */ + public boolean isRead() { + return !unread; } /** @@ -176,22 +194,7 @@ public GHSubscription subscribe(boolean subscribed, boolean ignored) throws IOEx .method("PUT") .with("subscribed", subscribed) .with("ignored", ignored) - .withUrlPath(subscription_url) + .withUrlPath(subscriptionUrl) .fetch(GHSubscription.class); } - - /** - * Returns the current subscription for this thread. - * - * @return null if no subscription exists. - * @throws IOException - * the io exception - */ - public GHSubscription getSubscription() throws IOException { - try { - return root().createRequest().method("POST").withUrlPath(subscription_url).fetch(GHSubscription.class); - } catch (FileNotFoundException e) { - return null; - } - } } diff --git a/src/main/java/org/kohsuke/github/GHTree.java b/src/main/java/org/kohsuke/github/GHTree.java index 867fa4531d..281d161b5b 100644 --- a/src/main/java/org/kohsuke/github/GHTree.java +++ b/src/main/java/org/kohsuke/github/GHTree.java @@ -20,35 +20,17 @@ justification = "JSON API") public class GHTree { - /** - * Create default GHTree instance - */ - public GHTree() { - } - - /** The repo. */ - /* package almost final */GHRepository repo; - - private boolean truncated; private String sha, url; - private GHTreeEntry[] tree; - /** - * The SHA for this trees. - * - * @return the sha - */ - public String getSha() { - return sha; - } + private GHTreeEntry[] tree; + private boolean truncated; + /** The repo. */ + /* package almost final */GHRepository repo; /** - * Return an array of entries of the trees. - * - * @return the tree + * Create default GHTree instance */ - public List getTree() { - return Collections.unmodifiableList(Arrays.asList(tree)); + public GHTree() { } /** @@ -69,12 +51,21 @@ public GHTreeEntry getEntry(String path) { } /** - * Returns true if the number of items in the tree array exceeded the GitHub maximum limit. + * The SHA for this trees. * - * @return true if the number of items in the tree array exceeded the GitHub maximum limit otherwise false. + * @return the sha */ - public boolean isTruncated() { - return truncated; + public String getSha() { + return sha; + } + + /** + * Return an array of entries of the trees. + * + * @return the tree + */ + public List getTree() { + return Collections.unmodifiableList(Arrays.asList(tree)); } /** @@ -87,6 +78,15 @@ public URL getUrl() { return GitHubClient.parseURL(url); } + /** + * Returns true if the number of items in the tree array exceeded the GitHub maximum limit. + * + * @return true if the number of items in the tree array exceeded the GitHub maximum limit otherwise false. + */ + public boolean isTruncated() { + return truncated; + } + /** * Wrap. * diff --git a/src/main/java/org/kohsuke/github/GHTreeBuilder.java b/src/main/java/org/kohsuke/github/GHTreeBuilder.java index c7e1902124..c771e60650 100644 --- a/src/main/java/org/kohsuke/github/GHTreeBuilder.java +++ b/src/main/java/org/kohsuke/github/GHTreeBuilder.java @@ -14,21 +14,31 @@ * Builder pattern for creating a new tree. Based on https://developer.github.com/v3/git/trees/#create-a-tree */ public class GHTreeBuilder { - private final GHRepository repo; - private final Requester req; - - private final List treeEntries = new ArrayList(); + private static class DeleteTreeEntry extends TreeEntry { + /** + * According to reference doc https://docs.github.com/en/rest/git/trees?apiVersion=2022-11-28#create-a-tree: if + * sha value is null then the file will be deleted. That's why in this DTO sha is always {@literal null} and is + * included to json. + */ + @JsonInclude + private final String sha = null; + private DeleteTreeEntry(String path) { + // The `mode` and `type` parameters are required by the API, but their values are ignored during delete. + // Supply reasonable placeholders. + super(path, "100644", "blob"); + } + } // Issue #636: Create Tree no longer accepts null value in sha field @JsonInclude(Include.NON_NULL) @SuppressFBWarnings("URF_UNREAD_FIELD") private static class TreeEntry { - private final String path; + private String content; private final String mode; - private final String type; + private final String path; private String sha; - private String content; + private final String type; private TreeEntry(String path, String mode, String type) { this.path = path; @@ -37,21 +47,11 @@ private TreeEntry(String path, String mode, String type) { } } - private static class DeleteTreeEntry extends TreeEntry { - /** - * According to reference doc https://docs.github.com/en/rest/git/trees?apiVersion=2022-11-28#create-a-tree: if - * sha value is null then the file will be deleted. That's why in this DTO sha is always {@literal null} and is - * included to json. - */ - @JsonInclude - private final String sha = null; + private final GHRepository repo; - private DeleteTreeEntry(String path) { - // The `mode` and `type` parameters are required by the API, but their values are ignored during delete. - // Supply reasonable placeholders. - super(path, "100644", "blob"); - } - } + private final Requester req; + + private final List treeEntries = new ArrayList(); /** * Instantiates a new GH tree builder. @@ -64,6 +64,41 @@ private DeleteTreeEntry(String path) { req = repo.root().createRequest(); } + /** + * Adds a new entry with the given text content to the tree. + * + * @param path + * the file path in the tree + * @param content + * the file content as UTF-8 encoded string + * @param executable + * true, if the file should be executable + * @return this GHTreeBuilder + */ + public GHTreeBuilder add(String path, String content, boolean executable) { + return add(path, content.getBytes(StandardCharsets.UTF_8), executable); + } + + /** + * Adds a new entry with the given binary content to the tree. + * + * @param path + * the file path in the tree + * @param content + * the file content as byte array + * @param executable + * true, if the file should be executable + * @return this GHTreeBuilder + */ + public GHTreeBuilder add(String path, byte[] content, boolean executable) { + try { + String dataSha = repo.createBlob().binaryContent(content).create().getSha(); + return shaEntry(path, dataSha, executable); + } catch (IOException e) { + throw new GHException("Cannot create binary content of '" + path + "'", e); + } + } + /** * Base tree gh tree builder. * @@ -76,6 +111,31 @@ public GHTreeBuilder baseTree(String baseTree) { return this; } + /** + * Creates a tree based on the parameters specified thus far. + * + * @return the gh tree + * @throws IOException + * the io exception + */ + public GHTree create() throws IOException { + req.with("tree", treeEntries); + return req.method("POST").withUrlPath(getApiTail()).fetch(GHTree.class).wrap(repo); + } + + /** + * Removes an entry with the given path from base tree. + * + * @param path + * the file path in the tree + * @return this GHTreeBuilder + */ + public GHTreeBuilder delete(String path) { + TreeEntry entry = new DeleteTreeEntry(path); + treeEntries.add(entry); + return this; + } + /** * Specialized version of entry() for adding an existing blob referred by its SHA. * @@ -116,67 +176,7 @@ public GHTreeBuilder textEntry(String path, String content, boolean executable) return this; } - /** - * Adds a new entry with the given binary content to the tree. - * - * @param path - * the file path in the tree - * @param content - * the file content as byte array - * @param executable - * true, if the file should be executable - * @return this GHTreeBuilder - */ - public GHTreeBuilder add(String path, byte[] content, boolean executable) { - try { - String dataSha = repo.createBlob().binaryContent(content).create().getSha(); - return shaEntry(path, dataSha, executable); - } catch (IOException e) { - throw new GHException("Cannot create binary content of '" + path + "'", e); - } - } - - /** - * Adds a new entry with the given text content to the tree. - * - * @param path - * the file path in the tree - * @param content - * the file content as UTF-8 encoded string - * @param executable - * true, if the file should be executable - * @return this GHTreeBuilder - */ - public GHTreeBuilder add(String path, String content, boolean executable) { - return add(path, content.getBytes(StandardCharsets.UTF_8), executable); - } - - /** - * Removes an entry with the given path from base tree. - * - * @param path - * the file path in the tree - * @return this GHTreeBuilder - */ - public GHTreeBuilder delete(String path) { - TreeEntry entry = new DeleteTreeEntry(path); - treeEntries.add(entry); - return this; - } - private String getApiTail() { return String.format("/repos/%s/%s/git/trees", repo.getOwnerName(), repo.getName()); } - - /** - * Creates a tree based on the parameters specified thus far. - * - * @return the gh tree - * @throws IOException - * the io exception - */ - public GHTree create() throws IOException { - req.with("tree", treeEntries); - return req.method("POST").withUrlPath(getApiTail()).fetch(GHTree.class).wrap(repo); - } } diff --git a/src/main/java/org/kohsuke/github/GHTreeEntry.java b/src/main/java/org/kohsuke/github/GHTreeEntry.java index 6759378e1f..a89bb432ff 100644 --- a/src/main/java/org/kohsuke/github/GHTreeEntry.java +++ b/src/main/java/org/kohsuke/github/GHTreeEntry.java @@ -13,17 +13,54 @@ */ public class GHTreeEntry { + private String path, mode, type, sha, url; + + private long size; + + /** The tree. */ + /* package almost final */GHTree tree; /** * Create default GHTreeEntry instance */ public GHTreeEntry() { } - /** The tree. */ - /* package almost final */GHTree tree; + /** + * If this tree entry represents a file, then return its information. Otherwise null. + * + * @return the gh blob + * @throws IOException + * the io exception + */ + public GHBlob asBlob() throws IOException { + if (type.equals("blob")) + return tree.repo.getBlob(sha); + else + return null; + } - private String path, mode, type, sha, url; - private long size; + /** + * If this tree entry represents a directory, then return it. Otherwise null. + * + * @return the gh tree + * @throws IOException + * the io exception + */ + public GHTree asTree() throws IOException { + if (type.equals("tree")) + return tree.repo.getTree(sha); + else + return null; + } + + /** + * Get mode such as 100644. + * + * @return the mode + */ + public String getMode() { + return mode; + } /** * Get the path such as "subdir/file.txt" @@ -35,12 +72,12 @@ public String getPath() { } /** - * Get mode such as 100644. + * SHA1 of this object. * - * @return the mode + * @return the sha */ - public String getMode() { - return mode; + public String getSha() { + return sha; } /** @@ -61,15 +98,6 @@ public String getType() { return type; } - /** - * SHA1 of this object. - * - * @return the sha - */ - public String getSha() { - return sha; - } - /** * API URL to this Git data, such as https://api.github.com/repos/jenkinsci * /jenkins/git/commits/b72322675eb0114363a9a86e9ad5a170d1d07ac0 @@ -80,20 +108,6 @@ public URL getUrl() { return GitHubClient.parseURL(url); } - /** - * If this tree entry represents a file, then return its information. Otherwise null. - * - * @return the gh blob - * @throws IOException - * the io exception - */ - public GHBlob asBlob() throws IOException { - if (type.equals("blob")) - return tree.repo.getBlob(sha); - else - return null; - } - /** * If this tree entry represents a file, then return its content. Otherwise null. * @@ -107,18 +121,4 @@ public InputStream readAsBlob() throws IOException { else return null; } - - /** - * If this tree entry represents a directory, then return it. Otherwise null. - * - * @return the gh tree - * @throws IOException - * the io exception - */ - public GHTree asTree() throws IOException { - if (type.equals("tree")) - return tree.repo.getTree(sha); - else - return null; - } } diff --git a/src/main/java/org/kohsuke/github/GHUser.java b/src/main/java/org/kohsuke/github/GHUser.java index b53da29180..e1a74beedb 100644 --- a/src/main/java/org/kohsuke/github/GHUser.java +++ b/src/main/java/org/kohsuke/github/GHUser.java @@ -23,7 +23,10 @@ */ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; + import java.io.IOException; +import java.time.Instant; import java.util.*; // TODO: Auto-generated Javadoc @@ -34,27 +37,32 @@ */ public class GHUser extends GHPerson { + /** The suspendedAt */ + private String suspendedAt; + + /** The ldap dn. */ + protected String ldapDn; + /** * Create default GHUser instance */ public GHUser() { } - /** The ldap dn. */ - protected String ldap_dn; - - /** The suspended_at */ - private String suspendedAt; - /** - * Gets keys. + * Equals. * - * @return the keys - * @throws IOException - * the io exception + * @param obj + * the obj + * @return true, if successful */ - public List getKeys() throws IOException { - return root().createRequest().withUrlPath(getApiTailUrl("keys")).toIterable(GHKey[].class, null).toList(); + @Override + public boolean equals(Object obj) { + if (obj instanceof GHUser) { + GHUser that = (GHUser) obj; + return this.login.equals(that.login); + } + return false; } /** @@ -68,13 +76,23 @@ public void follow() throws IOException { } /** - * Unfollow this user. + * Gets the bio. + * + * @return the bio + */ + public String getBio() { + return bio; + } + + /** + * Lists the users who are following this user. * + * @return the followers * @throws IOException * the io exception */ - public void unfollow() throws IOException { - root().createRequest().method("DELETE").withUrlPath("/user/following/" + login).send(); + public GHPersonSet getFollowers() throws IOException { + return new GHPersonSet(listFollowers().toList()); } /** @@ -89,71 +107,81 @@ public GHPersonSet getFollows() throws IOException { } /** - * Lists the users that this user is following. + * Gets keys. * - * @return the paged iterable + * @return the keys + * @throws IOException + * the io exception */ - public PagedIterable listFollows() { - return listUser("following"); + public List getKeys() throws IOException { + return root().createRequest().withUrlPath(getApiTailUrl("keys")).toIterable(GHKey[].class, null).toList(); } /** - * Lists the users who are following this user. + * Gets LDAP information for user. * - * @return the followers + * @return The LDAP information * @throws IOException * the io exception + * @see Github + * LDAP */ - public GHPersonSet getFollowers() throws IOException { - return new GHPersonSet(listFollowers().toList()); + public Optional getLdapDn() throws IOException { + super.populate(); + return Optional.ofNullable(ldapDn); } /** - * Lists the users who are following this user. + * Gets the organization that this user belongs to publicly. * - * @return the paged iterable + * @return the organizations + * @throws IOException + * the io exception */ - public PagedIterable listFollowers() { - return listUser("followers"); - } - - private PagedIterable listUser(final String suffix) { - return root().createRequest().withUrlPath(getApiTailUrl(suffix)).toIterable(GHUser[].class, null); + public GHPersonSet getOrganizations() throws IOException { + GHPersonSet orgs = new GHPersonSet(); + Set names = new HashSet(); + for (GHOrganization o : root().createRequest() + .withUrlPath("/users/" + login + "/orgs") + .toIterable(GHOrganization[].class, null) + .toArray()) { + if (names.add(o.getLogin())) // I've seen some duplicates in the data + orgs.add(root().getOrganization(o.getLogin())); + } + return orgs; } /** - * Lists all the subscribed (aka watched) repositories. - *

- * https://developer.github.com/v3/activity/watching/ + * When was this user suspended?. * - * @return the paged iterable + * @return updated date + * @throws IOException + * on error */ - public PagedIterable listSubscriptions() { - return listRepositories("subscriptions"); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getSuspendedAt() throws IOException { + super.populate(); + return GitHubClient.parseInstant(suspendedAt); } /** - * Lists all the repositories that this user has starred. + * Hash code. * - * @return the paged iterable + * @return the int */ - public PagedIterable listStarredRepositories() { - return listRepositories("starred"); + @Override + public int hashCode() { + return login.hashCode(); } /** - * Lists all the projects. - *

- * https://docs.github.com/en/rest/reference/projects#list-user-projects + * Returns true if this user is marked as hireable, false otherwise. * - * @return the paged iterable + * @return if the user is marked as hireable */ - public PagedIterable listProjects() { - return root().createRequest().withUrlPath(getApiTailUrl("projects")).toIterable(GHProject[].class, null); - } - - private PagedIterable listRepositories(final String suffix) { - return root().createRequest().withUrlPath(getApiTailUrl(suffix)).toIterable(GHRepository[].class, null); + public boolean isHireable() { + return hireable; } /** @@ -190,54 +218,34 @@ public boolean isPublicMemberOf(GHOrganization org) { } /** - * Returns true if this user is marked as hireable, false otherwise. - * - * @return if the user is marked as hireable - */ - public boolean isHireable() { - return hireable; - } - - /** - * Gets the bio. + * Lists events performed by a user (this includes private events if the caller is authenticated. * - * @return the bio + * @return the paged iterable + * @throws IOException + * Signals that an I/O exception has occurred. */ - public String getBio() { - return bio; + public PagedIterable listEvents() throws IOException { + return root().createRequest() + .withUrlPath(String.format("/users/%s/events", login)) + .toIterable(GHEventInfo[].class, null); } /** - * Gets the organization that this user belongs to publicly. + * Lists the users who are following this user. * - * @return the organizations - * @throws IOException - * the io exception + * @return the paged iterable */ - public GHPersonSet getOrganizations() throws IOException { - GHPersonSet orgs = new GHPersonSet(); - Set names = new HashSet(); - for (GHOrganization o : root().createRequest() - .withUrlPath("/users/" + login + "/orgs") - .toIterable(GHOrganization[].class, null) - .toArray()) { - if (names.add(o.getLogin())) // I've seen some duplicates in the data - orgs.add(root().getOrganization(o.getLogin())); - } - return orgs; + public PagedIterable listFollowers() { + return listUser("followers"); } /** - * Lists events performed by a user (this includes private events if the caller is authenticated. + * Lists the users that this user is following. * * @return the paged iterable - * @throws IOException - * Signals that an I/O exception has occurred. */ - public PagedIterable listEvents() throws IOException { - return root().createRequest() - .withUrlPath(String.format("/users/%s/events", login)) - .toIterable(GHEventInfo[].class, null); + public PagedIterable listFollows() { + return listUser("following"); } /** @@ -252,56 +260,52 @@ public PagedIterable listGists() { } /** - * Gets LDAP information for user. + * Lists all the projects. + *

+ * https://docs.github.com/en/rest/reference/projects#list-user-projects * - * @return The LDAP information - * @throws IOException - * the io exception - * @see Github - * LDAP + * @return the paged iterable */ - public Optional getLdapDn() throws IOException { - super.populate(); - return Optional.ofNullable(ldap_dn); + public PagedIterable listProjects() { + return root().createRequest().withUrlPath(getApiTailUrl("projects")).toIterable(GHProject[].class, null); } /** - * When was this user suspended?. + * Lists all the repositories that this user has starred. * - * @return updated date - * @throws IOException - * on error + * @return the paged iterable */ - public Date getSuspendedAt() throws IOException { - super.populate(); - return GitHubClient.parseDate(suspendedAt); + public PagedIterable listStarredRepositories() { + return listRepositories("starred"); } /** - * Hash code. + * Lists all the subscribed (aka watched) repositories. + *

+ * https://developer.github.com/v3/activity/watching/ * - * @return the int + * @return the paged iterable */ - @Override - public int hashCode() { - return login.hashCode(); + public PagedIterable listSubscriptions() { + return listRepositories("subscriptions"); } /** - * Equals. + * Unfollow this user. * - * @param obj - * the obj - * @return true, if successful + * @throws IOException + * the io exception */ - @Override - public boolean equals(Object obj) { - if (obj instanceof GHUser) { - GHUser that = (GHUser) obj; - return this.login.equals(that.login); - } - return false; + public void unfollow() throws IOException { + root().createRequest().method("DELETE").withUrlPath("/user/following/" + login).send(); + } + + private PagedIterable listRepositories(final String suffix) { + return root().createRequest().withUrlPath(getApiTailUrl(suffix)).toIterable(GHRepository[].class, null); + } + + private PagedIterable listUser(final String suffix) { + return root().createRequest().withUrlPath(getApiTailUrl(suffix)).toIterable(GHUser[].class, null); } /** diff --git a/src/main/java/org/kohsuke/github/GHUserSearchBuilder.java b/src/main/java/org/kohsuke/github/GHUserSearchBuilder.java index 8276b3d8e4..0193b2139e 100644 --- a/src/main/java/org/kohsuke/github/GHUserSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHUserSearchBuilder.java @@ -9,6 +9,28 @@ */ public class GHUserSearchBuilder extends GHSearchBuilder { + /** + * The enum Sort. + */ + public enum Sort { + + /** The followers. */ + FOLLOWERS, + /** The joined. */ + JOINED, + /** The repositories. */ + REPOSITORIES + } + + private static class UserSearchResult extends SearchResult { + private GHUser[] items; + + @Override + GHUser[] getItems(GitHub root) { + return items; + } + } + /** * Instantiates a new GH user search builder. * @@ -20,26 +42,25 @@ public class GHUserSearchBuilder extends GHSearchBuilder { } /** - * Search terms. + * Created gh user search builder. * - * @param term - * the term - * @return the GH user search builder + * @param v + * the v + * @return the gh user search builder */ - public GHUserSearchBuilder q(String term) { - super.q(term); - return this; + public GHUserSearchBuilder created(String v) { + return q("created:" + v); } /** - * Type gh user search builder. + * Followers gh user search builder. * * @param v * the v * @return the gh user search builder */ - public GHUserSearchBuilder type(String v) { - return q("type:" + v); + public GHUserSearchBuilder followers(String v) { + return q("followers:" + v); } /** @@ -54,14 +75,14 @@ public GHUserSearchBuilder in(String v) { } /** - * Repos gh user search builder. + * Language gh user search builder. * * @param v * the v * @return the gh user search builder */ - public GHUserSearchBuilder repos(String v) { - return q("repos:" + v); + public GHUserSearchBuilder language(String v) { + return q("language:" + v); } /** @@ -76,48 +97,38 @@ public GHUserSearchBuilder location(String v) { } /** - * Language gh user search builder. - * - * @param v - * the v - * @return the gh user search builder - */ - public GHUserSearchBuilder language(String v) { - return q("language:" + v); - } - - /** - * Created gh user search builder. + * Order gh user search builder. * * @param v * the v * @return the gh user search builder */ - public GHUserSearchBuilder created(String v) { - return q("created:" + v); + public GHUserSearchBuilder order(GHDirection v) { + req.with("order", v); + return this; } /** - * Followers gh user search builder. + * Search terms. * - * @param v - * the v - * @return the gh user search builder + * @param term + * the term + * @return the GH user search builder */ - public GHUserSearchBuilder followers(String v) { - return q("followers:" + v); + public GHUserSearchBuilder q(String term) { + super.q(term); + return this; } /** - * Order gh user search builder. + * Repos gh user search builder. * * @param v * the v * @return the gh user search builder */ - public GHUserSearchBuilder order(GHDirection v) { - req.with("order", v); - return this; + public GHUserSearchBuilder repos(String v) { + return q("repos:" + v); } /** @@ -133,25 +144,14 @@ public GHUserSearchBuilder sort(Sort sort) { } /** - * The enum Sort. + * Type gh user search builder. + * + * @param v + * the v + * @return the gh user search builder */ - public enum Sort { - - /** The followers. */ - FOLLOWERS, - /** The repositories. */ - REPOSITORIES, - /** The joined. */ - JOINED - } - - private static class UserSearchResult extends SearchResult { - private GHUser[] items; - - @Override - GHUser[] getItems(GitHub root) { - return items; - } + public GHUserSearchBuilder type(String v) { + return q("type:" + v); } /** diff --git a/src/main/java/org/kohsuke/github/GHVerification.java b/src/main/java/org/kohsuke/github/GHVerification.java index 6fb5493e88..04502b358f 100644 --- a/src/main/java/org/kohsuke/github/GHVerification.java +++ b/src/main/java/org/kohsuke/github/GHVerification.java @@ -17,53 +17,6 @@ justification = "JSON API") public class GHVerification { - /** - * Create default GHVerification instance - */ - public GHVerification() { - } - - private String signature, payload; - private boolean verified; - private Reason reason; - - /** - * Indicates whether GitHub considers the signature in this commit to be verified. - * - * @return true if the signature is valid else returns false. - */ - public boolean isVerified() { - return verified; - } - - /** - * Gets reason for verification value. - * - * @return reason of type {@link Reason}, such as "valid" or "unsigned". The possible values can be found in - * {@link Reason}} - */ - public Reason getReason() { - return reason; - } - - /** - * Gets signature used for the verification. - * - * @return null if not signed else encoded signature. - */ - public String getSignature() { - return signature; - } - - /** - * Gets the payload that was signed. - * - * @return null if not signed else encoded signature. - */ - public String getPayload() { - return payload; - } - /** * The possible values for reason in verification object from github. * @@ -73,58 +26,105 @@ public String getPayload() { */ public enum Reason { + /** The signing certificate or its chain could not be verified. */ + BAD_CERT, + + /** Invalid email used for signing. */ + BAD_EMAIL, + /** Signing key expired. */ EXPIRED_KEY, - /** The usage flags for the key that signed this don't allow signing. */ - NOT_SIGNING_KEY, - /** The GPG verification service misbehaved. */ GPGVERIFY_ERROR, /** The GPG verification service is unavailable at the moment. */ GPGVERIFY_UNAVAILABLE, - /** Unsigned. */ - UNSIGNED, + /** Invalid signature. */ + INVALID, - /** Unknown signature type. */ - UNKNOWN_SIGNATURE_TYPE, + /** Malformed signature. (Returned by graphQL) */ + MALFORMED_SIG, + + /** Malformed signature. */ + MALFORMED_SIGNATURE, + + /** The usage flags for the key that signed this don't allow signing. */ + NOT_SIGNING_KEY, /** Email used for signing not known to GitHub. */ NO_USER, - /** Email used for signing unverified on GitHub. */ - UNVERIFIED_EMAIL, + /** Valid signature, though certificate revocation check failed. */ + OCSP_ERROR, - /** Invalid email used for signing. */ - BAD_EMAIL, + /** Valid signature, pending certificate revocation checking. */ + OCSP_PENDING, + + /** One or more certificates in chain has been revoked. */ + OCSP_REVOKED, /** Key used for signing not known to GitHub. */ UNKNOWN_KEY, - /** Malformed signature. */ - MALFORMED_SIGNATURE, + /** Unknown signature type. */ + UNKNOWN_SIGNATURE_TYPE, - /** Invalid signature. */ - INVALID, + /** Unsigned. */ + UNSIGNED, + + /** Email used for signing unverified on GitHub. */ + UNVERIFIED_EMAIL, /** Valid signature and verified by GitHub. */ - VALID, + VALID + } - /** The signing certificate or its chain could not be verified. */ - BAD_CERT, + private Reason reason; + private String signature, payload; + private boolean verified; - /** Malformed signature. (Returned by graphQL) */ - MALFORMED_SIG, + /** + * Create default GHVerification instance + */ + public GHVerification() { + } - /** Valid signature, though certificate revocation check failed. */ - OCSP_ERROR, + /** + * Gets the payload that was signed. + * + * @return null if not signed else encoded signature. + */ + public String getPayload() { + return payload; + } - /** Valid signature, pending certificate revocation checking. */ - OCSP_PENDING, + /** + * Gets reason for verification value. + * + * @return reason of type {@link Reason}, such as "valid" or "unsigned". The possible values can be found in + * {@link Reason}} + */ + public Reason getReason() { + return reason; + } - /** One or more certificates in chain has been revoked. */ - OCSP_REVOKED + /** + * Gets signature used for the verification. + * + * @return null if not signed else encoded signature. + */ + public String getSignature() { + return signature; + } + + /** + * Indicates whether GitHub considers the signature in this commit to be verified. + * + * @return true if the signature is valid else returns false. + */ + public boolean isVerified() { + return verified; } } diff --git a/src/main/java/org/kohsuke/github/GHWorkflow.java b/src/main/java/org/kohsuke/github/GHWorkflow.java index 87d7278e80..dff9ffdc3d 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflow.java +++ b/src/main/java/org/kohsuke/github/GHWorkflow.java @@ -19,67 +19,77 @@ */ public class GHWorkflow extends GHObject { - /** - * Create default GHWorkflow instance - */ - public GHWorkflow() { - } + private String badgeUrl; + + private String htmlUrl; + private String name; // Not provided by the API. @JsonIgnore private GHRepository owner; - - private String name; private String path; - private String state; - - private String htmlUrl; - private String badgeUrl; + private String state; /** - * The name of the workflow. - * - * @return the name + * Create default GHWorkflow instance */ - public String getName() { - return name; + public GHWorkflow() { } /** - * The path of the workflow e.g. .github/workflows/blank.yaml + * Disable the workflow. * - * @return the path + * @throws IOException + * the io exception */ - public String getPath() { - return path; + public void disable() throws IOException { + root().createRequest().method("PUT").withUrlPath(getApiRoute(), "disable").send(); } /** - * The state of the workflow. + * Create a workflow dispatch event which triggers a manual workflow run. * - * @return the state + * @param ref + * the git reference for the workflow. The reference can be a branch or tag name. + * @throws IOException + * the io exception */ - public String getState() { - return state; + public void dispatch(String ref) throws IOException { + dispatch(ref, Collections.emptyMap()); } /** - * Gets the html url. + * Create a workflow dispatch event which triggers a manual workflow run. * - * @return the html url + * @param ref + * the git reference for the workflow. The reference can be a branch or tag name. + * @param inputs + * input keys and values configured in the workflow file. The maximum number of properties is 10. Any + * default properties configured in the workflow file will be used when inputs are omitted. + * @throws IOException + * the io exception */ - public URL getHtmlUrl() { - return GitHubClient.parseURL(htmlUrl); + public void dispatch(String ref, Map inputs) throws IOException { + Requester requester = root().createRequest() + .method("POST") + .withUrlPath(getApiRoute(), "dispatches") + .with("ref", ref); + + if (!inputs.isEmpty()) { + requester.with("inputs", inputs); + } + + requester.send(); } /** - * Repository to which the workflow belongs. + * Enable the workflow. * - * @return the repository + * @throws IOException + * the io exception */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHRepository getRepository() { - return owner; + public void enable() throws IOException { + root().createRequest().method("PUT").withUrlPath(getApiRoute(), "enable").send(); } /** @@ -92,59 +102,49 @@ public URL getBadgeUrl() { } /** - * Disable the workflow. + * Gets the html url. * - * @throws IOException - * the io exception + * @return the html url */ - public void disable() throws IOException { - root().createRequest().method("PUT").withUrlPath(getApiRoute(), "disable").send(); + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); } /** - * Enable the workflow. + * The name of the workflow. * - * @throws IOException - * the io exception + * @return the name */ - public void enable() throws IOException { - root().createRequest().method("PUT").withUrlPath(getApiRoute(), "enable").send(); + public String getName() { + return name; } /** - * Create a workflow dispatch event which triggers a manual workflow run. + * The path of the workflow e.g. .github/workflows/blank.yaml * - * @param ref - * the git reference for the workflow. The reference can be a branch or tag name. - * @throws IOException - * the io exception + * @return the path */ - public void dispatch(String ref) throws IOException { - dispatch(ref, Collections.emptyMap()); + public String getPath() { + return path; } /** - * Create a workflow dispatch event which triggers a manual workflow run. + * Repository to which the workflow belongs. * - * @param ref - * the git reference for the workflow. The reference can be a branch or tag name. - * @param inputs - * input keys and values configured in the workflow file. The maximum number of properties is 10. Any - * default properties configured in the workflow file will be used when inputs are omitted. - * @throws IOException - * the io exception + * @return the repository */ - public void dispatch(String ref, Map inputs) throws IOException { - Requester requester = root().createRequest() - .method("POST") - .withUrlPath(getApiRoute(), "dispatches") - .with("ref", ref); - - if (!inputs.isEmpty()) { - requester.with("inputs", inputs); - } + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHRepository getRepository() { + return owner; + } - requester.send(); + /** + * The state of the workflow. + * + * @return the state + */ + public String getState() { + return state; } /** diff --git a/src/main/java/org/kohsuke/github/GHWorkflowJob.java b/src/main/java/org/kohsuke/github/GHWorkflowJob.java index 76a2fddaef..c4fddcb553 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowJob.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowJob.java @@ -1,6 +1,7 @@ package org.kohsuke.github; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.commons.lang3.StringUtils; import org.kohsuke.github.GHWorkflowRun.Conclusion; @@ -9,6 +10,7 @@ import java.io.IOException; import java.net.URL; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -26,65 +28,147 @@ public class GHWorkflowJob extends GHObject { /** - * Create default GHWorkflowJob instance + * The Class Step. */ - public GHWorkflowJob() { - } + public static class Step extends GitHubBridgeAdapterObject { - // Not provided by the API. - @JsonIgnore - private GHRepository owner; + private String completedAt; - private String name; + private String conclusion; + private String name; - private String headSha; + private int number; + private String startedAt; + + private String status; + /** + * Create default Step instance + */ + public Step() { + } + + /** + * When was this step completed?. + * + * @return completion date + */ + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getCompletedAt() { + return GitHubClient.parseInstant(completedAt); + } + + /** + * Gets the conclusion of the step. + *

+ * Can be {@code UNKNOWN} if the value returned by GitHub is unknown from the API. + * + * @return conclusion of the step + */ + public Conclusion getConclusion() { + return Conclusion.from(conclusion); + } + + /** + * Gets the name of the step. + * + * @return name + */ + public String getName() { + return name; + } + + /** + * Gets the sequential number of the step. + * + * @return number + */ + public int getNumber() { + return number; + } + + /** + * When was this step started?. + * + * @return start date + */ + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getStartedAt() { + return GitHubClient.parseInstant(startedAt); + } + + /** + * Gets status of the step. + *

+ * Can be {@code UNKNOWN} if the value returned by GitHub is unknown from the API. + * + * @return status of the step + */ + public Status getStatus() { + return Status.from(status); + } + } + + private String checkRunUrl; - private String startedAt; private String completedAt; - private String status; private String conclusion; - private long runId; + private String headSha; + private String htmlUrl; + + private List labels = new ArrayList<>(); + private String name; + + // Not provided by the API. + @JsonIgnore + private GHRepository owner; private int runAttempt; - private String htmlUrl; - private String checkRunUrl; + private long runId; + private int runnerGroupId; + private String runnerGroupName; private int runnerId; private String runnerName; - private int runnerGroupId; - private String runnerGroupName; + private String startedAt; - private List steps = new ArrayList<>(); + private String status; - private List labels = new ArrayList<>(); + private List steps = new ArrayList<>(); /** - * The name of the job. - * - * @return the name + * Create default GHWorkflowJob instance */ - public String getName() { - return name; + public GHWorkflowJob() { } /** - * Gets the HEAD SHA. + * Downloads the logs. + *

+ * The logs are returned as a text file. * - * @return sha for the HEAD commit + * @param + * the type of result + * @param streamFunction + * The {@link InputStreamFunction} that will process the stream + * @return the result of reading the stream. + * @throws IOException + * The IO exception. */ - public String getHeadSha() { - return headSha; + public T downloadLogs(InputStreamFunction streamFunction) throws IOException { + requireNonNull(streamFunction, "Stream function must not be null"); + + return root().createRequest().method("GET").withUrlPath(getApiRoute(), "logs").fetchStream(streamFunction); } /** - * When was this job started?. + * The check run URL. * - * @return start date + * @return the check run url */ - public Date getStartedAt() { - return GitHubClient.parseDate(startedAt); + public URL getCheckRunUrl() { + return GitHubClient.parseURL(checkRunUrl); } /** @@ -92,19 +176,9 @@ public Date getStartedAt() { * * @return completion date */ - public Date getCompletedAt() { - return GitHubClient.parseDate(completedAt); - } - - /** - * Gets status of the job. - *

- * Can be {@code UNKNOWN} if the value returned by GitHub is unknown from the API. - * - * @return status of the job - */ - public Status getStatus() { - return Status.from(status); + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getCompletedAt() { + return GitHubClient.parseInstant(completedAt); } /** @@ -119,21 +193,12 @@ public Conclusion getConclusion() { } /** - * The run id. - * - * @return the run id - */ - public long getRunId() { - return runId; - } - - /** - * Attempt number of the associated workflow run, 1 for first attempt and higher if the workflow was re-run. + * Gets the HEAD SHA. * - * @return attempt number + * @return sha for the HEAD commit */ - public int getRunAttempt() { - return runAttempt; + public String getHeadSha() { + return headSha; } /** @@ -146,48 +211,49 @@ public URL getHtmlUrl() { } /** - * The check run URL. + * Gets the labels of the job. * - * @return the check run url + * @return the labels */ - public URL getCheckRunUrl() { - return GitHubClient.parseURL(checkRunUrl); + public List getLabels() { + return Collections.unmodifiableList(labels); } /** - * Gets the execution steps of this job. + * The name of the job. * - * @return the execution steps + * @return the name */ - public List getSteps() { - return Collections.unmodifiableList(steps); + public String getName() { + return name; } /** - * Gets the labels of the job. + * Repository to which the job belongs. * - * @return the labels + * @return the repository */ - public List getLabels() { - return Collections.unmodifiableList(labels); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHRepository getRepository() { + return owner; } /** - * the runner id. + * Attempt number of the associated workflow run, 1 for first attempt and higher if the workflow was re-run. * - * @return runnerId + * @return attempt number */ - public int getRunnerId() { - return runnerId; + public int getRunAttempt() { + return runAttempt; } /** - * the runner name. + * The run id. * - * @return runnerName + * @return the run id */ - public String getRunnerName() { - return runnerName; + public long getRunId() { + return runId; } /** @@ -209,32 +275,51 @@ public String getRunnerGroupName() { } /** - * Repository to which the job belongs. + * the runner id. * - * @return the repository + * @return runnerId */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHRepository getRepository() { - return owner; + public int getRunnerId() { + return runnerId; } /** - * Downloads the logs. + * the runner name. + * + * @return runnerName + */ + public String getRunnerName() { + return runnerName; + } + + /** + * When was this job started?. + * + * @return start date + */ + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getStartedAt() { + return GitHubClient.parseInstant(startedAt); + } + + /** + * Gets status of the job. *

- * The logs are returned as a text file. + * Can be {@code UNKNOWN} if the value returned by GitHub is unknown from the API. * - * @param - * the type of result - * @param streamFunction - * The {@link InputStreamFunction} that will process the stream - * @return the result of reading the stream. - * @throws IOException - * The IO exception. + * @return status of the job */ - public T downloadLogs(InputStreamFunction streamFunction) throws IOException { - requireNonNull(streamFunction, "Stream function must not be null"); + public Status getStatus() { + return Status.from(status); + } - return root().createRequest().method("GET").withUrlPath(getApiRoute(), "logs").fetchStream(streamFunction); + /** + * Gets the execution steps of this job. + * + * @return the execution steps + */ + public List getSteps() { + return Collections.unmodifiableList(steps); } private String getApiRoute() { @@ -258,83 +343,4 @@ GHWorkflowJob wrapUp(GHRepository owner) { this.owner = owner; return this; } - - /** - * The Class Step. - */ - public static class Step { - - /** - * Create default Step instance - */ - public Step() { - } - - private String name; - private int number; - - private String startedAt; - private String completedAt; - - private String status; - private String conclusion; - - /** - * Gets the name of the step. - * - * @return name - */ - public String getName() { - return name; - } - - /** - * Gets the sequential number of the step. - * - * @return number - */ - public int getNumber() { - return number; - } - - /** - * When was this step started?. - * - * @return start date - */ - public Date getStartedAt() { - return GitHubClient.parseDate(startedAt); - } - - /** - * When was this step completed?. - * - * @return completion date - */ - public Date getCompletedAt() { - return GitHubClient.parseDate(completedAt); - } - - /** - * Gets status of the step. - *

- * Can be {@code UNKNOWN} if the value returned by GitHub is unknown from the API. - * - * @return status of the step - */ - public Status getStatus() { - return Status.from(status); - } - - /** - * Gets the conclusion of the step. - *

- * Can be {@code UNKNOWN} if the value returned by GitHub is unknown from the API. - * - * @return conclusion of the step - */ - public Conclusion getConclusion() { - return Conclusion.from(conclusion); - } - } } diff --git a/src/main/java/org/kohsuke/github/GHWorkflowJobQueryBuilder.java b/src/main/java/org/kohsuke/github/GHWorkflowJobQueryBuilder.java index f9ff3a1e3e..9f011e9612 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowJobQueryBuilder.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowJobQueryBuilder.java @@ -22,22 +22,22 @@ public class GHWorkflowJobQueryBuilder extends GHQueryBuilder { } /** - * Apply a filter to only return the jobs of the most recent execution of the workflow run. + * Apply a filter to return jobs from all executions of this workflow run. * - * @return the workflow run job query builder + * @return the workflow run job run query builder */ - public GHWorkflowJobQueryBuilder latest() { - req.with("filter", "latest"); + public GHWorkflowJobQueryBuilder all() { + req.with("filter", "all"); return this; } /** - * Apply a filter to return jobs from all executions of this workflow run. + * Apply a filter to only return the jobs of the most recent execution of the workflow run. * - * @return the workflow run job run query builder + * @return the workflow run job query builder */ - public GHWorkflowJobQueryBuilder all() { - req.with("filter", "all"); + public GHWorkflowJobQueryBuilder latest() { + req.with("filter", "latest"); return this; } diff --git a/src/main/java/org/kohsuke/github/GHWorkflowJobsPage.java b/src/main/java/org/kohsuke/github/GHWorkflowJobsPage.java index 91d7013f70..8d4a7ca772 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowJobsPage.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowJobsPage.java @@ -9,8 +9,8 @@ @SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, justification = "JSON API") class GHWorkflowJobsPage { - private int total_count; private GHWorkflowJob[] jobs; + private int totalCount; /** * Gets the total count. @@ -18,7 +18,7 @@ class GHWorkflowJobsPage { * @return the total count */ public int getTotalCount() { - return total_count; + return totalCount; } /** diff --git a/src/main/java/org/kohsuke/github/GHWorkflowRun.java b/src/main/java/org/kohsuke/github/GHWorkflowRun.java index 04a431abbd..2489158626 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowRun.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowRun.java @@ -1,6 +1,7 @@ package org.kohsuke.github; import com.fasterxml.jackson.annotation.JsonProperty; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.commons.lang3.StringUtils; import org.kohsuke.github.function.InputStreamFunction; @@ -8,6 +9,7 @@ import java.io.IOException; import java.net.URL; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.Date; @@ -27,132 +29,321 @@ public class GHWorkflowRun extends GHObject { /** - * Create default GHWorkflowRun instance + * The Enum Conclusion. */ - public GHWorkflowRun() { + public static enum Conclusion { + + /** The action required. */ + ACTION_REQUIRED, + /** The cancelled. */ + CANCELLED, + /** The failure. */ + FAILURE, + /** The neutral. */ + NEUTRAL, + /** The skipped. */ + SKIPPED, + /** The stale. */ + STALE, + /** Start up fail */ + STARTUP_FAILURE, + /** The success. */ + SUCCESS, + /** The timed out. */ + TIMED_OUT, + /** The unknown. */ + UNKNOWN; + + /** + * From. + * + * @param value + * the value + * @return the conclusion + */ + public static Conclusion from(String value) { + return EnumUtils.getNullableEnumOrDefault(Conclusion.class, value, Conclusion.UNKNOWN); + } + + /** + * To string. + * + * @return the string + */ + @Override + public String toString() { + return name().toLowerCase(Locale.ROOT); + } } - @JsonProperty("repository") - private GHRepository owner; + /** + * The Class HeadCommit. + */ + public static class HeadCommit extends GitHubBridgeAdapterObject { - private String name; - private String displayTitle; - private long runNumber; - private long workflowId; + private GitUser author; - private long runAttempt; - private String runStartedAt; - private GHUser triggeringActor; + private GitUser committer; + private String id; + private String message; + private String timestamp; + private String treeId; + /** + * Create default HeadCommit instance + */ + public HeadCommit() { + } - private String htmlUrl; - private String jobsUrl; - private String logsUrl; - private String checkSuiteUrl; + /** + * Gets author. + * + * @return the author + */ + public GitUser getAuthor() { + return author; + } + + /** + * Gets committer. + * + * @return the committer + */ + public GitUser getCommitter() { + return committer; + } + + /** + * Gets id of the commit. + * + * @return id of the commit + */ + public String getId() { + return id; + } + + /** + * Gets message. + * + * @return commit message. + */ + public String getMessage() { + return message; + } + + /** + * Gets timestamp of the commit. + * + * @return timestamp of the commit + */ + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getTimestamp() { + return GitHubClient.parseInstant(timestamp); + } + + /** + * Gets id of the tree. + * + * @return id of the tree + */ + public String getTreeId() { + return treeId; + } + } + + /** + * The Enum Status. + */ + public static enum Status { + + /** The action required. */ + ACTION_REQUIRED, + /** The cancelled. */ + CANCELLED, + /** The completed. */ + COMPLETED, + /** The failure. */ + FAILURE, + /** The in progress. */ + IN_PROGRESS, + /** The neutral. */ + NEUTRAL, + /** The pending. */ + PENDING, + /** The queued. */ + QUEUED, + /** The requested. */ + REQUESTED, + /** The skipped. */ + SKIPPED, + /** The stale. */ + STALE, + /** The success. */ + SUCCESS, + /** The timed out. */ + TIMED_OUT, + /** The unknown. */ + UNKNOWN, + /** The waiting. */ + WAITING; + + /** + * From. + * + * @param value + * the value + * @return the status + */ + public static Status from(String value) { + return EnumUtils.getNullableEnumOrDefault(Status.class, value, Status.UNKNOWN); + } + + /** + * To string. + * + * @return the string + */ + @Override + public String toString() { + return name().toLowerCase(Locale.ROOT); + } + } + private GHUser actor; private String artifactsUrl; private String cancelUrl; - private String rerunUrl; - private String workflowUrl; + private String checkSuiteUrl; + private String conclusion; + + private String displayTitle; + private String event; private String headBranch; - private String headSha; - private GHRepository headRepository; private HeadCommit headCommit; + private GHRepository headRepository; + private String headSha; + private String htmlUrl; + private String jobsUrl; + private String logsUrl; + private String name; - private String event; + @JsonProperty("repository") + private GHRepository owner; + private GHPullRequest[] pullRequests; + private String rerunUrl; + private long runAttempt; + + private long runNumber; + private String runStartedAt; private String status; - private String conclusion; - private GHPullRequest[] pullRequests; + private GHUser triggeringActor; + + private long workflowId; + + private String workflowUrl; /** - * The name of the workflow run. - * - * @return the name + * Create default GHWorkflowRun instance */ - public String getName() { - return name; + public GHWorkflowRun() { } /** - * The display title of the workflow run. + * Approve the workflow run. * - * @return the displayTitle + * @throws IOException + * the io exception */ - public String getDisplayTitle() { - return displayTitle; + public void approve() throws IOException { + root().createRequest().method("POST").withUrlPath(getApiRoute(), "approve").send(); } /** - * The run number. + * Cancel the workflow run. * - * @return the run number + * @throws IOException + * the io exception */ - public long getRunNumber() { - return runNumber; + public void cancel() throws IOException { + root().createRequest().method("POST").withUrlPath(getApiRoute(), "cancel").send(); } /** - * The workflow id. + * Delete the workflow run. * - * @return the workflow id + * @throws IOException + * the io exception */ - public long getWorkflowId() { - return workflowId; + public void delete() throws IOException { + root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); } /** - * The run attempt. + * Delete the logs. * - * @return the run attempt + * @throws IOException + * the io exception */ - public long getRunAttempt() { - return runAttempt; + public void deleteLogs() throws IOException { + root().createRequest().method("DELETE").withUrlPath(getApiRoute(), "logs").send(); } /** - * When was this run triggered?. + * Downloads the logs. + *

+ * The logs are in the form of a zip archive. + *

+ * Note that the archive is the same as the one downloaded from a workflow run so it contains the logs for all jobs. * - * @return run triggered + * @param + * the type of result + * @param streamFunction + * The {@link InputStreamFunction} that will process the stream + * @return the result of reading the stream. + * @throws IOException + * The IO exception. */ - public Date getRunStartedAt() { - return GitHubClient.parseDate(runStartedAt); + public T downloadLogs(InputStreamFunction streamFunction) throws IOException { + requireNonNull(streamFunction, "Stream function must not be null"); + + return root().createRequest().method("GET").withUrlPath(getApiRoute(), "logs").fetchStream(streamFunction); } /** - * The actor which triggered the run. + * Force-cancel the workflow run. * - * @return the triggering actor + * @throws IOException + * the io exception */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHUser getTriggeringActor() { - return triggeringActor; + public void forceCancel() throws IOException { + root().createRequest().method("POST").withUrlPath(getApiRoute(), "force-cancel").send(); } /** - * Gets the html url. + * The actor which triggered the initial run. * - * @return the html url + * @return the triggering actor */ - public URL getHtmlUrl() { - return GitHubClient.parseURL(htmlUrl); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHUser getActor() { + return actor; } /** - * The jobs URL, like https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/jobs + * The artifacts URL, like https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/artifacts * - * @return the jobs url + * @return the artifacts url */ - public URL getJobsUrl() { - return GitHubClient.parseURL(jobsUrl); + public URL getArtifactsUrl() { + return GitHubClient.parseURL(artifactsUrl); } /** - * The logs URL, like https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/logs + * The cancel URL, like https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/cancel * - * @return the logs url + * @return the cancel url */ - public URL getLogsUrl() { - return GitHubClient.parseURL(logsUrl); + public URL getCancelUrl() { + return GitHubClient.parseURL(cancelUrl); } /** @@ -165,39 +356,32 @@ public URL getCheckSuiteUrl() { } /** - * The artifacts URL, like https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/artifacts - * - * @return the artifacts url - */ - public URL getArtifactsUrl() { - return GitHubClient.parseURL(artifactsUrl); - } - - /** - * The cancel URL, like https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/cancel + * Gets the conclusion of the workflow run. + *

+ * Can be {@code UNKNOWN} if the value returned by GitHub is unknown from the API. * - * @return the cancel url + * @return conclusion of the workflow run */ - public URL getCancelUrl() { - return GitHubClient.parseURL(cancelUrl); + public Conclusion getConclusion() { + return Conclusion.from(conclusion); } /** - * The rerun URL, like https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/rerun + * The display title of the workflow run. * - * @return the rerun url + * @return the displayTitle */ - public URL getRerunUrl() { - return GitHubClient.parseURL(rerunUrl); + public String getDisplayTitle() { + return displayTitle; } /** - * The workflow URL, like https://api.github.com/repos/octo-org/octo-repo/actions/workflows/159038 + * The type of event that triggered the build. * - * @return the workflow url + * @return type of event */ - public URL getWorkflowUrl() { - return GitHubClient.parseURL(workflowUrl); + public GHEvent getEvent() { + return EnumUtils.getNullableEnumOrDefault(GHEvent.class, event, GHEvent.UNKNOWN); } /** @@ -209,15 +393,6 @@ public String getHeadBranch() { return headBranch; } - /** - * Gets the HEAD SHA. - * - * @return sha for the HEAD commit - */ - public String getHeadSha() { - return headSha; - } - /** * The commit of current head. * @@ -238,44 +413,48 @@ public GHRepository getHeadRepository() { } /** - * The type of event that triggered the build. + * Gets the HEAD SHA. * - * @return type of event + * @return sha for the HEAD commit */ - public GHEvent getEvent() { - return EnumUtils.getNullableEnumOrDefault(GHEvent.class, event, GHEvent.UNKNOWN); + public String getHeadSha() { + return headSha; } /** - * Gets status of the workflow run. - *

- * Can be {@code UNKNOWN} if the value returned by GitHub is unknown from the API. + * Gets the html url. * - * @return status of the workflow run + * @return the html url */ - public Status getStatus() { - return Status.from(status); + public URL getHtmlUrl() { + return GitHubClient.parseURL(htmlUrl); } /** - * Gets the conclusion of the workflow run. - *

- * Can be {@code UNKNOWN} if the value returned by GitHub is unknown from the API. + * The jobs URL, like https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/jobs * - * @return conclusion of the workflow run + * @return the jobs url */ - public Conclusion getConclusion() { - return Conclusion.from(conclusion); + public URL getJobsUrl() { + return GitHubClient.parseURL(jobsUrl); } /** - * Repository to which the workflow run belongs. + * The logs URL, like https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/logs * - * @return the repository + * @return the logs url */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHRepository getRepository() { - return owner; + public URL getLogsUrl() { + return GitHubClient.parseURL(logsUrl); + } + + /** + * The name of the workflow run. + * + * @return the name + */ + public String getName() { + return name; } /** @@ -300,43 +479,98 @@ public List getPullRequests() throws IOException { } /** - * Cancel the workflow run. + * Repository to which the workflow run belongs. + * + * @return the repository + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHRepository getRepository() { + return owner; + } + + /** + * The rerun URL, like https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/rerun + * + * @return the rerun url + */ + public URL getRerunUrl() { + return GitHubClient.parseURL(rerunUrl); + } + + /** + * The run attempt. + * + * @return the run attempt + */ + public long getRunAttempt() { + return runAttempt; + } + + /** + * The run number. + * + * @return the run number + */ + public long getRunNumber() { + return runNumber; + } + + /** + * When was this run triggered?. + * + * @return run triggered + */ + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getRunStartedAt() { + return GitHubClient.parseInstant(runStartedAt); + } + + /** + * Gets status of the workflow run. + *

+ * Can be {@code UNKNOWN} if the value returned by GitHub is unknown from the API. + * + * @return status of the workflow run + */ + public Status getStatus() { + return Status.from(status); + } + + /** + * The actor which triggered the run. * - * @throws IOException - * the io exception + * @return the triggering actor */ - public void cancel() throws IOException { - root().createRequest().method("POST").withUrlPath(getApiRoute(), "cancel").send(); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHUser getTriggeringActor() { + return triggeringActor; } /** - * Delete the workflow run. + * The workflow id. * - * @throws IOException - * the io exception + * @return the workflow id */ - public void delete() throws IOException { - root().createRequest().method("DELETE").withUrlPath(getApiRoute()).send(); + public long getWorkflowId() { + return workflowId; } /** - * Rerun the workflow run. + * The workflow URL, like https://api.github.com/repos/octo-org/octo-repo/actions/workflows/159038 * - * @throws IOException - * the io exception + * @return the workflow url */ - public void rerun() throws IOException { - root().createRequest().method("POST").withUrlPath(getApiRoute(), "rerun").send(); + public URL getWorkflowUrl() { + return GitHubClient.parseURL(workflowUrl); } /** - * Approve the workflow run. + * Returns the list of jobs from all the executions of this workflow run. * - * @throws IOException - * the io exception + * @return list of jobs from all the executions */ - public void approve() throws IOException { - root().createRequest().method("POST").withUrlPath(getApiRoute(), "approve").send(); + public PagedIterable listAllJobs() { + return new GHWorkflowJobQueryBuilder(this).all().list(); } /** @@ -349,52 +583,56 @@ public PagedIterable listArtifacts() { } /** - * Downloads the logs. - *

- * The logs are in the form of a zip archive. - *

- * Note that the archive is the same as the one downloaded from a workflow run so it contains the logs for all jobs. + * Returns the list of jobs of this workflow run for the last execution. * - * @param - * the type of result - * @param streamFunction - * The {@link InputStreamFunction} that will process the stream - * @return the result of reading the stream. - * @throws IOException - * The IO exception. + * @return list of jobs from the last execution */ - public T downloadLogs(InputStreamFunction streamFunction) throws IOException { - requireNonNull(streamFunction, "Stream function must not be null"); + public PagedIterable listJobs() { + return new GHWorkflowJobQueryBuilder(this).latest().list(); + } - return root().createRequest().method("GET").withUrlPath(getApiRoute(), "logs").fetchStream(streamFunction); + /** + * Rerun the workflow run. + * + * @throws IOException + * the io exception + */ + public void rerun() throws IOException { + rerun(false); } /** - * Delete the logs. + * Rerun the workflow run. * + * @param enableDebugLogging + * whether to enable debug logging for the rerun * @throws IOException * the io exception */ - public void deleteLogs() throws IOException { - root().createRequest().method("DELETE").withUrlPath(getApiRoute(), "logs").send(); + public void rerun(boolean enableDebugLogging) throws IOException { + rerun("rerun", enableDebugLogging); } /** - * Returns the list of jobs of this workflow run for the last execution. + * Rerun failed jobs and their dependent jobs for this workflow run. * - * @return list of jobs from the last execution + * @throws IOException + * the io exception */ - public PagedIterable listJobs() { - return new GHWorkflowJobQueryBuilder(this).latest().list(); + public void rerunFailedJobs() throws IOException { + rerunFailedJobs(false); } /** - * Returns the list of jobs from all the executions of this workflow run. + * Rerun failed jobs and their dependent jobs for this workflow run. * - * @return list of jobs from all the executions + * @param enableDebugLogging + * whether to enable debug logging for the rerun + * @throws IOException + * the io exception */ - public PagedIterable listAllJobs() { - return new GHWorkflowJobQueryBuilder(this).all().list(); + public void rerunFailedJobs(boolean enableDebugLogging) throws IOException { + rerun("rerun-failed-jobs", enableDebugLogging); } private String getApiRoute() { @@ -407,6 +645,14 @@ private String getApiRoute() { return "/repos/" + owner.getOwnerName() + "/" + owner.getName() + "/actions/runs/" + getId(); } + private void rerun(String endpoint, boolean enableDebugLogging) throws IOException { + Requester requester = root().createRequest().method("POST").withUrlPath(getApiRoute(), endpoint); + if (enableDebugLogging) { + requester.with("enable_debug_logging", true); + } + requester.send(); + } + /** * Wrap up. * @@ -436,183 +682,4 @@ GHWorkflowRun wrapUp(GitHub root) { } return this; } - - /** - * The Class HeadCommit. - */ - public static class HeadCommit { - - /** - * Create default HeadCommit instance - */ - public HeadCommit() { - } - - private String id; - private String treeId; - private String message; - private String timestamp; - private GitUser author; - private GitUser committer; - - /** - * Gets id of the commit. - * - * @return id of the commit - */ - public String getId() { - return id; - } - - /** - * Gets id of the tree. - * - * @return id of the tree - */ - public String getTreeId() { - return treeId; - } - - /** - * Gets message. - * - * @return commit message. - */ - public String getMessage() { - return message; - } - - /** - * Gets timestamp of the commit. - * - * @return timestamp of the commit - */ - public Date getTimestamp() { - return GitHubClient.parseDate(timestamp); - } - - /** - * Gets author. - * - * @return the author - */ - public GitUser getAuthor() { - return author; - } - - /** - * Gets committer. - * - * @return the committer - */ - public GitUser getCommitter() { - return committer; - } - } - - /** - * The Enum Status. - */ - public static enum Status { - - /** The queued. */ - QUEUED, - /** The in progress. */ - IN_PROGRESS, - /** The completed. */ - COMPLETED, - /** The action required. */ - ACTION_REQUIRED, - /** The cancelled. */ - CANCELLED, - /** The failure. */ - FAILURE, - /** The neutral. */ - NEUTRAL, - /** The skipped. */ - SKIPPED, - /** The stale. */ - STALE, - /** The success. */ - SUCCESS, - /** The timed out. */ - TIMED_OUT, - /** The requested. */ - REQUESTED, - /** The waiting. */ - WAITING, - /** The pending. */ - PENDING, - /** The unknown. */ - UNKNOWN; - - /** - * From. - * - * @param value - * the value - * @return the status - */ - public static Status from(String value) { - return EnumUtils.getNullableEnumOrDefault(Status.class, value, Status.UNKNOWN); - } - - /** - * To string. - * - * @return the string - */ - @Override - public String toString() { - return name().toLowerCase(Locale.ROOT); - } - } - - /** - * The Enum Conclusion. - */ - public static enum Conclusion { - - /** The action required. */ - ACTION_REQUIRED, - /** The cancelled. */ - CANCELLED, - /** The failure. */ - FAILURE, - /** The neutral. */ - NEUTRAL, - /** The success. */ - SUCCESS, - /** The skipped. */ - SKIPPED, - /** The stale. */ - STALE, - /** The timed out. */ - TIMED_OUT, - /** Start up fail */ - STARTUP_FAILURE, - /** The unknown. */ - UNKNOWN; - - /** - * From. - * - * @param value - * the value - * @return the conclusion - */ - public static Conclusion from(String value) { - return EnumUtils.getNullableEnumOrDefault(Conclusion.class, value, Conclusion.UNKNOWN); - } - - /** - * To string. - * - * @return the string - */ - @Override - public String toString() { - return name().toLowerCase(Locale.ROOT); - } - } } diff --git a/src/main/java/org/kohsuke/github/GHWorkflowRunQueryBuilder.java b/src/main/java/org/kohsuke/github/GHWorkflowRunQueryBuilder.java index b5575abcdc..105dd77a84 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowRunQueryBuilder.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowRunQueryBuilder.java @@ -31,8 +31,8 @@ public class GHWorkflowRunQueryBuilder extends GHQueryBuilder { * the actor * @return the gh workflow run query builder */ - public GHWorkflowRunQueryBuilder actor(String actor) { - req.with("actor", actor); + public GHWorkflowRunQueryBuilder actor(GHUser actor) { + req.with("actor", actor.getLogin()); return this; } @@ -43,8 +43,8 @@ public GHWorkflowRunQueryBuilder actor(String actor) { * the actor * @return the gh workflow run query builder */ - public GHWorkflowRunQueryBuilder actor(GHUser actor) { - req.with("actor", actor.getLogin()); + public GHWorkflowRunQueryBuilder actor(String actor) { + req.with("actor", actor); return this; } @@ -60,42 +60,6 @@ public GHWorkflowRunQueryBuilder branch(String branch) { return this; } - /** - * Event workflow run query builder. - * - * @param event - * the event - * @return the gh workflow run query builder - */ - public GHWorkflowRunQueryBuilder event(GHEvent event) { - req.with("event", event.symbol()); - return this; - } - - /** - * Event workflow run query builder. - * - * @param event - * the event - * @return the gh workflow run query builder - */ - public GHWorkflowRunQueryBuilder event(String event) { - req.with("event", event); - return this; - } - - /** - * Status workflow run query builder. - * - * @param status - * the status - * @return the gh workflow run query builder - */ - public GHWorkflowRunQueryBuilder status(Status status) { - req.with("status", status.toString()); - return this; - } - /** * Conclusion workflow run query builder. *

@@ -126,6 +90,30 @@ public GHWorkflowRunQueryBuilder created(String created) { return this; } + /** + * Event workflow run query builder. + * + * @param event + * the event + * @return the gh workflow run query builder + */ + public GHWorkflowRunQueryBuilder event(GHEvent event) { + req.with("event", event.symbol()); + return this; + } + + /** + * Event workflow run query builder. + * + * @param event + * the event + * @return the gh workflow run query builder + */ + public GHWorkflowRunQueryBuilder event(String event) { + req.with("event", event); + return this; + } + /** * Head sha workflow run query builder. * @@ -147,4 +135,16 @@ public GHWorkflowRunQueryBuilder headSha(String headSha) { public PagedIterable list() { return new GHWorkflowRunsIterable(repo, req.withUrlPath(repo.getApiTailUrl("actions/runs"))); } + + /** + * Status workflow run query builder. + * + * @param status + * the status + * @return the gh workflow run query builder + */ + public GHWorkflowRunQueryBuilder status(Status status) { + req.with("status", status.toString()); + return this; + } } diff --git a/src/main/java/org/kohsuke/github/GHWorkflowsPage.java b/src/main/java/org/kohsuke/github/GHWorkflowsPage.java index 7786d11fab..1fb87a8147 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowsPage.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowsPage.java @@ -9,7 +9,7 @@ @SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, justification = "JSON API") class GHWorkflowsPage { - private int total_count; + private int totalCount; private GHWorkflow[] workflows; /** @@ -18,7 +18,7 @@ class GHWorkflowsPage { * @return the total count */ public int getTotalCount() { - return total_count; + return totalCount; } /** diff --git a/src/main/java/org/kohsuke/github/GitCommit.java b/src/main/java/org/kohsuke/github/GitCommit.java index 4478edf7cc..cc6619b33f 100644 --- a/src/main/java/org/kohsuke/github/GitCommit.java +++ b/src/main/java/org/kohsuke/github/GitCommit.java @@ -1,7 +1,9 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.time.Instant; import java.util.AbstractList; import java.util.Collections; import java.util.Date; @@ -16,35 +18,17 @@ */ @SuppressFBWarnings(value = { "NP_UNWRITTEN_FIELD", "UWF_UNWRITTEN_FIELD" }, justification = "JSON API") -public class GitCommit { - private GHRepository owner; - private String sha, node_id, url, html_url; - private GitUser author; - private GitUser committer; - - private String message; - - private GHVerification verification; - +public class GitCommit extends GitHubBridgeAdapterObject { /** * The Class Tree. */ static class Tree { - /** The url. */ - String url; - /** The sha. */ String sha; - /** - * Gets the url. - * - * @return the url - */ - public String getUrl() { - return url; - } + /** The url. */ + String url; /** * Gets the sha. @@ -55,12 +39,30 @@ public String getSha() { return sha; } + /** + * Gets the url. + * + * @return the url + */ + public String getUrl() { + return url; + } + } + private GitUser author; + private GitUser committer; + private String message; - private Tree tree; + private GHRepository owner; private List parents; + private String sha, nodeId, url, htmlUrl; + + private Tree tree; + + private GHVerification verification; + /** * Instantiates a new git commit. */ @@ -79,9 +81,9 @@ public GitCommit() { // to GHCommit, for testing purposes this.owner = commit.getOwner(); this.sha = commit.getSha(); - this.node_id = commit.getNodeId(); + this.nodeId = commit.getNodeId(); this.url = commit.getUrl(); - this.html_url = commit.getHtmlUrl(); + this.htmlUrl = commit.getHtmlUrl(); this.author = commit.getAuthor(); this.committer = commit.getCommitter(); this.message = commit.getMessage(); @@ -91,49 +93,41 @@ public GitCommit() { } /** - * Gets owner. - * - * @return the repository that contains the commit. - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") - public GHRepository getOwner() { - return owner; - } - - /** - * Gets SHA1. + * Gets author. * - * @return The SHA1 of this commit + * @return the author */ - public String getSHA1() { - return sha; + public GitUser getAuthor() { + return author; } /** - * Gets SHA. + * Gets authored date. * - * @return The SHA of this commit + * @return the authored date */ - public String getSha() { - return sha; + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getAuthoredDate() { + return author.getDate(); } /** - * Gets node id. + * Gets commit date. * - * @return The node id of this commit + * @return the commit date */ - public String getNodeId() { - return node_id; + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getCommitDate() { + return committer.getDate(); } /** - * Gets URL. + * Gets committer. * - * @return The URL of this commit + * @return the committer */ - public String getUrl() { - return url; + public GitUser getCommitter() { + return committer; } /** @@ -142,70 +136,74 @@ public String getUrl() { * @return The HTML URL of this commit */ public String getHtmlUrl() { - return html_url; + return htmlUrl; } /** - * Gets author. + * Gets message. * - * @return the author + * @return Commit message. */ - public GitUser getAuthor() { - return author; + public String getMessage() { + return message; } /** - * Gets authored date. + * Gets node id. * - * @return the authored date + * @return The node id of this commit */ - public Date getAuthoredDate() { - return author.getDate(); + public String getNodeId() { + return nodeId; } /** - * Gets committer. + * Gets owner. * - * @return the committer + * @return the repository that contains the commit. */ - public GitUser getCommitter() { - return committer; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") + public GHRepository getOwner() { + return owner; } /** - * Gets commit date. + * Gets the parent SHA 1 s. * - * @return the commit date + * @return the parent SHA 1 s */ - public Date getCommitDate() { - return committer.getDate(); - } + public List getParentSHA1s() { + if (parents == null || parents.size() == 0) + return Collections.emptyList(); + return new AbstractList() { + @Override + public String get(int index) { + return parents.get(index).sha; + } - /** - * Gets message. - * - * @return Commit message. - */ - public String getMessage() { - return message; + @Override + public int size() { + return parents.size(); + } + }; } /** - * Gets Verification Status. + * Gets SHA1. * - * @return the Verification status + * @return The SHA1 of this commit */ - public GHVerification getVerification() { - return verification; + public String getSHA1() { + return sha; } /** - * Gets the tree. + * Gets SHA. * - * @return the tree + * @return The SHA of this commit */ - Tree getTree() { - return tree; + public String getSha() { + return sha; } /** @@ -226,6 +224,24 @@ public String getTreeUrl() { return tree.getUrl(); } + /** + * Gets URL. + * + * @return The URL of this commit + */ + public String getUrl() { + return url; + } + + /** + * Gets Verification Status. + * + * @return the Verification status + */ + public GHVerification getVerification() { + return verification; + } + /** * Gets the parents. * @@ -237,24 +253,21 @@ List getParents() { } /** - * Gets the parent SHA 1 s. + * Gets the tree. * - * @return the parent SHA 1 s + * @return the tree */ - public List getParentSHA1s() { - if (parents == null || parents.size() == 0) - return Collections.emptyList(); - return new AbstractList() { - @Override - public String get(int index) { - return parents.get(index).sha; - } + Tree getTree() { + return tree; + } - @Override - public int size() { - return parents.size(); - } - }; + /** + * For test purposes only. + * + * @return Equivalent GHCommit + */ + GHCommit toGHCommit() { + return new GHCommit(new GHCommit.ShortInfo(this)); } /** @@ -269,13 +282,4 @@ GitCommit wrapUp(GHRepository owner) { return this; } - /** - * For test purposes only. - * - * @return Equivalent GHCommit - */ - GHCommit toGHCommit() { - return new GHCommit(new GHCommit.ShortInfo(this)); - } - } diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java index 8ef21e2cd0..9204660a92 100644 --- a/src/main/java/org/kohsuke/github/GitHub.java +++ b/src/main/java/org/kohsuke/github/GitHub.java @@ -55,145 +55,14 @@ */ public class GitHub { - @Nonnull - private final GitHubClient client; - - @CheckForNull - private GHMyself myself; - - private final ConcurrentMap users; - private final ConcurrentMap orgs; - - @Nonnull - private final GitHubSanityCachedValue sanityCachedMeta = new GitHubSanityCachedValue<>(); - - /** - * Creates a client API root object. - * - *

- * Several different combinations of the login/oauthAccessToken/password parameters are allowed to represent - * different ways of authentication. - * - *

- *
Log in anonymously - *
Leave all three parameters null and you will be making HTTP requests without any authentication. - * - *
Log in with password - *
Specify the login and password, then leave oauthAccessToken null. This will use the HTTP BASIC auth with the - * GitHub API. - * - *
Log in with OAuth token - *
Specify oauthAccessToken, and optionally specify the login. Leave password null. This will send OAuth token - * to the GitHub API. If the login parameter is null, The constructor makes an API call to figure out the user name - * that owns the token. - * - *
Log in with JWT token - *
Specify jwtToken. Leave password null. This will send JWT token to the GitHub API via the Authorization HTTP - * header. Please note that only operations in which permissions have been previously configured and accepted during - * the GitHub App will be executed successfully. - *
- * - * @param apiUrl - * The URL of GitHub (or GitHub enterprise) API endpoint, such as "https://api.github.com" or - * "http://ghe.acme.com/api/v3". Note that GitHub Enterprise has /api/v3 in the URL. For - * historical reasons, this parameter still accepts the bare domain name, but that's considered - * deprecated. - * @param connector - * a connector - * @param rateLimitHandler - * rateLimitHandler - * @param abuseLimitHandler - * abuseLimitHandler - * @param rateLimitChecker - * rateLimitChecker - * @param authorizationProvider - * a authorization provider - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @SuppressFBWarnings(value = { "CT_CONSTRUCTOR_THROW" }, justification = "internal constructor") - GitHub(String apiUrl, - GitHubConnector connector, - GitHubRateLimitHandler rateLimitHandler, - GitHubAbuseLimitHandler abuseLimitHandler, - GitHubRateLimitChecker rateLimitChecker, - AuthorizationProvider authorizationProvider) throws IOException { - if (authorizationProvider instanceof DependentAuthorizationProvider) { - ((DependentAuthorizationProvider) authorizationProvider).bind(this); - } else if (authorizationProvider instanceof ImmutableAuthorizationProvider - && authorizationProvider instanceof UserAuthorizationProvider) { - UserAuthorizationProvider provider = (UserAuthorizationProvider) authorizationProvider; - if (provider.getLogin() == null && provider.getEncodedAuthorization() != null - && provider.getEncodedAuthorization().startsWith("token")) { - authorizationProvider = new LoginLoadingUserAuthorizationProvider(provider, this); - } - } - - users = new ConcurrentHashMap<>(); - orgs = new ConcurrentHashMap<>(); - - this.client = new GitHubClient(apiUrl, - connector, - rateLimitHandler, - abuseLimitHandler, - rateLimitChecker, - authorizationProvider); - - // Ensure we have the login if it is available - // This preserves previously existing behavior. Consider removing in future. - if (authorizationProvider instanceof LoginLoadingUserAuthorizationProvider) { - ((LoginLoadingUserAuthorizationProvider) authorizationProvider).getLogin(); - } - } - - private GitHub(GitHubClient client) { - users = new ConcurrentHashMap<>(); - orgs = new ConcurrentHashMap<>(); - this.client = client; - } - - private static class LoginLoadingUserAuthorizationProvider implements UserAuthorizationProvider { - private final GitHub gitHub; - private final AuthorizationProvider authorizationProvider; - private boolean loginLoaded = false; - private String login; - - LoginLoadingUserAuthorizationProvider(AuthorizationProvider authorizationProvider, GitHub gitHub) { - this.gitHub = gitHub; - this.authorizationProvider = authorizationProvider; - } - - @Override - public String getEncodedAuthorization() throws IOException { - return authorizationProvider.getEncodedAuthorization(); - } - - @Override - public String getLogin() { - synchronized (this) { - if (!loginLoaded) { - loginLoaded = true; - try { - GHMyself u = gitHub.setMyself(); - if (u != null) { - login = u.getLogin(); - } - } catch (IOException e) { - } - } - return login; - } - } - } - /** * The Class DependentAuthorizationProvider. */ public static abstract class DependentAuthorizationProvider implements AuthorizationProvider { + private final AuthorizationProvider authorizationProvider; private GitHub baseGitHub; private GitHub gitHub; - private final AuthorizationProvider authorizationProvider; /** * An AuthorizationProvider that requires an authenticated GitHub instance to provide its authorization. @@ -206,6 +75,18 @@ protected DependentAuthorizationProvider(AuthorizationProvider authorizationProv this.authorizationProvider = authorizationProvider; } + /** + * Git hub. + * + * @return the git hub + */ + protected synchronized final GitHub gitHub() { + if (gitHub == null) { + gitHub = new GitHub.AuthorizationRefreshGitHubWrapper(this.baseGitHub, authorizationProvider); + } + return gitHub; + } + /** * Binds this authorization provider to a github instance. * @@ -220,18 +101,6 @@ synchronized void bind(GitHub github) { } this.baseGitHub = github; } - - /** - * Git hub. - * - * @return the git hub - */ - protected synchronized final GitHub gitHub() { - if (gitHub == null) { - gitHub = new GitHub.AuthorizationRefreshGitHubWrapper(this.baseGitHub, authorizationProvider); - } - return gitHub; - } } private static class AuthorizationRefreshGitHubWrapper extends GitHub { @@ -261,6 +130,41 @@ Requester createRequest() { } } + private static class LoginLoadingUserAuthorizationProvider implements UserAuthorizationProvider { + private final AuthorizationProvider authorizationProvider; + private final GitHub gitHub; + private String login; + private boolean loginLoaded = false; + + LoginLoadingUserAuthorizationProvider(AuthorizationProvider authorizationProvider, GitHub gitHub) { + this.gitHub = gitHub; + this.authorizationProvider = authorizationProvider; + } + + @Override + public String getEncodedAuthorization() throws IOException { + return authorizationProvider.getEncodedAuthorization(); + } + + @Override + public String getLogin() { + synchronized (this) { + if (!loginLoaded) { + loginLoaded = true; + try { + GHMyself u = gitHub.setMyself(); + if (u != null) { + login = u.getLogin(); + } + } catch (IOException e) { + } + } + return login; + } + } + } + private static final Logger LOGGER = Logger.getLogger(GitHub.class.getName()); + /** * Obtains the credential from "~/.github" or from the System Environment Properties. * @@ -273,13 +177,8 @@ public static GitHub connect() throws IOException { } /** - * Version that connects to GitHub Enterprise. + * Connect git hub. * - * @param apiUrl - * The URL of GitHub (or GitHub Enterprise) API endpoint, such as "https://api.github.com" or - * "http://ghe.acme.com/api/v3". Note that GitHub Enterprise has /api/v3 in the URL. For - * historical reasons, this parameter still accepts the bare domain name, but that's considered - * deprecated. * @param login * the login * @param oauthAccessToken @@ -288,14 +187,46 @@ public static GitHub connect() throws IOException { * @throws IOException * the io exception */ - public static GitHub connectToEnterpriseWithOAuth(String apiUrl, String login, String oauthAccessToken) - throws IOException { - return new GitHubBuilder().withEndpoint(apiUrl).withOAuthToken(oauthAccessToken, login).build(); + public static GitHub connect(String login, String oauthAccessToken) throws IOException { + return new GitHubBuilder().withOAuthToken(oauthAccessToken, login).build(); } /** - * Connect git hub. + * Connects to GitHub anonymously. + *

+ * All operations that require authentication will fail. + * + * @return the git hub + * @throws IOException + * the io exception + */ + public static GitHub connectAnonymously() throws IOException { + return new GitHubBuilder().build(); + } + + /** + * Connects to GitHub Enterprise anonymously. + *

+ * All operations that require authentication will fail. + * + * @param apiUrl + * the api url + * @return the git hub + * @throws IOException + * the io exception + */ + public static GitHub connectToEnterpriseAnonymously(String apiUrl) throws IOException { + return new GitHubBuilder().withEndpoint(apiUrl).build(); + } + + /** + * Version that connects to GitHub Enterprise. * + * @param apiUrl + * The URL of GitHub (or GitHub Enterprise) API endpoint, such as "https://api.github.com" or + * "http://ghe.acme.com/api/v3". Note that GitHub Enterprise has /api/v3 in the URL. For + * historical reasons, this parameter still accepts the bare domain name, but that's considered + * deprecated. * @param login * the login * @param oauthAccessToken @@ -304,8 +235,9 @@ public static GitHub connectToEnterpriseWithOAuth(String apiUrl, String login, S * @throws IOException * the io exception */ - public static GitHub connect(String login, String oauthAccessToken) throws IOException { - return new GitHubBuilder().withOAuthToken(oauthAccessToken, login).build(); + public static GitHub connectToEnterpriseWithOAuth(String apiUrl, String login, String oauthAccessToken) + throws IOException { + return new GitHubBuilder().withEndpoint(apiUrl).withOAuthToken(oauthAccessToken, login).build(); } /** @@ -337,31 +269,37 @@ public static GitHub connectUsingOAuth(String githubServer, String oauthAccessTo } /** - * Connects to GitHub anonymously. - *

- * All operations that require authentication will fail. + * Gets an {@link ObjectReader} that can be used to convert JSON into library data objects. * - * @return the git hub - * @throws IOException - * the io exception - */ - public static GitHub connectAnonymously() throws IOException { - return new GitHubBuilder().build(); - } - + * If you must manually create library data objects from JSON, the {@link ObjectReader} returned by this method is + * the only supported way of doing so. + * + * WARNING: Objects generated from this method have limited functionality. They will not throw when being crated + * from valid JSON matching the expected object, but they are not guaranteed to be usable beyond that. Use with + * extreme caution. + * + * @return an {@link ObjectReader} instance that can be further configured. + */ + @Nonnull + public static ObjectReader getMappingObjectReader() { + return GitHubClient.getMappingObjectReader(GitHub.offline()); + } + /** - * Connects to GitHub Enterprise anonymously. - *

- * All operations that require authentication will fail. + * Gets an {@link ObjectWriter} that can be used to convert data objects in this library to JSON. * - * @param apiUrl - * the api url - * @return the git hub - * @throws IOException - * the io exception + * If you must convert data object in this library to JSON, the {@link ObjectWriter} returned by this method is the + * only supported way of doing so. This {@link ObjectWriter} can be used to convert any library data object to JSON + * without throwing an exception. + * + * WARNING: While the JSON generated is generally expected to be stable, it is not part of the API of this library + * and may change without warning. Use with extreme caution. + * + * @return an {@link ObjectWriter} instance that can be further configured. */ - public static GitHub connectToEnterpriseAnonymously(String apiUrl) throws IOException { - return new GitHubBuilder().withEndpoint(apiUrl).build(); + @Nonnull + public static ObjectWriter getMappingObjectWriter() { + return GitHubClient.getMappingObjectWriter(); } /** @@ -381,234 +319,357 @@ public static GitHub offline() { } } - /** - * Is this an anonymous connection. - * - * @return {@code true} if operations that require authentication will fail. - */ - public boolean isAnonymous() { - return client.isAnonymous(); + @Nonnull + private final GitHubClient client; + + @CheckForNull + private GHMyself myself; + + private final ConcurrentMap orgs; + + @Nonnull + private final GitHubSanityCachedValue sanityCachedMeta = new GitHubSanityCachedValue<>(); + + private final ConcurrentMap users; + + private GitHub(GitHubClient client) { + users = new ConcurrentHashMap<>(); + orgs = new ConcurrentHashMap<>(); + this.client = client; } /** - * Is this an always offline "connection". + * Creates a client API root object. * - * @return {@code true} if this is an always offline "connection". + *

+ * Several different combinations of the login/oauthAccessToken/password parameters are allowed to represent + * different ways of authentication. + * + *

+ *
Log in anonymously + *
Leave all three parameters null and you will be making HTTP requests without any authentication. + * + *
Log in with password + *
Specify the login and password, then leave oauthAccessToken null. This will use the HTTP BASIC auth with the + * GitHub API. + * + *
Log in with OAuth token + *
Specify oauthAccessToken, and optionally specify the login. Leave password null. This will send OAuth token + * to the GitHub API. If the login parameter is null, The constructor makes an API call to figure out the user name + * that owns the token. + * + *
Log in with JWT token + *
Specify jwtToken. Leave password null. This will send JWT token to the GitHub API via the Authorization HTTP + * header. Please note that only operations in which permissions have been previously configured and accepted during + * the GitHub App will be executed successfully. + *
+ * + * @param apiUrl + * The URL of GitHub (or GitHub enterprise) API endpoint, such as "https://api.github.com" or + * "http://ghe.acme.com/api/v3". Note that GitHub Enterprise has /api/v3 in the URL. For + * historical reasons, this parameter still accepts the bare domain name, but that's considered + * deprecated. + * @param connector + * a connector + * @param rateLimitHandler + * rateLimitHandler + * @param abuseLimitHandler + * abuseLimitHandler + * @param rateLimitChecker + * rateLimitChecker + * @param authorizationProvider + * a authorization provider + * @throws IOException + * Signals that an I/O exception has occurred. */ - public boolean isOffline() { - return client.isOffline(); + @SuppressFBWarnings(value = { "CT_CONSTRUCTOR_THROW" }, justification = "internal constructor") + GitHub(String apiUrl, + GitHubConnector connector, + GitHubRateLimitHandler rateLimitHandler, + GitHubAbuseLimitHandler abuseLimitHandler, + GitHubRateLimitChecker rateLimitChecker, + AuthorizationProvider authorizationProvider) throws IOException { + if (authorizationProvider instanceof DependentAuthorizationProvider) { + ((DependentAuthorizationProvider) authorizationProvider).bind(this); + } else if (authorizationProvider instanceof ImmutableAuthorizationProvider + && authorizationProvider instanceof UserAuthorizationProvider) { + UserAuthorizationProvider provider = (UserAuthorizationProvider) authorizationProvider; + if (provider.getLogin() == null && provider.getEncodedAuthorization() != null + && provider.getEncodedAuthorization().startsWith("token")) { + authorizationProvider = new LoginLoadingUserAuthorizationProvider(provider, this); + } + } + + users = new ConcurrentHashMap<>(); + orgs = new ConcurrentHashMap<>(); + + this.client = new GitHubClient(apiUrl, + connector, + rateLimitHandler, + abuseLimitHandler, + rateLimitChecker, + authorizationProvider); + + // Ensure we have the login if it is available + // This preserves previously existing behavior. Consider removing in future. + if (authorizationProvider instanceof LoginLoadingUserAuthorizationProvider) { + ((LoginLoadingUserAuthorizationProvider) authorizationProvider).getLogin(); + } } /** - * Gets api url. + * Tests the connection. * - * @return the api url + *

+ * Verify that the API URL and credentials are valid to access this GitHub. + * + *

+ * This method returns normally if the endpoint is reachable and verified to be GitHub API URL. Otherwise this + * method throws {@link IOException} to indicate the problem. + * + * @throws IOException + * the io exception */ - public String getApiUrl() { - return client.getApiUrl(); + public void checkApiUrlValidity() throws IOException { + client.checkApiUrlValidity(); } /** - * Gets the current full rate limit information from the server. - * - * For some versions of GitHub Enterprise, the {@code /rate_limit} endpoint returns a {@code 404 Not Found}. In that - * case, the most recent {@link GHRateLimit} information will be returned, including rate limit information returned - * in the response header for this request in if was present. - * - * For most use cases it would be better to implement a {@link RateLimitChecker} and add it via - * {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}. + * Check auth gh authorization. * - * @return the rate limit + * @param clientId + * the client id + * @param accessToken + * the access token + * @return the gh authorization * @throws IOException * the io exception + * @see Check an + * authorization */ - @Nonnull - public GHRateLimit getRateLimit() throws IOException { - return client.getRateLimit(); + public GHAuthorization checkAuth(@Nonnull String clientId, @Nonnull String accessToken) throws IOException { + return createRequest().withUrlPath("/applications/" + clientId + "/tokens/" + accessToken) + .fetch(GHAuthorization.class); } /** - * Returns the most recently observed rate limit data or {@code null} if either there is no rate limit (for example - * GitHub Enterprise) or if no requests have been made. + * Creates a GitHub App from a manifest. * - * @return the most recently observed rate limit data or {@code null}. - * @deprecated implement a {@link RateLimitChecker} and add it via - * {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}. + * @param code + * temporary code returned during the manifest flow + * @return the app + * @throws IOException + * the IO exception + * @see Get an + * app */ - @Nonnull - @Deprecated - public GHRateLimit lastRateLimit() { - return client.lastRateLimit(); + public GHAppFromManifest createAppFromManifest(@Nonnull String code) throws IOException { + return createRequest().method("POST") + .withUrlPath("/app-manifests/" + code + "/conversions") + .fetch(GHAppFromManifest.class); } /** - * Gets the current rate limit while trying not to actually make any remote requests unless absolutely necessary. + * Create gist gh gist builder. * - * @return the current rate limit data. - * @throws IOException - * if we couldn't get the current rate limit data. - * @deprecated implement a {@link RateLimitChecker} and add it via - * {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}. + * @return the gh gist builder */ - @Nonnull - @Deprecated - public GHRateLimit rateLimit() throws IOException { - return client.rateLimit(RateLimitTarget.CORE); + public GHGistBuilder createGist() { + return new GHGistBuilder(this); } /** - * Gets the {@link GHUser} that represents yourself. + * Create or get auth gh authorization. * - * @return the myself + * @param clientId + * the client id + * @param clientSecret + * the client secret + * @param scopes + * the scopes + * @param note + * the note + * @param noteUrl + * the note url + * @return the gh authorization * @throws IOException * the io exception + * @see docs */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") - public GHMyself getMyself() throws IOException { - client.requireCredential(); - return setMyself(); - } + public GHAuthorization createOrGetAuth(String clientId, + String clientSecret, + List scopes, + String note, + String noteUrl) throws IOException { + Requester requester = createRequest().with("client_secret", clientSecret) + .with("scopes", scopes) + .with("note", note) + .with("note_url", noteUrl); - private GHMyself setMyself() throws IOException { - synchronized (this) { - if (this.myself == null) { - this.myself = createRequest().withUrlPath("/user").fetch(GHMyself.class); - } - return myself; - } + return requester.method("PUT").withUrlPath("/authorizations/clients/" + clientId).fetch(GHAuthorization.class); } /** - * Obtains the object that represents the named user. + * Starts a builder that creates a new repository. * - * @param login - * the login - * @return the user - * @throws IOException - * the io exception + *

+ * You use the returned builder to set various properties, then call {@link GHCreateRepositoryBuilder#create()} to + * finally create a repository. + * + * @param name + * the name + * @return the gh create repository builder */ - public GHUser getUser(String login) throws IOException { - GHUser u = users.get(login); - if (u == null) { - u = createRequest().withUrlPath("/users/" + login).fetch(GHUser.class); - users.put(u.getLogin(), u); - } - return u; + public GHCreateRepositoryBuilder createRepository(String name) { + return new GHCreateRepositoryBuilder(name, this, "/user/repos"); } /** - * clears all cached data in order for external changes (modifications and del) to be reflected. + * Creates a new authorization. + *

+ * The token created can be then used for {@link GitHub#connectUsingOAuth(String)} in the future. + * + * @param scope + * the scope + * @param note + * the note + * @param noteUrl + * the note url + * @return the gh authorization + * @throws IOException + * the io exception + * @see Documentation */ - public void refreshCache() { - users.clear(); - orgs.clear(); + public GHAuthorization createToken(Collection scope, String note, String noteUrl) throws IOException { + Requester requester = createRequest().with("scopes", scope).with("note", note).with("note_url", noteUrl); + + return requester.method("POST").withUrlPath("/authorizations").fetch(GHAuthorization.class); } /** - * Interns the given {@link GHUser}. + * Creates a new authorization using an OTP. + *

+ * Start by running createToken, if exception is thrown, prompt for OTP from user + *

+ * Once OTP is received, call this token request + *

+ * The token created can be then used for {@link GitHub#connectUsingOAuth(String)} in the future. * - * @param orig - * the orig - * @return the user + * @param scope + * the scope + * @param note + * the note + * @param noteUrl + * the note url + * @param OTP + * the otp + * @return the gh authorization + * @throws IOException + * the io exception + * @see Documentation */ - protected GHUser getUser(GHUser orig) { - GHUser u = users.get(orig.getLogin()); - if (u == null) { - users.put(orig.getLogin(), orig); - return orig; + public GHAuthorization createToken(Collection scope, String note, String noteUrl, Supplier OTP) + throws IOException { + try { + return createToken(scope, note, noteUrl); + } catch (GHOTPRequiredException ex) { + String OTPstring = OTP.get(); + Requester requester = createRequest().with("scopes", scope).with("note", note).with("note_url", noteUrl); + // Add the OTP from the user + requester.setHeader("x-github-otp", OTPstring); + return requester.method("POST").withUrlPath("/authorizations").fetch(GHAuthorization.class); } - return u; } /** - * Gets {@link GHOrganization} specified by name. + * Delete auth. * - * @param name - * the name - * @return the organization + * @param id + * the id * @throws IOException * the io exception + * @see Delete an + * authorization */ - public GHOrganization getOrganization(String name) throws IOException { - GHOrganization o = orgs.get(name); - if (o == null) { - o = createRequest().withUrlPath("/orgs/" + name).fetch(GHOrganization.class); - orgs.put(name, o); - } - return o; + public void deleteAuth(long id) throws IOException { + createRequest().method("DELETE").withUrlPath("/authorizations/" + id).send(); } /** - * Gets a list of all organizations. + * Gets api url. * - * @return the paged iterable + * @return the api url */ - public PagedIterable listOrganizations() { - return listOrganizations(null); + public String getApiUrl() { + return client.getApiUrl(); } /** - * Gets a list of all organizations starting after the organization identifier specified by 'since'. + * Returns the GitHub App associated with the authentication credentials used. + *

+ * You must use a JWT to access this endpoint. * - * @param since - * the since - * @return the paged iterable - * @see List All Orgs - Parameters + * @return the app + * @throws IOException + * the io exception + * @see Get the authenticated + * GitHub App */ - public PagedIterable listOrganizations(final String since) { - return createRequest().with("since", since) - .withUrlPath("/organizations") - .toIterable(GHOrganization[].class, null); + public GHApp getApp() throws IOException { + return createRequest().withUrlPath("/app").fetch(GHApp.class); } /** - * Gets the repository object from 'owner/repo' string that GitHub calls as "repository name". + * Returns the GitHub App identified by the given slug * - * @param name - * the name - * @return the repository + * @param slug + * the slug of the application + * @return the app * @throws IOException - * the io exception - * @see GHRepository#getName() GHRepository#getName() + * the IO exception + * @see Get an app */ - public GHRepository getRepository(String name) throws IOException { - String[] tokens = name.split("/"); - if (tokens.length != 2) { - throw new IllegalArgumentException("Repository name must be in format owner/repo"); - } - return GHRepository.read(this, tokens[0], tokens[1]); + public GHApp getApp(@Nonnull String slug) throws IOException { + return createRequest().withUrlPath("/apps/" + slug).fetch(GHApp.class); } /** - * Gets the repository object from its ID. + * Public events visible to you. Equivalent of what's displayed on https://github.com/ * - * @param id - * the id - * @return the repository by id + * @return the events * @throws IOException * the io exception */ - public GHRepository getRepositoryById(long id) throws IOException { - return createRequest().withUrlPath("/repositories/" + id).fetch(GHRepository.class); + public List getEvents() throws IOException { + return createRequest().withUrlPath("/events").toIterable(GHEventInfo[].class, null).toList(); } /** - * Returns a list of popular open source licenses. + * Gets a single gist by ID. * - * @return a list of popular open source licenses - * @see GitHub API - Licenses + * @param id + * the id + * @return the gist + * @throws IOException + * the io exception */ - public PagedIterable listLicenses() { - return createRequest().withUrlPath("/licenses").toIterable(GHLicense[].class, null); + public GHGist getGist(String id) throws IOException { + return createRequest().withUrlPath("/gists/" + id).fetch(GHGist.class); } /** - * Returns a list of all users. + * Returns the GitHub App Installation associated with the authentication credentials used. + *

+ * You must use an installation token to access this endpoint; otherwise consider {@link #getApp()} and its various + * ways of retrieving installations. * - * @return the paged iterable + * @return the app + * @see GitHub App installations */ - public PagedIterable listUsers() { - return createRequest().withUrlPath("/users").toIterable(GHUser[].class, null); + public GHAuthenticatedAppInstallation getInstallation() { + return new GHAuthenticatedAppInstallation(this); } /** @@ -626,18 +687,16 @@ public GHLicense getLicense(String key) throws IOException { } /** - * Returns a list all plans for your Marketplace listing - *

- * GitHub Apps must use a JWT to access this endpoint. - *

- * OAuth Apps must use basic authentication with their client ID and client secret to access this endpoint. + * Provides a list of GitHub's IP addresses. * - * @return the paged iterable - * @see List - * Plans + * @return an instance of {@link GHMeta} + * @throws IOException + * if the credentials supplied are invalid or if you're trying to access it as a GitHub App via the JWT + * authentication + * @see Get Meta */ - public PagedIterable listMarketplacePlans() { - return createRequest().withUrlPath("/marketplace_listing/plans").toIterable(GHMarketplacePlan[].class, null); + public GHMeta getMeta() throws IOException { + return this.sanityCachedMeta.get(() -> createRequest().withUrlPath("/meta").fetch(GHMeta.class)); } /** @@ -653,28 +712,6 @@ public List getMyInvitations() throws IOException { .toList(); } - /** - * This method returns shallowly populated organizations. - *

- * To retrieve full organization details, you need to call {@link #getOrganization(String)} TODO: make this - * automatic. - * - * @return the my organizations - * @throws IOException - * the io exception - */ - public Map getMyOrganizations() throws IOException { - GHOrganization[] orgs = createRequest().withUrlPath("/user/orgs") - .toIterable(GHOrganization[].class, null) - .toArray(); - Map r = new HashMap<>(); - for (GHOrganization o : orgs) { - // don't put 'o' into orgs because they are shallow - r.put(o.getLogin(), o); - } - return r; - } - /** * Returns only active subscriptions. *

@@ -693,36 +730,22 @@ public PagedIterable getMyMarketplacePurchases() { } /** - * Alias for {@link #getUserPublicOrganizations(String)}. - * - * @param user - * the user - * @return the user public organizations - * @throws IOException - * the io exception - */ - public Map getUserPublicOrganizations(GHUser user) throws IOException { - return getUserPublicOrganizations(user.getLogin()); - } - - /** - * This method returns a shallowly populated organizations. + * This method returns shallowly populated organizations. *

- * To retrieve full organization details, you need to call {@link #getOrganization(String)} + * To retrieve full organization details, you need to call {@link #getOrganization(String)} TODO: make this + * automatic. * - * @param login - * the user to retrieve public Organization membership information for - * @return the public Organization memberships for the user + * @return the my organizations * @throws IOException * the io exception */ - public Map getUserPublicOrganizations(String login) throws IOException { - GHOrganization[] orgs = createRequest().withUrlPath("/users/" + login + "/orgs") + public Map getMyOrganizations() throws IOException { + GHOrganization[] orgs = createRequest().withUrlPath("/user/orgs") .toIterable(GHOrganization[].class, null) .toArray(); Map r = new HashMap<>(); for (GHOrganization o : orgs) { - // don't put 'o' into orgs cache because they are shallow records + // don't put 'o' into orgs because they are shallow r.put(o.getLogin(), o); } return r; @@ -755,301 +778,203 @@ public Map> getMyTeams() throws IOException { } /** - * Public events visible to you. Equivalent of what's displayed on https://github.com/ - * - * @return the events - * @throws IOException - * the io exception - */ - public List getEvents() throws IOException { - return createRequest().withUrlPath("/events").toIterable(GHEventInfo[].class, null).toList(); - } - - /** - * List public events for a user - * see - * API documentation - * - * @param login - * the login (user) to look public events for - * @return the events - * @throws IOException - * the io exception - */ - public List getUserPublicEvents(String login) throws IOException { - return createRequest().withUrlPath("/users/" + login + "/events/public") - .toIterable(GHEventInfo[].class, null) - .toList(); - } - - /** - * Gets a single gist by ID. - * - * @param id - * the id - * @return the gist - * @throws IOException - * the io exception - */ - public GHGist getGist(String id) throws IOException { - return createRequest().withUrlPath("/gists/" + id).fetch(GHGist.class); - } - - /** - * Create gist gh gist builder. - * - * @return the gh gist builder - */ - public GHGistBuilder createGist() { - return new GHGistBuilder(this); - } - - /** - * Parses the GitHub event object. - *

- * This is primarily intended for receiving a POST HTTP call from a hook. Unfortunately, hook script payloads aren't - * self-descriptive, so you need to know the type of the payload you are expecting. + * Gets the {@link GHUser} that represents yourself. * - * @param - * the type parameter - * @param r - * the r - * @param type - * the type - * @return the t + * @return the myself * @throws IOException * the io exception */ - public T parseEventPayload(Reader r, Class type) throws IOException { - T t = GitHubClient.getMappingObjectReader(this).forType(type).readValue(r); - t.lateBind(); - return t; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHMyself getMyself() throws IOException { + client.requireCredential(); + return setMyself(); } /** - * Starts a builder that creates a new repository. - * - *

- * You use the returned builder to set various properties, then call {@link GHCreateRepositoryBuilder#create()} to - * finally create a repository. + * Gets {@link GHOrganization} specified by name. * * @param name * the name - * @return the gh create repository builder - */ - public GHCreateRepositoryBuilder createRepository(String name) { - return new GHCreateRepositoryBuilder(name, this, "/user/repos"); - } - - /** - * Creates a new authorization. - *

- * The token created can be then used for {@link GitHub#connectUsingOAuth(String)} in the future. - * - * @param scope - * the scope - * @param note - * the note - * @param noteUrl - * the note url - * @return the gh authorization + * @return the organization * @throws IOException * the io exception - * @see Documentation */ - public GHAuthorization createToken(Collection scope, String note, String noteUrl) throws IOException { - Requester requester = createRequest().with("scopes", scope).with("note", note).with("note_url", noteUrl); - - return requester.method("POST").withUrlPath("/authorizations").fetch(GHAuthorization.class); + public GHOrganization getOrganization(String name) throws IOException { + GHOrganization o = orgs.get(name); + if (o == null) { + o = createRequest().withUrlPath("/orgs/" + name).fetch(GHOrganization.class); + orgs.put(name, o); + } + return o; } - /** - * Creates a new authorization using an OTP. - *

- * Start by running createToken, if exception is thrown, prompt for OTP from user - *

- * Once OTP is received, call this token request - *

- * The token created can be then used for {@link GitHub#connectUsingOAuth(String)} in the future. - * - * @param scope - * the scope - * @param note - * the note - * @param noteUrl - * the note url - * @param OTP - * the otp - * @return the gh authorization + /** + * Gets project. + * + * @param id + * the id + * @return the project * @throws IOException * the io exception - * @see Documentation */ - public GHAuthorization createToken(Collection scope, String note, String noteUrl, Supplier OTP) - throws IOException { - try { - return createToken(scope, note, noteUrl); - } catch (GHOTPRequiredException ex) { - String OTPstring = OTP.get(); - Requester requester = createRequest().with("scopes", scope).with("note", note).with("note_url", noteUrl); - // Add the OTP from the user - requester.setHeader("x-github-otp", OTPstring); - return requester.method("POST").withUrlPath("/authorizations").fetch(GHAuthorization.class); - } + public GHProject getProject(long id) throws IOException { + return createRequest().withUrlPath("/projects/" + id).fetch(GHProject.class); } /** - * Create or get auth gh authorization. + * Gets project card. * - * @param clientId - * the client id - * @param clientSecret - * the client secret - * @param scopes - * the scopes - * @param note - * the note - * @param note_url - * the note url - * @return the gh authorization + * @param id + * the id + * @return the project card * @throws IOException * the io exception - * @see docs */ - public GHAuthorization createOrGetAuth(String clientId, - String clientSecret, - List scopes, - String note, - String note_url) throws IOException { - Requester requester = createRequest().with("client_secret", clientSecret) - .with("scopes", scopes) - .with("note", note) - .with("note_url", note_url); - - return requester.method("PUT").withUrlPath("/authorizations/clients/" + clientId).fetch(GHAuthorization.class); + public GHProjectCard getProjectCard(long id) throws IOException { + return createRequest().withUrlPath("/projects/columns/cards/" + id).fetch(GHProjectCard.class).lateBind(this); } /** - * Delete auth. + * Gets project column. * * @param id * the id + * @return the project column * @throws IOException * the io exception - * @see Delete an - * authorization */ - public void deleteAuth(long id) throws IOException { - createRequest().method("DELETE").withUrlPath("/authorizations/" + id).send(); + public GHProjectColumn getProjectColumn(long id) throws IOException { + return createRequest().withUrlPath("/projects/columns/" + id).fetch(GHProjectColumn.class).lateBind(this); } /** - * Check auth gh authorization. + * Gets the current full rate limit information from the server. * - * @param clientId - * the client id - * @param accessToken - * the access token - * @return the gh authorization + * For some versions of GitHub Enterprise, the {@code /rate_limit} endpoint returns a {@code 404 Not Found}. In that + * case, the most recent {@link GHRateLimit} information will be returned, including rate limit information returned + * in the response header for this request in if was present. + * + * For most use cases it would be better to implement a {@link RateLimitChecker} and add it via + * {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}. + * + * @return the rate limit * @throws IOException * the io exception - * @see Check an - * authorization */ - public GHAuthorization checkAuth(@Nonnull String clientId, @Nonnull String accessToken) throws IOException { - return createRequest().withUrlPath("/applications/" + clientId + "/tokens/" + accessToken) - .fetch(GHAuthorization.class); + @Nonnull + public GHRateLimit getRateLimit() throws IOException { + return client.getRateLimit(); } /** - * Reset auth gh authorization. + * Gets the repository object from 'owner/repo' string that GitHub calls as "repository name". * - * @param clientId - * the client id - * @param accessToken - * the access token - * @return the gh authorization + * @param name + * the name + * @return the repository * @throws IOException * the io exception - * @see Reset an - * authorization + * @see GHRepository#getName() GHRepository#getName() */ - public GHAuthorization resetAuth(@Nonnull String clientId, @Nonnull String accessToken) throws IOException { - return createRequest().method("POST") - .withUrlPath("/applications/" + clientId + "/tokens/" + accessToken) - .fetch(GHAuthorization.class); + public GHRepository getRepository(String name) throws IOException { + String[] tokens = name.split("/"); + if (tokens.length != 2) { + throw new IllegalArgumentException("Repository name must be in format owner/repo"); + } + return GHRepository.read(this, tokens[0], tokens[1]); } /** - * Returns a list of all authorizations. + * Gets the repository object from its ID. * - * @return the paged iterable - * @see List your - * authorizations + * @param id + * the id + * @return the repository by id + * @throws IOException + * the io exception */ - public PagedIterable listMyAuthorizations() { - return createRequest().withUrlPath("/authorizations").toIterable(GHAuthorization[].class, null); + public GHRepository getRepositoryById(long id) throws IOException { + return createRequest().withUrlPath("/repositories/" + id).fetch(GHRepository.class); } /** - * Returns the GitHub App associated with the authentication credentials used. - *

- * You must use a JWT to access this endpoint. + * Obtains the object that represents the named user. * - * @return the app + * @param login + * the login + * @return the user * @throws IOException * the io exception - * @see Get the authenticated - * GitHub App */ - public GHApp getApp() throws IOException { - return createRequest().withUrlPath("/app").fetch(GHApp.class); + public GHUser getUser(String login) throws IOException { + GHUser u = users.get(login); + if (u == null) { + u = createRequest().withUrlPath("/users/" + login).fetch(GHUser.class); + users.put(u.getLogin(), u); + } + return u; } /** - * Returns the GitHub App identified by the given slug + * List public events for a user + * see + * API documentation * - * @param slug - * the slug of the application - * @return the app + * @param login + * the login (user) to look public events for + * @return the events * @throws IOException - * the IO exception - * @see Get an app + * the io exception */ - public GHApp getApp(@Nonnull String slug) throws IOException { - return createRequest().withUrlPath("/apps/" + slug).fetch(GHApp.class); + public List getUserPublicEvents(String login) throws IOException { + return createRequest().withUrlPath("/users/" + login + "/events/public") + .toIterable(GHEventInfo[].class, null) + .toList(); } /** - * Creates a GitHub App from a manifest. + * Alias for {@link #getUserPublicOrganizations(String)}. * - * @param code - * temporary code returned during the manifest flow - * @return the app + * @param user + * the user + * @return the user public organizations * @throws IOException - * the IO exception - * @see Get an - * app + * the io exception */ - public GHAppFromManifest createAppFromManifest(@Nonnull String code) throws IOException { - return createRequest().method("POST") - .withUrlPath("/app-manifests/" + code + "/conversions") - .fetch(GHAppFromManifest.class); + public Map getUserPublicOrganizations(GHUser user) throws IOException { + return getUserPublicOrganizations(user.getLogin()); } /** - * Returns the GitHub App Installation associated with the authentication credentials used. + * This method returns a shallowly populated organizations. *

- * You must use an installation token to access this endpoint; otherwise consider {@link #getApp()} and its various - * ways of retrieving installations. + * To retrieve full organization details, you need to call {@link #getOrganization(String)} * - * @return the app - * @see GitHub App installations + * @param login + * the user to retrieve public Organization membership information for + * @return the public Organization memberships for the user + * @throws IOException + * the io exception */ - public GHAuthenticatedAppInstallation getInstallation() { - return new GHAuthenticatedAppInstallation(this); + public Map getUserPublicOrganizations(String login) throws IOException { + GHOrganization[] orgs = createRequest().withUrlPath("/users/" + login + "/orgs") + .toIterable(GHOrganization[].class, null) + .toArray(); + Map r = new HashMap<>(); + for (GHOrganization o : orgs) { + // don't put 'o' into orgs cache because they are shallow records + r.put(o.getLogin(), o); + } + return r; + } + + /** + * Is this an anonymous connection. + * + * @return {@code true} if operations that require authentication will fail. + */ + public boolean isAnonymous() { + return client.isAnonymous(); } /** @@ -1062,157 +987,170 @@ public boolean isCredentialValid() { } /** - * Provides a list of GitHub's IP addresses. + * Is this an always offline "connection". * - * @return an instance of {@link GHMeta} - * @throws IOException - * if the credentials supplied are invalid or if you're trying to access it as a GitHub App via the JWT - * authentication - * @see Get Meta + * @return {@code true} if this is an always offline "connection". */ - public GHMeta getMeta() throws IOException { - return this.sanityCachedMeta.get(() -> createRequest().withUrlPath("/meta").fetch(GHMeta.class)); + public boolean isOffline() { + return client.isOffline(); } /** - * Gets project. + * Returns the most recently observed rate limit data or {@code null} if either there is no rate limit (for example + * GitHub Enterprise) or if no requests have been made. * - * @param id - * the id - * @return the project - * @throws IOException - * the io exception + * @return the most recently observed rate limit data or {@code null}. + * @deprecated implement a {@link RateLimitChecker} and add it via + * {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}. */ - public GHProject getProject(long id) throws IOException { - return createRequest().withUrlPath("/projects/" + id).fetch(GHProject.class); + @Nonnull + @Deprecated + public GHRateLimit lastRateLimit() { + return client.lastRateLimit(); } /** - * Gets project column. + * This provides a dump of every public repository, in the order that they were created. * - * @param id - * the id - * @return the project column - * @throws IOException - * the io exception + * @return the paged iterable + * @see documentation */ - public GHProjectColumn getProjectColumn(long id) throws IOException { - return createRequest().withUrlPath("/projects/columns/" + id).fetch(GHProjectColumn.class).lateBind(this); + public PagedIterable listAllPublicRepositories() { + return listAllPublicRepositories(null); } /** - * Gets project card. + * This provides a dump of every public repository, in the order that they were created. * - * @param id - * the id - * @return the project card - * @throws IOException - * the io exception + * @param since + * The numeric ID of the last Repository that you’ve seen. See {@link GHRepository#getId()} + * @return the paged iterable + * @see documentation */ - public GHProjectCard getProjectCard(long id) throws IOException { - return createRequest().withUrlPath("/projects/columns/cards/" + id).fetch(GHProjectCard.class).lateBind(this); + public PagedIterable listAllPublicRepositories(final String since) { + return createRequest().with("since", since).withUrlPath("/repositories").toIterable(GHRepository[].class, null); } /** - * Tests the connection. - * - *

- * Verify that the API URL and credentials are valid to access this GitHub. - * - *

- * This method returns normally if the endpoint is reachable and verified to be GitHub API URL. Otherwise this - * method throws {@link IOException} to indicate the problem. + * Returns a list of popular open source licenses. * - * @throws IOException - * the io exception + * @return a list of popular open source licenses + * @see GitHub API - Licenses */ - public void checkApiUrlValidity() throws IOException { - client.checkApiUrlValidity(); + public PagedIterable listLicenses() { + return createRequest().withUrlPath("/licenses").toIterable(GHLicense[].class, null); } /** - * Search commits. + * Returns a list all plans for your Marketplace listing + *

+ * GitHub Apps must use a JWT to access this endpoint. + *

+ * OAuth Apps must use basic authentication with their client ID and client secret to access this endpoint. * - * @return the gh commit search builder + * @return the paged iterable + * @see List + * Plans */ - public GHCommitSearchBuilder searchCommits() { - return new GHCommitSearchBuilder(this); + public PagedIterable listMarketplacePlans() { + return createRequest().withUrlPath("/marketplace_listing/plans").toIterable(GHMarketplacePlan[].class, null); } /** - * Search issues. + * Returns a list of all authorizations. * - * @return the gh issue search builder + * @return the paged iterable + * @see List your + * authorizations */ - public GHIssueSearchBuilder searchIssues() { - return new GHIssueSearchBuilder(this); + public PagedIterable listMyAuthorizations() { + return createRequest().withUrlPath("/authorizations").toIterable(GHAuthorization[].class, null); } /** - * Search for pull requests. + * List all the notifications. * - * @return gh pull request search builder + * @return the gh notification stream */ - public GHPullRequestSearchBuilder searchPullRequests() { - return new GHPullRequestSearchBuilder(this); + public GHNotificationStream listNotifications() { + return new GHNotificationStream(this, "/notifications"); } /** - * Search users. + * Gets a list of all organizations. * - * @return the gh user search builder + * @return the paged iterable */ - public GHUserSearchBuilder searchUsers() { - return new GHUserSearchBuilder(this); + public PagedIterable listOrganizations() { + return listOrganizations(null); } /** - * Search repositories. + * Gets a list of all organizations starting after the organization identifier specified by 'since'. * - * @return the gh repository search builder + * @param since + * the since + * @return the paged iterable + * @see List All Orgs - Parameters */ - public GHRepositorySearchBuilder searchRepositories() { - return new GHRepositorySearchBuilder(this); + public PagedIterable listOrganizations(final String since) { + return createRequest().with("since", since) + .withUrlPath("/organizations") + .toIterable(GHOrganization[].class, null); } /** - * Search content. + * Returns a list of all users. * - * @return the gh content search builder + * @return the paged iterable */ - public GHContentSearchBuilder searchContent() { - return new GHContentSearchBuilder(this); + public PagedIterable listUsers() { + return createRequest().withUrlPath("/users").toIterable(GHUser[].class, null); } /** - * List all the notifications. + * Parses the GitHub event object. + *

+ * This is primarily intended for receiving a POST HTTP call from a hook. Unfortunately, hook script payloads aren't + * self-descriptive, so you need to know the type of the payload you are expecting. * - * @return the gh notification stream + * @param + * the type parameter + * @param r + * the r + * @param type + * the type + * @return the t + * @throws IOException + * the io exception */ - public GHNotificationStream listNotifications() { - return new GHNotificationStream(this, "/notifications"); + public T parseEventPayload(Reader r, Class type) throws IOException { + T t = GitHubClient.getMappingObjectReader(this).forType(type).readValue(r); + t.lateBind(); + return t; } /** - * This provides a dump of every public repository, in the order that they were created. + * Gets the current rate limit while trying not to actually make any remote requests unless absolutely necessary. * - * @return the paged iterable - * @see documentation + * @return the current rate limit data. + * @throws IOException + * if we couldn't get the current rate limit data. + * @deprecated implement a {@link RateLimitChecker} and add it via + * {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}. */ - public PagedIterable listAllPublicRepositories() { - return listAllPublicRepositories(null); + @Nonnull + @Deprecated + public GHRateLimit rateLimit() throws IOException { + return client.rateLimit(RateLimitTarget.CORE); } /** - * This provides a dump of every public repository, in the order that they were created. - * - * @param since - * The numeric ID of the last Repository that you’ve seen. See {@link GHRepository#getId()} - * @return the paged iterable - * @see documentation + * clears all cached data in order for external changes (modifications and del) to be reflected. */ - public PagedIterable listAllPublicRepositories(final String since) { - return createRequest().with("since", since).withUrlPath("/repositories").toIterable(GHRepository[].class, null); + public void refreshCache() { + users.clear(); + orgs.clear(); } /** @@ -1240,47 +1178,117 @@ public Reader renderMarkdown(String text) throws IOException { } /** - * Gets an {@link ObjectWriter} that can be used to convert data objects in this library to JSON. + * Reset auth gh authorization. * - * If you must convert data object in this library to JSON, the {@link ObjectWriter} returned by this method is the - * only supported way of doing so. This {@link ObjectWriter} can be used to convert any library data object to JSON - * without throwing an exception. + * @param clientId + * the client id + * @param accessToken + * the access token + * @return the gh authorization + * @throws IOException + * the io exception + * @see Reset an + * authorization + */ + public GHAuthorization resetAuth(@Nonnull String clientId, @Nonnull String accessToken) throws IOException { + return createRequest().method("POST") + .withUrlPath("/applications/" + clientId + "/tokens/" + accessToken) + .fetch(GHAuthorization.class); + } + + /** + * Search commits. * - * WARNING: While the JSON generated is generally expected to be stable, it is not part of the API of this library - * and may change without warning. Use with extreme caution. + * @return the gh commit search builder + */ + public GHCommitSearchBuilder searchCommits() { + return new GHCommitSearchBuilder(this); + } + + /** + * Search content. * - * @return an {@link ObjectWriter} instance that can be further configured. + * @return the gh content search builder */ - @Nonnull - public static ObjectWriter getMappingObjectWriter() { - return GitHubClient.getMappingObjectWriter(); + public GHContentSearchBuilder searchContent() { + return new GHContentSearchBuilder(this); } /** - * Gets an {@link ObjectReader} that can be used to convert JSON into library data objects. + * Search issues. * - * If you must manually create library data objects from JSON, the {@link ObjectReader} returned by this method is - * the only supported way of doing so. + * @return the gh issue search builder + */ + public GHIssueSearchBuilder searchIssues() { + return new GHIssueSearchBuilder(this); + } + + /** + * Search for pull requests. * - * WARNING: Objects generated from this method have limited functionality. They will not throw when being crated - * from valid JSON matching the expected object, but they are not guaranteed to be usable beyond that. Use with - * extreme caution. + * @return gh pull request search builder + */ + public GHPullRequestSearchBuilder searchPullRequests() { + return new GHPullRequestSearchBuilder(this); + } + + /** + * Search repositories. * - * @return an {@link ObjectReader} instance that can be further configured. + * @return the gh repository search builder */ - @Nonnull - public static ObjectReader getMappingObjectReader() { - return GitHubClient.getMappingObjectReader(GitHub.offline()); + public GHRepositorySearchBuilder searchRepositories() { + return new GHRepositorySearchBuilder(this); } /** - * Gets the client. + * Search users. * - * @return the client + * @return the gh user search builder + */ + public GHUserSearchBuilder searchUsers() { + return new GHUserSearchBuilder(this); + } + + private GHMyself setMyself() throws IOException { + synchronized (this) { + if (this.myself == null) { + this.myself = createRequest().withUrlPath("/user").fetch(GHMyself.class); + } + return myself; + } + } + + /** + * Interns the given {@link GHUser}. + * + * @param orig + * the orig + * @return the user + */ + protected GHUser getUser(GHUser orig) { + GHUser u = users.get(orig.getLogin()); + if (u == null) { + users.put(orig.getLogin(), orig); + return orig; + } + return u; + } + + /** + * Creates a request to GitHub GraphQL API. + * + * @param query + * the query for the GraphQL + * @return the requester */ @Nonnull - GitHubClient getClient() { - return client; + Requester createGraphQLRequest(String query) { + return createRequest().method("POST") + .withApiUrl(getApiUrl().replace("/api/v3", "/api")) + .rateLimit(RateLimitTarget.GRAPHQL) + .with("query", query) + .withUrlPath("/graphql"); } /** @@ -1299,6 +1307,16 @@ Requester createRequest() { return requester; } + /** + * Gets the client. + * + * @return the client + */ + @Nonnull + GitHubClient getClient() { + return client; + } + /** * Intern. * @@ -1317,6 +1335,4 @@ GHUser intern(GHUser user) { } return user; } - - private static final Logger LOGGER = Logger.getLogger(GitHub.class.getName()); } diff --git a/src/main/java/org/kohsuke/github/GitHubAbuseLimitHandler.java b/src/main/java/org/kohsuke/github/GitHubAbuseLimitHandler.java index 84dd8c48e9..2c05dd1455 100644 --- a/src/main/java/org/kohsuke/github/GitHubAbuseLimitHandler.java +++ b/src/main/java/org/kohsuke/github/GitHubAbuseLimitHandler.java @@ -24,11 +24,73 @@ */ public abstract class GitHubAbuseLimitHandler extends GitHubConnectorResponseErrorHandler { + /** + * Fail immediately. + */ + public static final GitHubAbuseLimitHandler FAIL = new GitHubAbuseLimitHandler() { + @Override + public void onError(GitHubConnectorResponse connectorResponse) throws IOException { + throw new HttpException("Abuse limit reached", + connectorResponse.statusCode(), + connectorResponse.header("Status"), + connectorResponse.request().url().toString()) + .withResponseHeaderFields(connectorResponse.allHeaders()); + } + }; + + /** + * Wait until the API abuse "wait time" is passed. + */ + public static final GitHubAbuseLimitHandler WAIT = new GitHubAbuseLimitHandler() { + @Override + public void onError(GitHubConnectorResponse connectorResponse) throws IOException { + try { + Thread.sleep(parseWaitTime(connectorResponse)); + } catch (InterruptedException ex) { + throw (InterruptedIOException) new InterruptedIOException().initCause(ex); + } + } + }; + /** * On a wait, even if the response suggests a very short wait, wait for a minimum duration. */ private static final int MINIMUM_ABUSE_RETRY_MILLIS = 1000; + // If "Retry-After" missing, wait for unambiguously over one minute per GitHub guidance + static long DEFAULT_WAIT_MILLIS = Duration.ofSeconds(61).toMillis(); + + /* + * Exposed for testability. Given an http response, find the retry-after header field and parse it as either a + * number or a date (the spec allows both). If no header is found, wait for a reasonably amount of time. + */ + static long parseWaitTime(GitHubConnectorResponse connectorResponse) { + String v = connectorResponse.header("Retry-After"); + if (v == null) { + return DEFAULT_WAIT_MILLIS; + } + + try { + return Math.max(MINIMUM_ABUSE_RETRY_MILLIS, Duration.ofSeconds(Long.parseLong(v)).toMillis()); + } catch (NumberFormatException nfe) { + // The retry-after header could be a number in seconds, or an http-date + // We know it was a date if we got a number format exception :) + + // Don't use ZonedDateTime.now(), because the local and remote server times may not be in sync + // Instead, we can take advantage of the Date field in the response to see what time the remote server + // thinks it is + String dateField = connectorResponse.header("Date"); + ZonedDateTime now; + if (dateField != null) { + now = ZonedDateTime.parse(dateField, DateTimeFormatter.RFC_1123_DATE_TIME); + } else { + now = ZonedDateTime.now(); + } + ZonedDateTime zdt = ZonedDateTime.parse(v, DateTimeFormatter.RFC_1123_DATE_TIME); + return Math.max(MINIMUM_ABUSE_RETRY_MILLIS, ChronoUnit.MILLIS.between(now, zdt)); + } + } + /** * Create default GitHubAbuseLimitHandler instance */ @@ -36,40 +98,36 @@ public GitHubAbuseLimitHandler() { } /** - * Checks if is error. + * Called when the library encounters HTTP error indicating that the API abuse limit is reached. + * + *

+ * Any exception thrown from this method will cause the request to fail, and the caller of github-api will receive + * an exception. If this method returns normally, another request will be attempted. For that to make sense, the + * implementation needs to wait for some time. * * @param connectorResponse - * the connector response - * @return true, if is error + * Response information for this request. * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Override - boolean isError(@Nonnull GitHubConnectorResponse connectorResponse) { - return isTooManyRequests(connectorResponse) - || (isForbidden(connectorResponse) && hasRetryOrLimitHeader(connectorResponse)); - } - - /** - * Checks if the response status code is TOO_MANY_REQUESTS (429). + * on failure + * @see API documentation from GitHub + * @see Dealing + * with abuse rate limits * - * @param connectorResponse - * the response from the GitHub connector - * @return true if the status code is TOO_MANY_REQUESTS */ - private boolean isTooManyRequests(GitHubConnectorResponse connectorResponse) { - return connectorResponse.statusCode() == TOO_MANY_REQUESTS; - } + public abstract void onError(@Nonnull GitHubConnectorResponse connectorResponse) throws IOException; /** - * Checks if the response status code is HTTP_FORBIDDEN (403). + * Checks if the response contains a specific header. * * @param connectorResponse * the response from the GitHub connector - * @return true if the status code is HTTP_FORBIDDEN + * @param headerName + * the name of the header to check for + * @return true if the specified header is present */ - private boolean isForbidden(GitHubConnectorResponse connectorResponse) { - return connectorResponse.statusCode() == HTTP_FORBIDDEN; + private boolean hasHeader(GitHubConnectorResponse connectorResponse, String headerName) { + return connectorResponse.header(headerName) != null; } /** @@ -89,98 +147,40 @@ private boolean hasRetryOrLimitHeader(GitHubConnectorResponse connectorResponse) } /** - * Checks if the response contains a specific header. + * Checks if the response status code is HTTP_FORBIDDEN (403). * * @param connectorResponse * the response from the GitHub connector - * @param headerName - * the name of the header to check for - * @return true if the specified header is present + * @return true if the status code is HTTP_FORBIDDEN */ - private boolean hasHeader(GitHubConnectorResponse connectorResponse, String headerName) { - return connectorResponse.header(headerName) != null; + private boolean isForbidden(GitHubConnectorResponse connectorResponse) { + return connectorResponse.statusCode() == HTTP_FORBIDDEN; } /** - * Called when the library encounters HTTP error indicating that the API abuse limit is reached. - * - *

- * Any exception thrown from this method will cause the request to fail, and the caller of github-api will receive - * an exception. If this method returns normally, another request will be attempted. For that to make sense, the - * implementation needs to wait for some time. + * Checks if the response status code is TOO_MANY_REQUESTS (429). * * @param connectorResponse - * Response information for this request. - * @throws IOException - * on failure - * @see API documentation from GitHub - * @see Dealing - * with abuse rate limits - * - */ - public abstract void onError(@Nonnull GitHubConnectorResponse connectorResponse) throws IOException; - - /** - * Wait until the API abuse "wait time" is passed. + * the response from the GitHub connector + * @return true if the status code is TOO_MANY_REQUESTS */ - public static final GitHubAbuseLimitHandler WAIT = new GitHubAbuseLimitHandler() { - @Override - public void onError(GitHubConnectorResponse connectorResponse) throws IOException { - try { - Thread.sleep(parseWaitTime(connectorResponse)); - } catch (InterruptedException ex) { - throw (InterruptedIOException) new InterruptedIOException().initCause(ex); - } - } - }; + private boolean isTooManyRequests(GitHubConnectorResponse connectorResponse) { + return connectorResponse.statusCode() == TOO_MANY_REQUESTS; + } /** - * Fail immediately. - */ - public static final GitHubAbuseLimitHandler FAIL = new GitHubAbuseLimitHandler() { - @Override - public void onError(GitHubConnectorResponse connectorResponse) throws IOException { - throw new HttpException("Abuse limit reached", - connectorResponse.statusCode(), - connectorResponse.header("Status"), - connectorResponse.request().url().toString()) - .withResponseHeaderFields(connectorResponse.allHeaders()); - } - }; - - // If "Retry-After" missing, wait for unambiguously over one minute per GitHub guidance - static long DEFAULT_WAIT_MILLIS = Duration.ofSeconds(61).toMillis(); - - /* - * Exposed for testability. Given an http response, find the retry-after header field and parse it as either a - * number or a date (the spec allows both). If no header is found, wait for a reasonably amount of time. + * Checks if is error. + * + * @param connectorResponse + * the connector response + * @return true, if is error + * @throws IOException + * Signals that an I/O exception has occurred. */ - static long parseWaitTime(GitHubConnectorResponse connectorResponse) { - String v = connectorResponse.header("Retry-After"); - if (v == null) { - return DEFAULT_WAIT_MILLIS; - } - - try { - return Math.max(MINIMUM_ABUSE_RETRY_MILLIS, Duration.ofSeconds(Long.parseLong(v)).toMillis()); - } catch (NumberFormatException nfe) { - // The retry-after header could be a number in seconds, or an http-date - // We know it was a date if we got a number format exception :) - - // Don't use ZonedDateTime.now(), because the local and remote server times may not be in sync - // Instead, we can take advantage of the Date field in the response to see what time the remote server - // thinks it is - String dateField = connectorResponse.header("Date"); - ZonedDateTime now; - if (dateField != null) { - now = ZonedDateTime.parse(dateField, DateTimeFormatter.RFC_1123_DATE_TIME); - } else { - now = ZonedDateTime.now(); - } - ZonedDateTime zdt = ZonedDateTime.parse(v, DateTimeFormatter.RFC_1123_DATE_TIME); - return Math.max(MINIMUM_ABUSE_RETRY_MILLIS, ChronoUnit.MILLIS.between(now, zdt)); - } + @Override + boolean isError(@Nonnull GitHubConnectorResponse connectorResponse) { + return isTooManyRequests(connectorResponse) + || (isForbidden(connectorResponse) && hasRetryOrLimitHeader(connectorResponse)); } } diff --git a/src/main/java/org/kohsuke/github/GitHubBridgeAdapterObject.java b/src/main/java/org/kohsuke/github/GitHubBridgeAdapterObject.java new file mode 100644 index 0000000000..3477c96013 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GitHubBridgeAdapterObject.java @@ -0,0 +1,25 @@ +package org.kohsuke.github; + +import java.time.Instant; +import java.util.Date; + +/** + * Defines a base class that holds bridge adapter methods. + * + * @author Liam Newman + */ +abstract class GitHubBridgeAdapterObject { + /** + * Instantiates a new git hub bridge adapter object. + */ + GitHubBridgeAdapterObject() { + } + + // Used by bridge method to convert Instant to Date + Object instantToDate(Instant value, Class type) { + if (value == null) + return null; + + return Date.from(value); + } +} diff --git a/src/main/java/org/kohsuke/github/GitHubBuilder.java b/src/main/java/org/kohsuke/github/GitHubBuilder.java index 035ad76a6c..3f762fd059 100644 --- a/src/main/java/org/kohsuke/github/GitHubBuilder.java +++ b/src/main/java/org/kohsuke/github/GitHubBuilder.java @@ -28,67 +28,6 @@ public class GitHubBuilder implements Cloneable { // for testing static File HOME_DIRECTORY = null; - // default scoped so unit tests can read them. - /** The endpoint. */ - /* private */ String endpoint = GitHubClient.GITHUB_URL; - - private GitHubConnector connector; - - private GitHubRateLimitHandler rateLimitHandler = GitHubRateLimitHandler.WAIT; - private GitHubAbuseLimitHandler abuseLimitHandler = GitHubAbuseLimitHandler.WAIT; - private GitHubRateLimitChecker rateLimitChecker = new GitHubRateLimitChecker(); - - /** The authorization provider. */ - /* private */ AuthorizationProvider authorizationProvider = AuthorizationProvider.ANONYMOUS; - - /** - * Instantiates a new Git hub builder. - */ - public GitHubBuilder() { - } - - /** - * First check if the credentials are configured in the environment. We use environment first because users are not - * likely to give required (full) permissions to their default key. - * - * If no user is specified it means there is no configuration present, so try using the ~/.github properties file. - ** - * If there is still no user it means there are no credentials defined and throw an IOException. - * - * @return the configured Builder from credentials defined on the system or in the environment. Otherwise returns - * null. - * - * @throws IOException - * If there are no credentials defined in the ~/.github properties file or the process environment. - */ - static GitHubBuilder fromCredentials() throws IOException { - Exception cause = null; - GitHubBuilder builder = null; - - builder = fromEnvironment(); - - if (builder.authorizationProvider != AuthorizationProvider.ANONYMOUS) - return builder; - - try { - builder = fromPropertyFile(); - - if (builder.authorizationProvider != AuthorizationProvider.ANONYMOUS) - return builder; - } catch (FileNotFoundException e) { - // fall through - cause = e; - } - throw (IOException) new IOException("Failed to resolve credentials from ~/.github or the environment.") - .initCause(cause); - } - - private static void loadIfSet(String envName, Properties p, String propName) { - String v = System.getenv(envName); - if (v != null) - p.put(propName, v); - } - /** * Creates {@link GitHubBuilder} by picking up coordinates from environment variables. * @@ -118,6 +57,29 @@ public static GitHubBuilder fromEnvironment() { return fromProperties(props); } + /** + * From properties GitHubBuilder. + * + * @param props + * the props + * @return the GitHubBuilder + */ + public static GitHubBuilder fromProperties(Properties props) { + GitHubBuilder self = new GitHubBuilder(); + String oauth = props.getProperty("oauth"); + String jwt = props.getProperty("jwt"); + String login = props.getProperty("login"); + + if (oauth != null) { + self.withOAuthToken(oauth, login); + } + if (jwt != null) { + self.withJwtToken(jwt); + } + self.withEndpoint(props.getProperty("endpoint", GitHubClient.GITHUB_URL)); + return self; + } + /** * From property file GitHubBuilder. * @@ -130,7 +92,6 @@ public static GitHubBuilder fromPropertyFile() throws IOException { File propertyFile = new File(homeDir, ".github"); return fromPropertyFile(propertyFile.getPath()); } - /** * From property file GitHubBuilder. * @@ -152,81 +113,113 @@ public static GitHubBuilder fromPropertyFile(String propertyFileName) throws IOE return fromProperties(props); } + private static void loadIfSet(String envName, Properties p, String propName) { + String v = System.getenv(envName); + if (v != null) + p.put(propName, v); + } /** - * From properties GitHubBuilder. + * First check if the credentials are configured in the environment. We use environment first because users are not + * likely to give required (full) permissions to their default key. * - * @param props - * the props - * @return the GitHubBuilder + * If no user is specified it means there is no configuration present, so try using the ~/.github properties file. + ** + * If there is still no user it means there are no credentials defined and throw an IOException. + * + * @return the configured Builder from credentials defined on the system or in the environment. Otherwise returns + * null. + * + * @throws IOException + * If there are no credentials defined in the ~/.github properties file or the process environment. */ - public static GitHubBuilder fromProperties(Properties props) { - GitHubBuilder self = new GitHubBuilder(); - String oauth = props.getProperty("oauth"); - String jwt = props.getProperty("jwt"); - String login = props.getProperty("login"); + static GitHubBuilder fromCredentials() throws IOException { + Exception cause = null; + GitHubBuilder builder = null; - if (oauth != null) { - self.withOAuthToken(oauth, login); - } - if (jwt != null) { - self.withJwtToken(jwt); + builder = fromEnvironment(); + + if (builder.authorizationProvider != AuthorizationProvider.ANONYMOUS) + return builder; + + try { + builder = fromPropertyFile(); + + if (builder.authorizationProvider != AuthorizationProvider.ANONYMOUS) + return builder; + } catch (FileNotFoundException e) { + // fall through + cause = e; } - self.withEndpoint(props.getProperty("endpoint", GitHubClient.GITHUB_URL)); - return self; + throw (IOException) new IOException("Failed to resolve credentials from ~/.github or the environment.") + .initCause(cause); } + private GitHubAbuseLimitHandler abuseLimitHandler = GitHubAbuseLimitHandler.WAIT; + + private GitHubConnector connector; + + private GitHubRateLimitChecker rateLimitChecker = new GitHubRateLimitChecker(); + + private GitHubRateLimitHandler rateLimitHandler = GitHubRateLimitHandler.WAIT; + + /** The authorization provider. */ + /* private */ AuthorizationProvider authorizationProvider = AuthorizationProvider.ANONYMOUS; + + // default scoped so unit tests can read them. + /** The endpoint. */ + /* private */ String endpoint = GitHubClient.GITHUB_URL; + /** - * With endpoint GitHubBuilder. - * - * @param endpoint - * The URL of GitHub (or GitHub enterprise) API endpoint, such as "https://api.github.com" or - * "https://ghe.acme.com/api/v3". Note that GitHub Enterprise has /api/v3 in the URL. For - * historical reasons, this parameter still accepts the bare domain name, but that's considered - * deprecated. - * @return the GitHubBuilder + * Instantiates a new Git hub builder. */ - public GitHubBuilder withEndpoint(String endpoint) { - this.endpoint = endpoint; - return this; + public GitHubBuilder() { } /** - * With o auth token GitHubBuilder. + * Builds a {@link GitHub} instance. * - * @param oauthToken - * the oauth token - * @return the GitHubBuilder + * @return the github + * @throws IOException + * the io exception */ - public GitHubBuilder withOAuthToken(String oauthToken) { - return withAuthorizationProvider(ImmutableAuthorizationProvider.fromOauthToken(oauthToken)); + public GitHub build() throws IOException { + return new GitHub(endpoint, + connector, + rateLimitHandler, + abuseLimitHandler, + rateLimitChecker, + authorizationProvider); } /** - * With o auth token GitHubBuilder. + * Clone. * - * @param oauthToken - * the oauth token - * @param user - * the user * @return the GitHubBuilder */ - public GitHubBuilder withOAuthToken(String oauthToken, String user) { - return withAuthorizationProvider(ImmutableAuthorizationProvider.fromOauthToken(oauthToken, user)); + @Override + public GitHubBuilder clone() { + try { + return (GitHubBuilder) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("Clone should be supported", e); + } } /** - * Configures a {@link AuthorizationProvider} for this builder - * - * There can be only one authorization provider per client instance. + * Adds a {@link GitHubAbuseLimitHandler} to this {@link GitHubBuilder}. + *

+ * When a client sends too many requests in a short time span, GitHub may return an error and set a header telling + * the client to not make any more request for some period of time. If this happens, + * {@link GitHubAbuseLimitHandler#onError(GitHubConnectorResponse)} will be called. + *

* - * @param authorizationProvider - * the authorization provider + * @param handler + * the handler * @return the GitHubBuilder - * */ - public GitHubBuilder withAuthorizationProvider(final AuthorizationProvider authorizationProvider) { - this.authorizationProvider = authorizationProvider; + public GitHubBuilder withAbuseLimitHandler(GitHubAbuseLimitHandler handler) { + this.abuseLimitHandler = handler; return this; } @@ -243,14 +236,18 @@ public GitHubBuilder withAppInstallationToken(String appInstallationToken) { } /** - * With jwt token GitHubBuilder. + * Configures a {@link AuthorizationProvider} for this builder * - * @param jwtToken - * the jwt token + * There can be only one authorization provider per client instance. + * + * @param authorizationProvider + * the authorization provider * @return the GitHubBuilder + * */ - public GitHubBuilder withJwtToken(String jwtToken) { - return withAuthorizationProvider(ImmutableAuthorizationProvider.fromJwtToken(jwtToken)); + public GitHubBuilder withAuthorizationProvider(final AuthorizationProvider authorizationProvider) { + this.authorizationProvider = authorizationProvider; + return this; } /** @@ -266,47 +263,53 @@ public GitHubBuilder withConnector(GitHubConnector connector) { } /** - * Adds a {@link GitHubRateLimitHandler} to this {@link GitHubBuilder}. - *

- * GitHub allots a certain number of requests to each user or application per period of time (usually per hour). The - * number of requests remaining is returned in the response header and can also be requested using - * {@link GitHub#getRateLimit()}. This requests per interval is referred to as the "rate limit". - *

- *

- * When the remaining number of requests reaches zero, the next request will return an error. If this happens, - * {@link GitHubRateLimitHandler#onError(GitHubConnectorResponse)} will be called. - *

- *

- * NOTE: GitHub treats clients that exceed their rate limit very harshly. If possible, clients should avoid - * exceeding their rate limit. Consider adding a {@link RateLimitChecker} to automatically check the rate limit for - * each request and wait if needed. - *

+ * With endpoint GitHubBuilder. * - * @param handler - * the handler + * @param endpoint + * The URL of GitHub (or GitHub enterprise) API endpoint, such as "https://api.github.com" or + * "https://ghe.acme.com/api/v3". Note that GitHub Enterprise has /api/v3 in the URL. For + * historical reasons, this parameter still accepts the bare domain name, but that's considered + * deprecated. * @return the GitHubBuilder - * @see #withRateLimitChecker(RateLimitChecker) */ - public GitHubBuilder withRateLimitHandler(GitHubRateLimitHandler handler) { - this.rateLimitHandler = handler; + public GitHubBuilder withEndpoint(String endpoint) { + this.endpoint = endpoint; return this; } /** - * Adds a {@link GitHubAbuseLimitHandler} to this {@link GitHubBuilder}. - *

- * When a client sends too many requests in a short time span, GitHub may return an error and set a header telling - * the client to not make any more request for some period of time. If this happens, - * {@link GitHubAbuseLimitHandler#onError(GitHubConnectorResponse)} will be called. - *

+ * With jwt token GitHubBuilder. * - * @param handler - * the handler + * @param jwtToken + * the jwt token * @return the GitHubBuilder */ - public GitHubBuilder withAbuseLimitHandler(GitHubAbuseLimitHandler handler) { - this.abuseLimitHandler = handler; - return this; + public GitHubBuilder withJwtToken(String jwtToken) { + return withAuthorizationProvider(ImmutableAuthorizationProvider.fromJwtToken(jwtToken)); + } + + /** + * With o auth token GitHubBuilder. + * + * @param oauthToken + * the oauth token + * @return the GitHubBuilder + */ + public GitHubBuilder withOAuthToken(String oauthToken) { + return withAuthorizationProvider(ImmutableAuthorizationProvider.fromOauthToken(oauthToken)); + } + + /** + * With o auth token GitHubBuilder. + * + * @param oauthToken + * the oauth token + * @param user + * the user + * @return the GitHubBuilder + */ + public GitHubBuilder withOAuthToken(String oauthToken, String user) { + return withAuthorizationProvider(ImmutableAuthorizationProvider.fromOauthToken(oauthToken, user)); } /** @@ -352,32 +355,29 @@ public GitHubBuilder withRateLimitChecker(@Nonnull RateLimitChecker rateLimitChe } /** - * Builds a {@link GitHub} instance. - * - * @return the github - * @throws IOException - * the io exception - */ - public GitHub build() throws IOException { - return new GitHub(endpoint, - connector, - rateLimitHandler, - abuseLimitHandler, - rateLimitChecker, - authorizationProvider); - } - - /** - * Clone. + * Adds a {@link GitHubRateLimitHandler} to this {@link GitHubBuilder}. + *

+ * GitHub allots a certain number of requests to each user or application per period of time (usually per hour). The + * number of requests remaining is returned in the response header and can also be requested using + * {@link GitHub#getRateLimit()}. This requests per interval is referred to as the "rate limit". + *

+ *

+ * When the remaining number of requests reaches zero, the next request will return an error. If this happens, + * {@link GitHubRateLimitHandler#onError(GitHubConnectorResponse)} will be called. + *

+ *

+ * NOTE: GitHub treats clients that exceed their rate limit very harshly. If possible, clients should avoid + * exceeding their rate limit. Consider adding a {@link RateLimitChecker} to automatically check the rate limit for + * each request and wait if needed. + *

* + * @param handler + * the handler * @return the GitHubBuilder + * @see #withRateLimitChecker(RateLimitChecker) */ - @Override - public GitHubBuilder clone() { - try { - return (GitHubBuilder) super.clone(); - } catch (CloneNotSupportedException e) { - throw new RuntimeException("Clone should be supported", e); - } + public GitHubBuilder withRateLimitHandler(GitHubRateLimitHandler handler) { + this.rateLimitHandler = handler; + return this; } } diff --git a/src/main/java/org/kohsuke/github/GitHubClient.java b/src/main/java/org/kohsuke/github/GitHubClient.java index f669280b4d..7963de57e2 100644 --- a/src/main/java/org/kohsuke/github/GitHubClient.java +++ b/src/main/java/org/kohsuke/github/GitHubClient.java @@ -2,6 +2,8 @@ import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.introspect.VisibilityChecker; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.apache.commons.io.IOUtils; import org.kohsuke.github.authorization.AuthorizationProvider; import org.kohsuke.github.authorization.UserAuthorizationProvider; @@ -49,697 +51,657 @@ */ class GitHubClient { - /** The Constant CONNECTION_ERROR_RETRIES. */ - private static final int DEFAULT_CONNECTION_ERROR_RETRIES = 2; + private static class GHApiInfo { + private String rateLimitUrl; - /** The Constant DEFAULT_MINIMUM_RETRY_TIMEOUT_MILLIS. */ - private static final int DEFAULT_MINIMUM_RETRY_MILLIS = 100; + void check(String apiUrl) throws IOException { + if (rateLimitUrl == null) + throw new IOException(apiUrl + " doesn't look like GitHub API URL"); - /** The Constant DEFAULT_MAXIMUM_RETRY_TIMEOUT_MILLIS. */ - private static final int DEFAULT_MAXIMUM_RETRY_MILLIS = DEFAULT_MINIMUM_RETRY_MILLIS; + // make sure that the URL is legitimate + new URL(rateLimitUrl); + } + } - private static final ThreadLocal sendRequestTraceId = new ThreadLocal<>(); + /** + * Represents a supplier of results that can throw. + * + * @param + * the type of results supplied by this supplier + */ + @FunctionalInterface + interface BodyHandler extends FunctionThrows { + } - // Cache of myself object. - private final String apiUrl; + /** + * The Class RetryRequestException. + */ + static class RetryRequestException extends IOException { - private final GitHubRateLimitHandler rateLimitHandler; - private final GitHubAbuseLimitHandler abuseLimitHandler; - private final GitHubRateLimitChecker rateLimitChecker; - private final AuthorizationProvider authorizationProvider; + /** The connector request. */ + final GitHubConnectorRequest connectorRequest; - private GitHubConnector connector; + /** + * Instantiates a new retry request exception. + */ + RetryRequestException() { + this(null); + } - @Nonnull - private final AtomicReference rateLimit = new AtomicReference<>(GHRateLimit.DEFAULT); + /** + * Instantiates a new retry request exception. + * + * @param connectorRequest + * the connector request + */ + RetryRequestException(GitHubConnectorRequest connectorRequest) { + this.connectorRequest = connectorRequest; + } + } - @Nonnull - private final GitHubSanityCachedValue sanityCachedRateLimit = new GitHubSanityCachedValue<>(); + private static final DateTimeFormatter DATE_TIME_PARSER_SLASHES = DateTimeFormatter + .ofPattern("yyyy/MM/dd HH:mm:ss Z"); - @Nonnull - private GitHubSanityCachedValue sanityCachedIsCredentialValid = new GitHubSanityCachedValue<>(); + /** The Constant CONNECTION_ERROR_RETRIES. */ + private static final int DEFAULT_CONNECTION_ERROR_RETRIES = 2; + /** The Constant DEFAULT_MAXIMUM_RETRY_TIMEOUT_MILLIS. */ + private static final int DEFAULT_MAXIMUM_RETRY_MILLIS = 100; + /** The Constant DEFAULT_MINIMUM_RETRY_TIMEOUT_MILLIS. */ + private static final int DEFAULT_MINIMUM_RETRY_MILLIS = DEFAULT_MAXIMUM_RETRY_MILLIS; private static final Logger LOGGER = Logger.getLogger(GitHubClient.class.getName()); + private static final ObjectMapper MAPPER = JsonMapper.builder() + .addModule(new JavaTimeModule()) + .visibility(new VisibilityChecker.Std(NONE, NONE, NONE, NONE, ANY)) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) + .propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE) + .build(); - private static final ObjectMapper MAPPER = new ObjectMapper(); + private static final ThreadLocal sendRequestTraceId = new ThreadLocal<>(); /** The Constant GITHUB_URL. */ static final String GITHUB_URL = "https://api.github.com"; - private static final DateTimeFormatter DATE_TIME_PARSER_SLASHES = DateTimeFormatter - .ofPattern("yyyy/MM/dd HH:mm:ss Z"); - - static { - MAPPER.setVisibility(new VisibilityChecker.Std(NONE, NONE, NONE, NONE, ANY)); - MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - MAPPER.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true); - MAPPER.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + @Nonnull + private static GitHubResponse createResponse(@Nonnull GitHubConnectorResponse connectorResponse, + @CheckForNull BodyHandler handler) throws IOException { + T body = null; + if (handler != null) { + if (!shouldIgnoreBody(connectorResponse)) { + body = handler.apply(connectorResponse); + } + } + return new GitHubResponse<>(connectorResponse, body); } - /** - * Instantiates a new git hub client. - * - * @param apiUrl - * the api url - * @param connector - * the connector - * @param rateLimitHandler - * the rate limit handler - * @param abuseLimitHandler - * the abuse limit handler - * @param rateLimitChecker - * the rate limit checker - * @param authorizationProvider - * the authorization provider - */ - GitHubClient(String apiUrl, - GitHubConnector connector, - GitHubRateLimitHandler rateLimitHandler, - GitHubAbuseLimitHandler abuseLimitHandler, - GitHubRateLimitChecker rateLimitChecker, - AuthorizationProvider authorizationProvider) { - - if (apiUrl.endsWith("/")) { - apiUrl = apiUrl.substring(0, apiUrl.length() - 1); // normalize + private static void detectOTPRequired(@Nonnull GitHubConnectorResponse connectorResponse) throws GHIOException { + // 401 Unauthorized == bad creds or OTP request + if (connectorResponse.statusCode() == HTTP_UNAUTHORIZED) { + // In the case of a user with 2fa enabled, a header with X-GitHub-OTP + // will be returned indicating the user needs to respond with an otp + if (connectorResponse.header("X-GitHub-OTP") != null) { + throw new GHOTPRequiredException().withResponseHeaderFields(connectorResponse.allHeaders()); + } } + } - if (null == connector) { - connector = GitHubConnector.DEFAULT; + // This implements the exact same rules as the ones applied in jdk.internal.net.http.RedirectFilter + private static String getRedirectedMethod(int statusCode, String originalMethod) { + switch (statusCode) { + case HTTP_MOVED_PERM : + case HTTP_MOVED_TEMP : + return originalMethod.equals("POST") ? "GET" : originalMethod; + case 303 : + return "GET"; + case 307 : + case 308 : + return originalMethod; + default : + return originalMethod; } - this.apiUrl = apiUrl; - this.connector = connector; + } - // Prefer credential configuration via provider - this.authorizationProvider = authorizationProvider; + private static URI getRedirectedUri(URI requestUri, GitHubConnectorResponse connectorResponse) throws IOException { + URI redirectedURI; + redirectedURI = Optional.of(connectorResponse.header("Location")) + .map(URI::create) + .orElseThrow(() -> new IOException("Invalid redirection")); - this.rateLimitHandler = rateLimitHandler; - this.abuseLimitHandler = abuseLimitHandler; - this.rateLimitChecker = rateLimitChecker; + // redirect could be relative to original URL, but if not + // then redirect is used. + redirectedURI = requestUri.resolve(redirectedURI); + return redirectedURI; } /** - * Gets the login. - * - * @return the login + * Handle API error by either throwing it or by returning normally to retry. */ - String getLogin() { - try { - if (this.authorizationProvider instanceof UserAuthorizationProvider - && this.authorizationProvider.getEncodedAuthorization() != null) { + private static IOException interpretApiError(IOException e, + @Nonnull GitHubConnectorRequest connectorRequest, + @CheckForNull GitHubConnectorResponse connectorResponse) { + // If we're already throwing a GHIOException, pass through + if (e instanceof GHIOException) { + return e; + } - UserAuthorizationProvider userAuthorizationProvider = (UserAuthorizationProvider) this.authorizationProvider; + int statusCode = -1; + String message = null; + Map> headers = new HashMap<>(); + String errorMessage = null; - return userAuthorizationProvider.getLogin(); + if (connectorResponse != null) { + statusCode = connectorResponse.statusCode(); + message = connectorResponse.header("Status"); + headers = connectorResponse.allHeaders(); + if (connectorResponse.statusCode() >= HTTP_BAD_REQUEST) { + errorMessage = GitHubResponse.getBodyAsStringOrNull(connectorResponse); } - } catch (IOException e) { } - return null; - } - - private T fetch(Class type, String urlPath) throws IOException { - GitHubRequest request = GitHubRequest.newBuilder().withApiUrl(getApiUrl()).withUrlPath(urlPath).build(); - return sendRequest(request, (connectorResponse) -> GitHubResponse.parseBody(connectorResponse, type)).body(); - } - /** - * Ensures that the credential for this client is valid. - * - * @return the boolean - */ - public boolean isCredentialValid() { - return sanityCachedIsCredentialValid.get(() -> { - try { - // If 404, ratelimit returns a default value. - // This works as credential test because invalid credentials returns 401, not 404 - getRateLimit(); - return Boolean.TRUE; - } catch (IOException e) { - LOGGER.log(FINE, - e, - () -> String.format("(%s) Exception validating credentials on %s with login '%s'", - sendRequestTraceId.get(), - getApiUrl(), - getLogin())); - return Boolean.FALSE; + if (errorMessage != null) { + if (e instanceof FileNotFoundException) { + // pass through 404 Not Found to allow the caller to handle it intelligently + e = new GHFileNotFoundException(e.getMessage() + " " + errorMessage, e) + .withResponseHeaderFields(headers); + } else if (statusCode >= 0) { + e = new HttpException(errorMessage, statusCode, message, connectorRequest.url().toString(), e); + } else { + e = new GHIOException(errorMessage).withResponseHeaderFields(headers); } - }); - } - - /** - * Is this an always offline "connection". - * - * @return {@code true} if this is an always offline "connection". - */ - public boolean isOffline() { - return connector == GitHubConnector.OFFLINE; - } - - /** - * Is this an anonymous connection. - * - * @return {@code true} if operations that require authentication will fail. - */ - public boolean isAnonymous() { - try { - return getLogin() == null && this.authorizationProvider.getEncodedAuthorization() == null; - } catch (IOException e) { - // An exception here means that the provider failed to provide authorization parameters, - // basically meaning the same as "no auth" - return false; + } else if (!(e instanceof FileNotFoundException)) { + e = new HttpException(statusCode, message, connectorRequest.url().toString(), e); } + return e; } - /** - * Gets the current full rate limit information from the server. - * - * For some versions of GitHub Enterprise, the {@code /rate_limit} endpoint returns a {@code 404 Not Found}. In that - * case, the most recent {@link GHRateLimit} information will be returned, including rate limit information returned - * in the response header for this request in if was present. - * - * For most use cases it would be better to implement a {@link RateLimitChecker} and add it via - * {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}. - * - * @return the rate limit - * @throws IOException - * the io exception - */ - @Nonnull - public GHRateLimit getRateLimit() throws IOException { - return getRateLimit(RateLimitTarget.NONE); + // This implements the exact same rules as the ones applied in jdk.internal.net.http.RedirectFilter + private static boolean isRedirecting(int statusCode) { + return statusCode == HTTP_MOVED_PERM || statusCode == HTTP_MOVED_TEMP || statusCode == 303 || statusCode == 307 + || statusCode == 308; } - /** - * Gets the encoded authorization. - * - * @return the encoded authorization - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @CheckForNull - String getEncodedAuthorization() throws IOException { - return authorizationProvider.getEncodedAuthorization(); + private static void logRetryConnectionError(IOException e, URL url, int retries) throws IOException { + // There are a range of connection errors where we want to wait a moment and just automatically retry + + // WARNING: These are unsupported environment variables. + // The GitHubClient class is internal and may change at any time. + int minRetryInterval = Math.max(DEFAULT_MINIMUM_RETRY_MILLIS, + Integer.getInteger(GitHubClient.class.getName() + ".minRetryInterval", DEFAULT_MINIMUM_RETRY_MILLIS)); + int maxRetryInterval = Math.max(DEFAULT_MAXIMUM_RETRY_MILLIS, + Integer.getInteger(GitHubClient.class.getName() + ".maxRetryInterval", DEFAULT_MAXIMUM_RETRY_MILLIS)); + + long sleepTime = maxRetryInterval <= minRetryInterval + ? minRetryInterval + : ThreadLocalRandom.current().nextLong(minRetryInterval, maxRetryInterval); + + LOGGER.log(INFO, + () -> String.format( + "(%s) %s while connecting to %s: '%s'. Sleeping %d milliseconds before retrying (%d retries remaining)", + sendRequestTraceId.get(), + e.getClass().toString(), + url.toString(), + e.getMessage(), + sleepTime, + retries)); + try { + Thread.sleep(sleepTime); + } catch (InterruptedException ie) { + throw (IOException) new InterruptedIOException().initCause(e); + } } - /** - * Gets the rate limit. - * - * @param rateLimitTarget - * the rate limit target - * @return the rate limit - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Nonnull - GHRateLimit getRateLimit(@Nonnull RateLimitTarget rateLimitTarget) throws IOException { - // Even when explicitly asking for rate limit, restrict to sane query frequency - // return cached value if available - GHRateLimit output = sanityCachedRateLimit.get( - (currentValue) -> currentValue == null || currentValue.getRecord(rateLimitTarget).isExpired(), - () -> { - GHRateLimit result; - try { - final GitHubRequest request = GitHubRequest.newBuilder() - .rateLimit(RateLimitTarget.NONE) - .withApiUrl(getApiUrl()) - .withUrlPath("/rate_limit") - .build(); - result = this - .sendRequest(request, - (connectorResponse) -> GitHubResponse.parseBody(connectorResponse, - JsonRateLimit.class)) - .body().resources; - } catch (FileNotFoundException e) { - // For some versions of GitHub Enterprise, the rate_limit endpoint returns a 404. - LOGGER.log(FINE, "(%s) /rate_limit returned 404 Not Found.", sendRequestTraceId.get()); + private static GitHubConnectorRequest prepareConnectorRequest(GitHubRequest request, + AuthorizationProvider authorizationProvider) throws IOException { + GitHubRequest.Builder builder = request.toBuilder(); + // if the authentication is needed but no credential is given, try it anyway (so that some calls + // that do work with anonymous access in the reduced form should still work.) + if (!request.allHeaders().containsKey("Authorization")) { + String authorization = authorizationProvider.getEncodedAuthorization(); + if (authorization != null) { + builder.setHeader("Authorization", authorization); + } + } + if (request.header("Accept") == null) { + builder.setHeader("Accept", "application/vnd.github+json"); + } + builder.setHeader("Accept-Encoding", "gzip"); - // However some newer versions of GHE include rate limit header information - // If the header info is missing and the endpoint returns 404, fill the rate limit - // with unknown - result = GHRateLimit.fromRecord(GHRateLimit.UnknownLimitRecord.current(), rateLimitTarget); - } - return result; - }); - return updateRateLimit(output); + builder.setHeader("X-GitHub-Api-Version", "2022-11-28"); + + if (request.hasBody()) { + if (request.body() != null) { + builder.contentType(defaultString(request.contentType(), "application/x-www-form-urlencoded")); + } else { + builder.contentType("application/json"); + Map json = new HashMap<>(); + for (GitHubRequest.Entry e : request.args()) { + json.put(e.key, e.value); + } + builder.with(new ByteArrayInputStream(getMappingObjectWriter().writeValueAsBytes(json))); + } + + } + + return builder.build(); + } + + private static boolean shouldIgnoreBody(@Nonnull GitHubConnectorResponse connectorResponse) { + if (connectorResponse.statusCode() == HTTP_NOT_MODIFIED) { + // special case handling for 304 unmodified, as the content will be "" + return true; + } else if (connectorResponse.statusCode() == HTTP_ACCEPTED) { + + // Response code 202 means data is being generated or an action that can require some time is triggered. + // This happens in specific cases: + // statistics - See https://developer.github.com/v3/repos/statistics/#a-word-about-caching + // fork creation - See https://developer.github.com/v3/repos/forks/#create-a-fork + // workflow run cancellation - See https://docs.github.com/en/rest/reference/actions#cancel-a-workflow-run + + LOGGER.log(FINE, + () -> String.format("(%s) Received HTTP_ACCEPTED(202) from %s. Please try again in 5 seconds.", + sendRequestTraceId.get(), + connectorResponse.request().url().toString())); + return true; + } else { + return false; + } } /** - * Returns the most recently observed rate limit data. - * - * Generally, instead of calling this you should implement a {@link RateLimitChecker} or call + * Helper for {@link #getMappingObjectReader(GitHubConnectorResponse)}. * - * @return the most recently observed rate limit data. This may include expired or - * {@link GHRateLimit.UnknownLimitRecord} entries. - * @deprecated implement a {@link RateLimitChecker} and add it via - * {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}. + * @param root + * the root GitHub object for this reader + * @return an {@link ObjectReader} instance that can be further configured. */ @Nonnull - @Deprecated - GHRateLimit lastRateLimit() { - return rateLimit.get(); + static ObjectReader getMappingObjectReader(@Nonnull GitHub root) { + ObjectReader reader = getMappingObjectReader((GitHubConnectorResponse) null); + ((InjectableValues.Std) reader.getInjectableValues()).addValue(GitHub.class, root); + return reader; } /** - * Gets the current rate limit for an endpoint while trying not to actually make any remote requests unless - * absolutely necessary. + * Gets an {@link ObjectReader}. * - * If the {@link GHRateLimit.Record} for {@code urlPath} is not expired, it is returned. If the - * {@link GHRateLimit.Record} for {@code urlPath} is expired, {@link #getRateLimit()} will be called to get the - * current rate limit. + * Members of {@link InjectableValues} must be present even if {@code null}, otherwise classes expecting those + * values will fail to read. This differs from regular JSONProperties which provide defaults instead of failing. * - * @param rateLimitTarget - * the endpoint to get the rate limit for. + * Having one spot to create readers and having it take all injectable values is not a great long term solution but + * it is sufficient for this first cut. * - * @return the current rate limit data. {@link GHRateLimit.Record}s in this instance may be expired when returned. - * @throws IOException - * if there was an error getting current rate limit data. + * @param connectorResponse + * the {@link GitHubConnectorResponse} to inject for this reader. + * + * @return an {@link ObjectReader} instance that can be further configured. */ @Nonnull - GHRateLimit rateLimit(@Nonnull RateLimitTarget rateLimitTarget) throws IOException { - GHRateLimit result = rateLimit.get(); - // Most of the time rate limit is not expired, so try to avoid locking. - if (result.getRecord(rateLimitTarget).isExpired()) { - // if the rate limit is expired, synchronize to ensure - // only one call to getRateLimit() is made to refresh it. - synchronized (this) { - if (rateLimit.get().getRecord(rateLimitTarget).isExpired()) { - getRateLimit(rateLimitTarget); - } + static ObjectReader getMappingObjectReader(@CheckForNull GitHubConnectorResponse connectorResponse) { + Map injected = new HashMap<>(); + + // Required or many things break + injected.put(GitHubConnectorResponse.class.getName(), null); + injected.put(GitHub.class.getName(), null); + + if (connectorResponse != null) { + injected.put(GitHubConnectorResponse.class.getName(), connectorResponse); + GitHubConnectorRequest request = connectorResponse.request(); + // This is cheating, but it is an acceptable cheat for now. + if (request instanceof GitHubRequest) { + injected.putAll(((GitHubRequest) connectorResponse.request()).injectedMappingValues()); } - result = rateLimit.get(); } - return result; + return MAPPER.reader(new InjectableValues.Std(injected)); } /** - * Update the Rate Limit with the latest info from response header. - * - * Due to multi-threading, requests might complete out of order. This method calls - * {@link GHRateLimit#getMergedRateLimit(GHRateLimit)} to ensure the most current records are used. + * Gets an {@link ObjectWriter}. * - * @param observed - * {@link GHRateLimit.Record} constructed from the response header information + * @return an {@link ObjectWriter} instance that can be further configured. */ - private GHRateLimit updateRateLimit(@Nonnull GHRateLimit observed) { - GHRateLimit result = rateLimit.accumulateAndGet(observed, (current, x) -> current.getMergedRateLimit(x)); - LOGGER.log(FINEST, "Rate limit now: {0}", rateLimit.get()); - return result; + @Nonnull + static ObjectWriter getMappingObjectWriter() { + return MAPPER.writer(); } /** - * Tests the connection. - * - *

- * Verify that the API URL and credentials are valid to access this GitHub. + * Parses the instant. * - *

- * This method returns normally if the endpoint is reachable and verified to be GitHub API URL. Otherwise this - * method throws {@link IOException} to indicate the problem. + * @param timestamp + * the timestamp + * @return the instant + */ + static Instant parseInstant(String timestamp) { + if (timestamp == null) + return null; + + if (timestamp.charAt(4) == '/') { + // Unsure where this is used, but retained for compatibility. + return Instant.from(DATE_TIME_PARSER_SLASHES.parse(timestamp)); + } else { + return Instant.from(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(timestamp)); + } + } + + /** + * Parses the URL. * - * @throws IOException - * the io exception + * @param s + * the s + * @return the url */ - public void checkApiUrlValidity() throws IOException { + static URL parseURL(String s) { try { - this.fetch(GHApiInfo.class, "/").check(getApiUrl()); - } catch (IOException e) { - if (isPrivateModeEnabled()) { - throw (IOException) new IOException( - "GitHub Enterprise server (" + getApiUrl() + ") with private mode enabled").initCause(e); - } - throw e; + return s == null ? null : new URL(s); + } catch (MalformedURLException e) { + throw new IllegalStateException("Invalid URL: " + s); } } /** - * Gets the api url. + * Prints the instant. * - * @return the api url + * @param instant + * the instant + * @return the string */ - public String getApiUrl() { - return apiUrl; + static String printInstant(Instant instant) { + return DateTimeFormatter.ISO_INSTANT.format(instant.truncatedTo(ChronoUnit.SECONDS)); } /** - * Builds a {@link GitHubRequest}, sends the {@link GitHubRequest} to the server, and uses the {@link BodyHandler} - * to parse the response info and response body data into an instance of {@code T}. + * Convert Date to Instant or null. * - * @param - * the type of the parse body data. - * @param builder - * used to build the request that will be sent to the server. - * @param handler - * parse the response info and body data into a instance of {@code T}. If null, no parsing occurs and - * {@link GitHubResponse#body()} will return null. - * @return a {@link GitHubResponse} containing the parsed body data as a {@code T}. Parsed instance may be null. - * @throws IOException - * if an I/O Exception occurs + * @param date + * the date + * @return the date */ - @Nonnull - public GitHubResponse sendRequest(@Nonnull GitHubRequest.Builder builder, - @CheckForNull BodyHandler handler) throws IOException { - return sendRequest(builder.build(), handler); + static Instant toInstantOrNull(Date date) { + if (date == null) + return null; + + return date.toInstant(); } /** - * Sends the {@link GitHubRequest} to the server, and uses the {@link BodyHandler} to parse the response info and - * response body data into an instance of {@code T}. + * Unmodifiable list or null. * * @param - * the type of the parse body data. - * @param request - * the request that will be sent to the server. - * @param handler - * parse the response info and body data into a instance of {@code T}. If null, no parsing occurs and - * {@link GitHubResponse#body()} will return null. - * @return a {@link GitHubResponse} containing the parsed body data as a {@code T}. Parsed instance may be null. - * @throws IOException - * if an I/O Exception occurs + * the generic type + * @param list + * the list + * @return the list */ - @Nonnull - public GitHubResponse sendRequest(GitHubRequest request, @CheckForNull BodyHandler handler) - throws IOException { - // WARNING: This is an unsupported environment variable. - // The GitHubClient class is internal and may change at any time. - int retryCount = Math.max(DEFAULT_CONNECTION_ERROR_RETRIES, - Integer.getInteger(GitHubClient.class.getName() + ".retryCount", DEFAULT_CONNECTION_ERROR_RETRIES)); - - int retries = retryCount; - sendRequestTraceId.set(Integer.toHexString(request.hashCode())); - GitHubConnectorRequest connectorRequest = prepareConnectorRequest(request, authorizationProvider); - do { - GitHubConnectorResponse connectorResponse = null; - try { - logRequest(connectorRequest); - rateLimitChecker.checkRateLimit(this, request.rateLimitTarget()); - connectorResponse = connector.send(connectorRequest); - logResponse(connectorResponse); - noteRateLimit(request.rateLimitTarget(), connectorResponse); - detectKnownErrors(connectorResponse, request, handler != null); - logResponseBody(connectorResponse); - return createResponse(connectorResponse, handler); - } catch (RetryRequestException e) { - // retry requested by requested by error handler (rate limit handler for example) - if (retries > 0 && e.connectorRequest != null) { - connectorRequest = e.connectorRequest; - } - } catch (IOException e) { - throw interpretApiError(e, connectorRequest, connectorResponse); - } finally { - IOUtils.closeQuietly(connectorResponse); - } - } while (--retries >= 0); - - throw new GHIOException("Ran out of retries for URL: " + request.url().toString()); + static List unmodifiableListOrNull(List list) { + return list == null ? null : Collections.unmodifiableList(list); } - private void detectKnownErrors(GitHubConnectorResponse connectorResponse, - GitHubRequest request, - boolean detectStatusCodeError) throws IOException { - detectOTPRequired(connectorResponse); - detectInvalidCached404Response(connectorResponse, request); - detectExpiredToken(connectorResponse, request); - detectRedirect(connectorResponse, request); - if (rateLimitHandler.isError(connectorResponse)) { - rateLimitHandler.onError(connectorResponse); - throw new RetryRequestException(); - } else if (abuseLimitHandler.isError(connectorResponse)) { - abuseLimitHandler.onError(connectorResponse); - throw new RetryRequestException(); - } else if (detectStatusCodeError - && GitHubConnectorResponseErrorHandler.STATUS_HTTP_BAD_REQUEST_OR_GREATER.isError(connectorResponse)) { - GitHubConnectorResponseErrorHandler.STATUS_HTTP_BAD_REQUEST_OR_GREATER.onError(connectorResponse); - } + /** + * Unmodifiable map or null. + * + * @param + * the key type + * @param + * the value type + * @param map + * the map + * @return the map + */ + static Map unmodifiableMapOrNull(Map map) { + return map == null ? null : Collections.unmodifiableMap(map); } - private void detectExpiredToken(GitHubConnectorResponse connectorResponse, GitHubRequest request) - throws IOException { - if (connectorResponse.statusCode() != HTTP_UNAUTHORIZED) { - return; - } - String originalAuthorization = connectorResponse.request().header("Authorization"); - if (Objects.isNull(originalAuthorization) || originalAuthorization.isEmpty()) { - return; - } - GitHubConnectorRequest updatedRequest = prepareConnectorRequest(request, authorizationProvider); - String updatedAuthorization = updatedRequest.header("Authorization"); - if (!originalAuthorization.equals(updatedAuthorization)) { - throw new RetryRequestException(updatedRequest); - } - } + private final GitHubAbuseLimitHandler abuseLimitHandler; - private void detectRedirect(GitHubConnectorResponse connectorResponse, GitHubRequest request) throws IOException { - if (isRedirecting(connectorResponse.statusCode())) { - // For redirects, GitHub expects the Authorization header to be removed. - // GitHubConnector implementations can follow any redirects automatically as long as they remove the header - // as well. - // Okhttp does this. - // https://github.com/square/okhttp/blob/f9dfd4e8cc070ca2875a67d8f7ad939d95e7e296/okhttp/src/main/kotlin/okhttp3/internal/http/RetryAndFollowUpInterceptor.kt#L313-L318 - // GitHubClient always strips Authorization from detected redirects for security. - // This problem was discovered when upload-artifact@v4 was released as the new - // service we are redirected to for downloading the artifacts doesn't support - // having the Authorization header set. - // See also https://github.com/arduino/report-size-deltas/pull/83 for more context + // Cache of myself object. + private final String apiUrl; - GitHubConnectorRequest updatedRequest = prepareRedirectRequest(connectorResponse, request); - throw new RetryRequestException(updatedRequest); - } - } + private final AuthorizationProvider authorizationProvider; - private GitHubConnectorRequest prepareRedirectRequest(GitHubConnectorResponse connectorResponse, - GitHubRequest request) throws IOException { - URI requestUri = URI.create(request.url().toString()); - URI redirectedUri = getRedirectedUri(requestUri, connectorResponse); - // If we switch ports on the same host, we consider that as a different host - // This is slightly different from Redirect#NORMAL, but needed for local testing - boolean sameHost = redirectedUri.getHost().equalsIgnoreCase(request.url().getHost()) - && redirectedUri.getPort() == request.url().getPort(); + private GitHubConnector connector; - // mimicking the behavior of Redirect#NORMAL which was the behavior we used before - // Always redirect, except from HTTPS URLs to HTTP URLs. - if (!requestUri.getScheme().equalsIgnoreCase(redirectedUri.getScheme()) - && !"https".equalsIgnoreCase(redirectedUri.getScheme())) { - throw new HttpException("Attemped to redirect to a different scheme and the target scheme as not https.", - connectorResponse.statusCode(), - "Redirect", - connectorResponse.request().url().toString()); - } + @Nonnull + private final AtomicReference rateLimit = new AtomicReference<>(GHRateLimit.DEFAULT); - String redirectedMethod = getRedirectedMethod(connectorResponse.statusCode(), request.method()); + private final GitHubRateLimitChecker rateLimitChecker; - // let's build the new redirected request - GitHubRequest.Builder requestBuilder = request.toBuilder() - .setRawUrlPath(redirectedUri.toString()) - .method(redirectedMethod); - // if we redirect to a different host (even https), we remove the Authorization header - AuthorizationProvider provider = authorizationProvider; - if (!sameHost) { - requestBuilder.removeHeader("Authorization"); - provider = AuthorizationProvider.ANONYMOUS; - } - return prepareConnectorRequest(requestBuilder.build(), provider); - } + private final GitHubRateLimitHandler rateLimitHandler; - private static URI getRedirectedUri(URI requestUri, GitHubConnectorResponse connectorResponse) throws IOException { - URI redirectedURI; - redirectedURI = Optional.of(connectorResponse.header("Location")) - .map(URI::create) - .orElseThrow(() -> new IOException("Invalid redirection")); + @Nonnull + private GitHubSanityCachedValue sanityCachedIsCredentialValid = new GitHubSanityCachedValue<>(); - // redirect could be relative to original URL, but if not - // then redirect is used. - redirectedURI = requestUri.resolve(redirectedURI); - return redirectedURI; - } + @Nonnull + private final GitHubSanityCachedValue sanityCachedRateLimit = new GitHubSanityCachedValue<>(); - // This implements the exact same rules as the ones applied in jdk.internal.net.http.RedirectFilter - private static boolean isRedirecting(int statusCode) { - return statusCode == HTTP_MOVED_PERM || statusCode == HTTP_MOVED_TEMP || statusCode == 303 || statusCode == 307 - || statusCode == 308; - } + /** + * Instantiates a new git hub client. + * + * @param apiUrl + * the api url + * @param connector + * the connector + * @param rateLimitHandler + * the rate limit handler + * @param abuseLimitHandler + * the abuse limit handler + * @param rateLimitChecker + * the rate limit checker + * @param authorizationProvider + * the authorization provider + */ + GitHubClient(String apiUrl, + GitHubConnector connector, + GitHubRateLimitHandler rateLimitHandler, + GitHubAbuseLimitHandler abuseLimitHandler, + GitHubRateLimitChecker rateLimitChecker, + AuthorizationProvider authorizationProvider) { - // This implements the exact same rules as the ones applied in jdk.internal.net.http.RedirectFilter - private static String getRedirectedMethod(int statusCode, String originalMethod) { - switch (statusCode) { - case HTTP_MOVED_PERM : - case HTTP_MOVED_TEMP : - return originalMethod.equals("POST") ? "GET" : originalMethod; - case 303 : - return "GET"; - case 307 : - case 308 : - return originalMethod; - default : - return originalMethod; + if (apiUrl.endsWith("/")) { + apiUrl = apiUrl.substring(0, apiUrl.length() - 1); // normalize } - } - private static GitHubConnectorRequest prepareConnectorRequest(GitHubRequest request, - AuthorizationProvider authorizationProvider) throws IOException { - GitHubRequest.Builder builder = request.toBuilder(); - // if the authentication is needed but no credential is given, try it anyway (so that some calls - // that do work with anonymous access in the reduced form should still work.) - if (!request.allHeaders().containsKey("Authorization")) { - String authorization = authorizationProvider.getEncodedAuthorization(); - if (authorization != null) { - builder.setHeader("Authorization", authorization); - } - } - if (request.header("Accept") == null) { - builder.setHeader("Accept", "application/vnd.github+json"); + if (null == connector) { + connector = GitHubConnector.DEFAULT; } - builder.setHeader("Accept-Encoding", "gzip"); + this.apiUrl = apiUrl; + this.connector = connector; - builder.setHeader("X-GitHub-Api-Version", "2022-11-28"); + // Prefer credential configuration via provider + this.authorizationProvider = authorizationProvider; - if (request.hasBody()) { - if (request.body() != null) { - builder.contentType(defaultString(request.contentType(), "application/x-www-form-urlencoded")); - } else { - builder.contentType("application/json"); - Map json = new HashMap<>(); - for (GitHubRequest.Entry e : request.args()) { - json.put(e.key, e.value); - } - builder.with(new ByteArrayInputStream(getMappingObjectWriter().writeValueAsBytes(json))); - } + this.rateLimitHandler = rateLimitHandler; + this.abuseLimitHandler = abuseLimitHandler; + this.rateLimitChecker = rateLimitChecker; + } + /** + * Tests the connection. + * + *

+ * Verify that the API URL and credentials are valid to access this GitHub. + * + *

+ * This method returns normally if the endpoint is reachable and verified to be GitHub API URL. Otherwise this + * method throws {@link IOException} to indicate the problem. + * + * @throws IOException + * the io exception + */ + public void checkApiUrlValidity() throws IOException { + try { + this.fetch(GHApiInfo.class, "/").check(getApiUrl()); + } catch (IOException e) { + if (isPrivateModeEnabled()) { + throw (IOException) new IOException( + "GitHub Enterprise server (" + getApiUrl() + ") with private mode enabled").initCause(e); + } + throw e; } + } - return builder.build(); + /** + * Gets the api url. + * + * @return the api url + */ + public String getApiUrl() { + return apiUrl; } - private void logRequest(@Nonnull final GitHubConnectorRequest request) { - LOGGER.log(FINE, - () -> String.format("(%s) GitHub API request: %s %s", - sendRequestTraceId.get(), - request.method(), - request.url().toString())); + /** + * Gets the current full rate limit information from the server. + * + * For some versions of GitHub Enterprise, the {@code /rate_limit} endpoint returns a {@code 404 Not Found}. In that + * case, the most recent {@link GHRateLimit} information will be returned, including rate limit information returned + * in the response header for this request in if was present. + * + * For most use cases it would be better to implement a {@link RateLimitChecker} and add it via + * {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}. + * + * @return the rate limit + * @throws IOException + * the io exception + */ + @Nonnull + public GHRateLimit getRateLimit() throws IOException { + return getRateLimit(RateLimitTarget.NONE); } - private void logResponse(@Nonnull final GitHubConnectorResponse response) { - LOGGER.log(FINER, () -> { - return String.format("(%s) GitHub API response: %s", - sendRequestTraceId.get(), - response.request().url().toString(), - response.statusCode()); - }); + /** + * Is this an anonymous connection. + * + * @return {@code true} if operations that require authentication will fail. + */ + public boolean isAnonymous() { + try { + return getLogin() == null && this.authorizationProvider.getEncodedAuthorization() == null; + } catch (IOException e) { + // An exception here means that the provider failed to provide authorization parameters, + // basically meaning the same as "no auth" + return false; + } } - private void logResponseBody(@Nonnull final GitHubConnectorResponse response) { - LOGGER.log(FINEST, () -> { - String body; + /** + * Ensures that the credential for this client is valid. + * + * @return the boolean + */ + public boolean isCredentialValid() { + return sanityCachedIsCredentialValid.get(() -> { try { - body = GitHubResponse.getBodyAsString(response); - } catch (Throwable e) { - body = "Error reading response body"; + // If 404, ratelimit returns a default value. + // This works as credential test because invalid credentials returns 401, not 404 + getRateLimit(); + return Boolean.TRUE; + } catch (IOException e) { + LOGGER.log(FINE, + e, + () -> String.format("(%s) Exception validating credentials on %s with login '%s'", + sendRequestTraceId.get(), + getApiUrl(), + getLogin())); + return Boolean.FALSE; } - return String.format("(%s) GitHub API response body: %s", sendRequestTraceId.get(), body); - }); } - @Nonnull - private static GitHubResponse createResponse(@Nonnull GitHubConnectorResponse connectorResponse, - @CheckForNull BodyHandler handler) throws IOException { - T body = null; - if (handler != null) { - if (!shouldIgnoreBody(connectorResponse)) { - body = handler.apply(connectorResponse); - } - } - return new GitHubResponse<>(connectorResponse, body); - } - - private static boolean shouldIgnoreBody(@Nonnull GitHubConnectorResponse connectorResponse) { - if (connectorResponse.statusCode() == HTTP_NOT_MODIFIED) { - // special case handling for 304 unmodified, as the content will be "" - return true; - } else if (connectorResponse.statusCode() == HTTP_ACCEPTED) { - - // Response code 202 means data is being generated or an action that can require some time is triggered. - // This happens in specific cases: - // statistics - See https://developer.github.com/v3/repos/statistics/#a-word-about-caching - // fork creation - See https://developer.github.com/v3/repos/forks/#create-a-fork - // workflow run cancellation - See https://docs.github.com/en/rest/reference/actions#cancel-a-workflow-run - - LOGGER.log(FINE, - () -> String.format("(%s) Received HTTP_ACCEPTED(202) from %s. Please try again in 5 seconds.", - sendRequestTraceId.get(), - connectorResponse.request().url().toString())); - return true; - } else { - return false; - } + /** + * Is this an always offline "connection". + * + * @return {@code true} if this is an always offline "connection". + */ + public boolean isOffline() { + return connector == GitHubConnector.OFFLINE; } /** - * Handle API error by either throwing it or by returning normally to retry. + * Sends the {@link GitHubRequest} to the server, and uses the {@link BodyHandler} to parse the response info and + * response body data into an instance of {@code T}. + * + * @param + * the type of the parse body data. + * @param request + * the request that will be sent to the server. + * @param handler + * parse the response info and body data into a instance of {@code T}. If null, no parsing occurs and + * {@link GitHubResponse#body()} will return null. + * @return a {@link GitHubResponse} containing the parsed body data as a {@code T}. Parsed instance may be null. + * @throws IOException + * if an I/O Exception occurs */ - private static IOException interpretApiError(IOException e, - @Nonnull GitHubConnectorRequest connectorRequest, - @CheckForNull GitHubConnectorResponse connectorResponse) { - // If we're already throwing a GHIOException, pass through - if (e instanceof GHIOException) { - return e; - } - - int statusCode = -1; - String message = null; - Map> headers = new HashMap<>(); - String errorMessage = null; + @Nonnull + public GitHubResponse sendRequest(GitHubRequest request, @CheckForNull BodyHandler handler) + throws IOException { + // WARNING: This is an unsupported environment variable. + // The GitHubClient class is internal and may change at any time. + int retryCount = Math.max(DEFAULT_CONNECTION_ERROR_RETRIES, + Integer.getInteger(GitHubClient.class.getName() + ".retryCount", DEFAULT_CONNECTION_ERROR_RETRIES)); - if (connectorResponse != null) { - statusCode = connectorResponse.statusCode(); - message = connectorResponse.header("Status"); - headers = connectorResponse.allHeaders(); - if (connectorResponse.statusCode() >= HTTP_BAD_REQUEST) { - errorMessage = GitHubResponse.getBodyAsStringOrNull(connectorResponse); + int retries = retryCount; + sendRequestTraceId.set(Integer.toHexString(request.hashCode())); + GitHubConnectorRequest connectorRequest = prepareConnectorRequest(request, authorizationProvider); + do { + GitHubConnectorResponse connectorResponse = null; + try { + logRequest(connectorRequest); + rateLimitChecker.checkRateLimit(this, request.rateLimitTarget()); + connectorResponse = connector.send(connectorRequest); + logResponse(connectorResponse); + noteRateLimit(request.rateLimitTarget(), connectorResponse); + detectKnownErrors(connectorResponse, request, handler != null); + logResponseBody(connectorResponse); + return createResponse(connectorResponse, handler); + } catch (RetryRequestException e) { + // retry requested by requested by error handler (rate limit handler for example) + if (retries > 0 && e.connectorRequest != null) { + connectorRequest = e.connectorRequest; + } + } catch (IOException e) { + throw interpretApiError(e, connectorRequest, connectorResponse); + } finally { + IOUtils.closeQuietly(connectorResponse); } - } + } while (--retries >= 0); - if (errorMessage != null) { - if (e instanceof FileNotFoundException) { - // pass through 404 Not Found to allow the caller to handle it intelligently - e = new GHFileNotFoundException(e.getMessage() + " " + errorMessage, e) - .withResponseHeaderFields(headers); - } else if (statusCode >= 0) { - e = new HttpException(errorMessage, statusCode, message, connectorRequest.url().toString(), e); - } else { - e = new GHIOException(errorMessage).withResponseHeaderFields(headers); - } - } else if (!(e instanceof FileNotFoundException)) { - e = new HttpException(statusCode, message, connectorRequest.url().toString(), e); - } - return e; + throw new GHIOException("Ran out of retries for URL: " + request.url().toString()); } - private static void logRetryConnectionError(IOException e, URL url, int retries) throws IOException { - // There are a range of connection errors where we want to wait a moment and just automatically retry - - // WARNING: These are unsupported environment variables. - // The GitHubClient class is internal and may change at any time. - int minRetryInterval = Math.max(DEFAULT_MINIMUM_RETRY_MILLIS, - Integer.getInteger(GitHubClient.class.getName() + ".minRetryInterval", DEFAULT_MINIMUM_RETRY_MILLIS)); - int maxRetryInterval = Math.max(DEFAULT_MAXIMUM_RETRY_MILLIS, - Integer.getInteger(GitHubClient.class.getName() + ".maxRetryInterval", DEFAULT_MAXIMUM_RETRY_MILLIS)); - - long sleepTime = maxRetryInterval <= minRetryInterval - ? minRetryInterval - : ThreadLocalRandom.current().nextLong(minRetryInterval, maxRetryInterval); + /** + * Builds a {@link GitHubRequest}, sends the {@link GitHubRequest} to the server, and uses the {@link BodyHandler} + * to parse the response info and response body data into an instance of {@code T}. + * + * @param + * the type of the parse body data. + * @param builder + * used to build the request that will be sent to the server. + * @param handler + * parse the response info and body data into a instance of {@code T}. If null, no parsing occurs and + * {@link GitHubResponse#body()} will return null. + * @return a {@link GitHubResponse} containing the parsed body data as a {@code T}. Parsed instance may be null. + * @throws IOException + * if an I/O Exception occurs + */ + @Nonnull + public GitHubResponse sendRequest(@Nonnull GitHubRequest.Builder builder, + @CheckForNull BodyHandler handler) throws IOException { + return sendRequest(builder.build(), handler); + } - LOGGER.log(INFO, - () -> String.format( - "(%s) %s while connecting to %s: '%s'. Sleeping %d milliseconds before retrying (%d retries remaining)", - sendRequestTraceId.get(), - e.getClass().toString(), - url.toString(), - e.getMessage(), - sleepTime, - retries)); - try { - Thread.sleep(sleepTime); - } catch (InterruptedException ie) { - throw (IOException) new InterruptedIOException().initCause(e); + private void detectExpiredToken(GitHubConnectorResponse connectorResponse, GitHubRequest request) + throws IOException { + if (connectorResponse.statusCode() != HTTP_UNAUTHORIZED) { + return; + } + String originalAuthorization = connectorResponse.request().header("Authorization"); + if (Objects.isNull(originalAuthorization) || originalAuthorization.isEmpty()) { + return; + } + GitHubConnectorRequest updatedRequest = prepareConnectorRequest(request, authorizationProvider); + String updatedAuthorization = updatedRequest.header("Authorization"); + if (!originalAuthorization.equals(updatedAuthorization)) { + throw new RetryRequestException(updatedRequest); } } @@ -772,52 +734,46 @@ private void detectInvalidCached404Response(GitHubConnectorResponse connectorRes } } - private void noteRateLimit(@Nonnull RateLimitTarget rateLimitTarget, - @Nonnull GitHubConnectorResponse connectorResponse) { - try { - int limit = connectorResponse.parseInt("X-RateLimit-Limit"); - int remaining = connectorResponse.parseInt("X-RateLimit-Remaining"); - int reset = connectorResponse.parseInt("X-RateLimit-Reset"); - GHRateLimit.Record observed = new GHRateLimit.Record(limit, remaining, reset, connectorResponse); - updateRateLimit(GHRateLimit.fromRecord(observed, rateLimitTarget)); - } catch (NumberFormatException e) { - LOGGER.log(FINER, - () -> String.format("(%s) Missing or malformed X-RateLimit header: %s", - sendRequestTraceId.get(), - e.getMessage())); + private void detectKnownErrors(GitHubConnectorResponse connectorResponse, + GitHubRequest request, + boolean detectStatusCodeError) throws IOException { + detectOTPRequired(connectorResponse); + detectInvalidCached404Response(connectorResponse, request); + detectExpiredToken(connectorResponse, request); + detectRedirect(connectorResponse, request); + if (rateLimitHandler.isError(connectorResponse)) { + rateLimitHandler.onError(connectorResponse); + throw new RetryRequestException(); + } else if (abuseLimitHandler.isError(connectorResponse)) { + abuseLimitHandler.onError(connectorResponse); + throw new RetryRequestException(); + } else if (detectStatusCodeError + && GitHubConnectorResponseErrorHandler.STATUS_HTTP_BAD_REQUEST_OR_GREATER.isError(connectorResponse)) { + GitHubConnectorResponseErrorHandler.STATUS_HTTP_BAD_REQUEST_OR_GREATER.onError(connectorResponse); } } - private static void detectOTPRequired(@Nonnull GitHubConnectorResponse connectorResponse) throws GHIOException { - // 401 Unauthorized == bad creds or OTP request - if (connectorResponse.statusCode() == HTTP_UNAUTHORIZED) { - // In the case of a user with 2fa enabled, a header with X-GitHub-OTP - // will be returned indicating the user needs to respond with an otp - if (connectorResponse.header("X-GitHub-OTP") != null) { - throw new GHOTPRequiredException().withResponseHeaderFields(connectorResponse.allHeaders()); - } - } - } + private void detectRedirect(GitHubConnectorResponse connectorResponse, GitHubRequest request) throws IOException { + if (isRedirecting(connectorResponse.statusCode())) { + // For redirects, GitHub expects the Authorization header to be removed. + // GitHubConnector implementations can follow any redirects automatically as long as they remove the header + // as well. + // Okhttp does this. + // https://github.com/square/okhttp/blob/f9dfd4e8cc070ca2875a67d8f7ad939d95e7e296/okhttp/src/main/kotlin/okhttp3/internal/http/RetryAndFollowUpInterceptor.kt#L313-L318 + // GitHubClient always strips Authorization from detected redirects for security. + // This problem was discovered when upload-artifact@v4 was released as the new + // service we are redirected to for downloading the artifacts doesn't support + // having the Authorization header set. + // See also https://github.com/arduino/report-size-deltas/pull/83 for more context - /** - * Require credential. - */ - void requireCredential() { - if (isAnonymous()) - throw new IllegalStateException( - "This operation requires a credential but none is given to the GitHub constructor"); + GitHubConnectorRequest updatedRequest = prepareRedirectRequest(connectorResponse, request); + throw new RetryRequestException(updatedRequest); + } } - private static class GHApiInfo { - private String rate_limit_url; - - void check(String apiUrl) throws IOException { - if (rate_limit_url == null) - throw new IOException(apiUrl + " doesn't look like GitHub API URL"); - - // make sure that the URL is legitimate - new URL(rate_limit_url); - } + private T fetch(Class type, String urlPath) throws IOException { + GitHubRequest request = GitHubRequest.newBuilder().withApiUrl(getApiUrl()).withUrlPath(urlPath).build(); + return sendRequest(request, (connectorResponse) -> GitHubResponse.parseBody(connectorResponse, type)).body(); } /** @@ -857,183 +813,229 @@ private boolean isPrivateModeEnabled() { } } - /** - * Parses the URL. - * - * @param s - * the s - * @return the url - */ - static URL parseURL(String s) { + private void logRequest(@Nonnull final GitHubConnectorRequest request) { + LOGGER.log(FINE, + () -> String.format("(%s) GitHub API request: %s %s", + sendRequestTraceId.get(), + request.method(), + request.url().toString())); + } + + private void logResponse(@Nonnull final GitHubConnectorResponse response) { + LOGGER.log(FINER, () -> { + return String.format("(%s) GitHub API response: %s", + sendRequestTraceId.get(), + response.request().url().toString(), + response.statusCode()); + }); + } + + private void logResponseBody(@Nonnull final GitHubConnectorResponse response) { + LOGGER.log(FINEST, () -> { + String body; + try { + response.setBodyStreamRereadable(); + body = GitHubResponse.getBodyAsString(response); + } catch (Throwable e) { + body = "Error reading response body"; + } + return String.format("(%s) GitHub API response body: %s", sendRequestTraceId.get(), body); + + }); + } + + private void noteRateLimit(@Nonnull RateLimitTarget rateLimitTarget, + @Nonnull GitHubConnectorResponse connectorResponse) { try { - return s == null ? null : new URL(s); - } catch (MalformedURLException e) { - throw new IllegalStateException("Invalid URL: " + s); + int limit = connectorResponse.parseInt("X-RateLimit-Limit"); + int remaining = connectorResponse.parseInt("X-RateLimit-Remaining"); + int reset = connectorResponse.parseInt("X-RateLimit-Reset"); + GHRateLimit.Record observed = new GHRateLimit.Record(limit, remaining, reset, connectorResponse); + updateRateLimit(GHRateLimit.fromRecord(observed, rateLimitTarget)); + } catch (NumberFormatException e) { + LOGGER.log(FINER, + () -> String.format("(%s) Missing or malformed X-RateLimit header: %s", + sendRequestTraceId.get(), + e.getMessage())); } } - /** - * Parses the date. - * - * @param timestamp - * the timestamp - * @return the date - */ - static Date parseDate(String timestamp) { - if (timestamp == null) - return null; + private GitHubConnectorRequest prepareRedirectRequest(GitHubConnectorResponse connectorResponse, + GitHubRequest request) throws IOException { + URI requestUri = URI.create(request.url().toString()); + URI redirectedUri = getRedirectedUri(requestUri, connectorResponse); + // If we switch ports on the same host, we consider that as a different host + // This is slightly different from Redirect#NORMAL, but needed for local testing + boolean sameHost = redirectedUri.getHost().equalsIgnoreCase(request.url().getHost()) + && redirectedUri.getPort() == request.url().getPort(); - return Date.from(parseInstant(timestamp)); - } + // mimicking the behavior of Redirect#NORMAL which was the behavior we used before + // Always redirect, except from HTTPS URLs to HTTP URLs. + if (!requestUri.getScheme().equalsIgnoreCase(redirectedUri.getScheme()) + && !"https".equalsIgnoreCase(redirectedUri.getScheme())) { + throw new HttpException("Attemped to redirect to a different scheme and the target scheme as not https.", + connectorResponse.statusCode(), + "Redirect", + connectorResponse.request().url().toString()); + } - /** - * Parses the instant. - * - * @param timestamp - * the timestamp - * @return the instant - */ - static Instant parseInstant(String timestamp) { - if (timestamp == null) - return null; + String redirectedMethod = getRedirectedMethod(connectorResponse.statusCode(), request.method()); - if (timestamp.charAt(4) == '/') { - // Unsure where this is used, but retained for compatibility. - return Instant.from(DATE_TIME_PARSER_SLASHES.parse(timestamp)); - } else { - return Instant.from(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(timestamp)); + // let's build the new redirected request + GitHubRequest.Builder requestBuilder = request.toBuilder() + .setRawUrlPath(redirectedUri.toString()) + .method(redirectedMethod); + // if we redirect to a different host (even https), we remove the Authorization header + AuthorizationProvider provider = authorizationProvider; + if (!sameHost) { + requestBuilder.removeHeader("Authorization"); + provider = AuthorizationProvider.ANONYMOUS; } + return prepareConnectorRequest(requestBuilder.build(), provider); } /** - * Prints the date. + * Update the Rate Limit with the latest info from response header. * - * @param dt - * the dt - * @return the string - */ - static String printDate(Date dt) { - return DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(dt.getTime()).truncatedTo(ChronoUnit.SECONDS)); - } - - /** - * Gets an {@link ObjectWriter}. + * Due to multi-threading, requests might complete out of order. This method calls + * {@link GHRateLimit#getMergedRateLimit(GHRateLimit)} to ensure the most current records are used. * - * @return an {@link ObjectWriter} instance that can be further configured. + * @param observed + * {@link GHRateLimit.Record} constructed from the response header information */ - @Nonnull - static ObjectWriter getMappingObjectWriter() { - return MAPPER.writer(); + private GHRateLimit updateRateLimit(@Nonnull GHRateLimit observed) { + GHRateLimit result = rateLimit.accumulateAndGet(observed, (current, x) -> current.getMergedRateLimit(x)); + LOGGER.log(FINEST, "Rate limit now: {0}", rateLimit.get()); + return result; } /** - * Helper for {@link #getMappingObjectReader(GitHubConnectorResponse)}. + * Gets the encoded authorization. * - * @param root - * the root GitHub object for this reader - * @return an {@link ObjectReader} instance that can be further configured. + * @return the encoded authorization + * @throws IOException + * Signals that an I/O exception has occurred. */ - @Nonnull - static ObjectReader getMappingObjectReader(@Nonnull GitHub root) { - ObjectReader reader = getMappingObjectReader((GitHubConnectorResponse) null); - ((InjectableValues.Std) reader.getInjectableValues()).addValue(GitHub.class, root); - return reader; + @CheckForNull + String getEncodedAuthorization() throws IOException { + return authorizationProvider.getEncodedAuthorization(); } /** - * Gets an {@link ObjectReader}. - * - * Members of {@link InjectableValues} must be present even if {@code null}, otherwise classes expecting those - * values will fail to read. This differs from regular JSONProperties which provide defaults instead of failing. - * - * Having one spot to create readers and having it take all injectable values is not a great long term solution but - * it is sufficient for this first cut. - * - * @param connectorResponse - * the {@link GitHubConnectorResponse} to inject for this reader. + * Gets the login. * - * @return an {@link ObjectReader} instance that can be further configured. + * @return the login */ - @Nonnull - static ObjectReader getMappingObjectReader(@CheckForNull GitHubConnectorResponse connectorResponse) { - Map injected = new HashMap<>(); + String getLogin() { + try { + if (this.authorizationProvider instanceof UserAuthorizationProvider + && this.authorizationProvider.getEncodedAuthorization() != null) { - // Required or many things break - injected.put(GitHubConnectorResponse.class.getName(), null); - injected.put(GitHub.class.getName(), null); + UserAuthorizationProvider userAuthorizationProvider = (UserAuthorizationProvider) this.authorizationProvider; - if (connectorResponse != null) { - injected.put(GitHubConnectorResponse.class.getName(), connectorResponse); - GitHubConnectorRequest request = connectorResponse.request(); - // This is cheating, but it is an acceptable cheat for now. - if (request instanceof GitHubRequest) { - injected.putAll(((GitHubRequest) connectorResponse.request()).injectedMappingValues()); + return userAuthorizationProvider.getLogin(); } + } catch (IOException e) { } - return MAPPER.reader(new InjectableValues.Std(injected)); + return null; } /** - * Unmodifiable map or null. + * Gets the rate limit. * - * @param - * the key type - * @param - * the value type - * @param map - * the map - * @return the map + * @param rateLimitTarget + * the rate limit target + * @return the rate limit + * @throws IOException + * Signals that an I/O exception has occurred. */ - static Map unmodifiableMapOrNull(Map map) { - return map == null ? null : Collections.unmodifiableMap(map); + @Nonnull + GHRateLimit getRateLimit(@Nonnull RateLimitTarget rateLimitTarget) throws IOException { + // Even when explicitly asking for rate limit, restrict to sane query frequency + // return cached value if available + GHRateLimit output = sanityCachedRateLimit.get( + (currentValue) -> currentValue == null || currentValue.getRecord(rateLimitTarget).isExpired(), + () -> { + GHRateLimit result; + try { + final GitHubRequest request = GitHubRequest.newBuilder() + .rateLimit(RateLimitTarget.NONE) + .withApiUrl(getApiUrl()) + .withUrlPath("/rate_limit") + .build(); + result = this + .sendRequest(request, + (connectorResponse) -> GitHubResponse.parseBody(connectorResponse, + JsonRateLimit.class)) + .body().resources; + } catch (FileNotFoundException e) { + // For some versions of GitHub Enterprise, the rate_limit endpoint returns a 404. + LOGGER.log(FINE, "(%s) /rate_limit returned 404 Not Found.", sendRequestTraceId.get()); + + // However some newer versions of GHE include rate limit header information + // If the header info is missing and the endpoint returns 404, fill the rate limit + // with unknown + result = GHRateLimit.fromRecord(GHRateLimit.UnknownLimitRecord.current(), rateLimitTarget); + } + return result; + }); + return updateRateLimit(output); } /** - * Unmodifiable list or null. + * Returns the most recently observed rate limit data. * - * @param - * the generic type - * @param list - * the list - * @return the list + * Generally, instead of calling this you should implement a {@link RateLimitChecker} or call + * + * @return the most recently observed rate limit data. This may include expired or + * {@link GHRateLimit.UnknownLimitRecord} entries. + * @deprecated implement a {@link RateLimitChecker} and add it via + * {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}. */ - static List unmodifiableListOrNull(List list) { - return list == null ? null : Collections.unmodifiableList(list); + @Nonnull + @Deprecated + GHRateLimit lastRateLimit() { + return rateLimit.get(); } /** - * The Class RetryRequestException. + * Gets the current rate limit for an endpoint while trying not to actually make any remote requests unless + * absolutely necessary. + * + * If the {@link GHRateLimit.Record} for {@code urlPath} is not expired, it is returned. If the + * {@link GHRateLimit.Record} for {@code urlPath} is expired, {@link #getRateLimit()} will be called to get the + * current rate limit. + * + * @param rateLimitTarget + * the endpoint to get the rate limit for. + * + * @return the current rate limit data. {@link GHRateLimit.Record}s in this instance may be expired when returned. + * @throws IOException + * if there was an error getting current rate limit data. */ - static class RetryRequestException extends IOException { - - /** The connector request. */ - final GitHubConnectorRequest connectorRequest; - - /** - * Instantiates a new retry request exception. - */ - RetryRequestException() { - this(null); - } - - /** - * Instantiates a new retry request exception. - * - * @param connectorRequest - * the connector request - */ - RetryRequestException(GitHubConnectorRequest connectorRequest) { - this.connectorRequest = connectorRequest; + @Nonnull + GHRateLimit rateLimit(@Nonnull RateLimitTarget rateLimitTarget) throws IOException { + GHRateLimit result = rateLimit.get(); + // Most of the time rate limit is not expired, so try to avoid locking. + if (result.getRecord(rateLimitTarget).isExpired()) { + // if the rate limit is expired, synchronize to ensure + // only one call to getRateLimit() is made to refresh it. + synchronized (this) { + if (rateLimit.get().getRecord(rateLimitTarget).isExpired()) { + getRateLimit(rateLimitTarget); + } + } + result = rateLimit.get(); } + return result; } /** - * Represents a supplier of results that can throw. - * - * @param - * the type of results supplied by this supplier + * Require credential. */ - @FunctionalInterface - interface BodyHandler extends FunctionThrows { + void requireCredential() { + if (isAnonymous()) + throw new IllegalStateException( + "This operation requires a credential but none is given to the GitHub constructor"); } } diff --git a/src/main/java/org/kohsuke/github/GitHubConnectorResponseErrorHandler.java b/src/main/java/org/kohsuke/github/GitHubConnectorResponseErrorHandler.java index 0a7f7fa9fb..cb729e321c 100644 --- a/src/main/java/org/kohsuke/github/GitHubConnectorResponseErrorHandler.java +++ b/src/main/java/org/kohsuke/github/GitHubConnectorResponseErrorHandler.java @@ -34,34 +34,6 @@ abstract class GitHubConnectorResponseErrorHandler { */ public static final int TOO_MANY_REQUESTS = 429; - /** - * Called to detect an error handled by this handler. - * - * @param connectorResponse - * the connector response - * @return {@code true} if there is an error and {@link #onError(GitHubConnectorResponse)} should be called - * @throws IOException - * Signals that an I/O exception has occurred. - */ - abstract boolean isError(@Nonnull GitHubConnectorResponse connectorResponse) throws IOException; - - /** - * Called when the library encounters HTTP error matching {@link #isError(GitHubConnectorResponse)} - * - *

- * Any exception thrown from this method will cause the request to fail, and the caller of github-api will receive - * an exception. If this method returns normally, another request will be attempted. For that to make sense, the - * implementation needs to wait for some time. - * - * @param connectorResponse - * Response information for this request. - * - * @throws IOException - * the io exception - * @see API documentation from GitHub - */ - public abstract void onError(@Nonnull GitHubConnectorResponse connectorResponse) throws IOException; - /** The status http bad request or greater. */ static GitHubConnectorResponseErrorHandler STATUS_HTTP_BAD_REQUEST_OR_GREATER = new GitHubConnectorResponseErrorHandler() { private static final String CONTENT_TYPE = "Content-type"; @@ -111,4 +83,32 @@ private boolean isServiceDown(GitHubConnectorResponse connectorResponse) throws return false; } }; + + /** + * Called when the library encounters HTTP error matching {@link #isError(GitHubConnectorResponse)} + * + *

+ * Any exception thrown from this method will cause the request to fail, and the caller of github-api will receive + * an exception. If this method returns normally, another request will be attempted. For that to make sense, the + * implementation needs to wait for some time. + * + * @param connectorResponse + * Response information for this request. + * + * @throws IOException + * the io exception + * @see API documentation from GitHub + */ + public abstract void onError(@Nonnull GitHubConnectorResponse connectorResponse) throws IOException; + + /** + * Called to detect an error handled by this handler. + * + * @param connectorResponse + * the connector response + * @return {@code true} if there is an error and {@link #onError(GitHubConnectorResponse)} should be called + * @throws IOException + * Signals that an I/O exception has occurred. + */ + abstract boolean isError(@Nonnull GitHubConnectorResponse connectorResponse) throws IOException; } diff --git a/src/main/java/org/kohsuke/github/GitHubInteractiveObject.java b/src/main/java/org/kohsuke/github/GitHubInteractiveObject.java index a34f6485e7..d1a8aebdc7 100644 --- a/src/main/java/org/kohsuke/github/GitHubInteractiveObject.java +++ b/src/main/java/org/kohsuke/github/GitHubInteractiveObject.java @@ -15,7 +15,7 @@ * * @author Liam Newman */ -abstract class GitHubInteractiveObject { +abstract class GitHubInteractiveObject extends GitHubBridgeAdapterObject { @JacksonInject @CheckForNull private transient final GitHub root; @@ -38,22 +38,21 @@ abstract class GitHubInteractiveObject { } /** - * Get the root {@link GitHub} instance for this object. + * Object is offline. * - * @return the root {@link GitHub} instance + * @return true if GitHub instance is null or offline. */ - @NonNull - GitHub root() { - return Objects.requireNonNull(root, - "The root GitHub reference for this instance is null. Probably caused by deserializing this class without using a GitHub instance. If you must do this, use the MappingObjectReader from GitHub.getMappingObjectReader()."); + boolean isOffline() { + return root == null || root.isOffline(); } /** - * Object is offline. + * Get the root {@link GitHub} instance for this object. * - * @return true if GitHub instance is null or offline. + * @return the root {@link GitHub} instance */ - boolean isOffline() { - return root == null || root.isOffline(); + @NonNull GitHub root() { + return Objects.requireNonNull(root, + "The root GitHub reference for this instance is null. Probably caused by deserializing this class without using a GitHub instance. If you must do this, use the MappingObjectReader from GitHub.getMappingObjectReader()."); } } diff --git a/src/main/java/org/kohsuke/github/GitHubPageContentsIterable.java b/src/main/java/org/kohsuke/github/GitHubPageContentsIterable.java index 3239f6f71a..f8fc7e4907 100644 --- a/src/main/java/org/kohsuke/github/GitHubPageContentsIterable.java +++ b/src/main/java/org/kohsuke/github/GitHubPageContentsIterable.java @@ -19,10 +19,29 @@ */ class GitHubPageContentsIterable extends PagedIterable { + /** + * This class is not thread-safe. Any one instance should only be called from a single thread. + */ + private class GitHubPageContentsIterator extends PagedIterator { + + public GitHubPageContentsIterator(GitHubPageIterator iterator, Consumer itemInitializer) { + super(iterator, itemInitializer); + } + + /** + * Gets the {@link GitHubResponse} for the last page received. + * + * @return the {@link GitHubResponse} for the last page received. + */ + private GitHubResponse lastResponse() { + return ((GitHubPageIterator) base).finalResponse(); + } + } + private final GitHubClient client; - private final GitHubRequest request; - private final Class receiverType; private final Consumer itemInitializer; + private final Class receiverType; + private final GitHubRequest request; /** * Instantiates a new git hub page contents iterable. @@ -71,23 +90,4 @@ GitHubResponse toResponse() throws IOException { GitHubResponse lastResponse = iterator.lastResponse(); return new GitHubResponse<>(lastResponse, items); } - - /** - * This class is not thread-safe. Any one instance should only be called from a single thread. - */ - private class GitHubPageContentsIterator extends PagedIterator { - - public GitHubPageContentsIterator(GitHubPageIterator iterator, Consumer itemInitializer) { - super(iterator, itemInitializer); - } - - /** - * Gets the {@link GitHubResponse} for the last page received. - * - * @return the {@link GitHubResponse} for the last page received. - */ - private GitHubResponse lastResponse() { - return ((GitHubPageIterator) base).finalResponse(); - } - } } diff --git a/src/main/java/org/kohsuke/github/GitHubPageIterator.java b/src/main/java/org/kohsuke/github/GitHubPageIterator.java index 23a6198445..4a831bf3f8 100644 --- a/src/main/java/org/kohsuke/github/GitHubPageIterator.java +++ b/src/main/java/org/kohsuke/github/GitHubPageIterator.java @@ -23,8 +23,41 @@ */ class GitHubPageIterator implements Iterator { + /** + * Loads paginated resources. + * + * @param + * type of each page (not the items in the page). + * @param client + * the {@link GitHubClient} from which to request responses + * @param type + * type of each page (not the items in the page). + * @param request + * the request + * @param pageSize + * the page size + * @return iterator + */ + static GitHubPageIterator create(GitHubClient client, Class type, GitHubRequest request, int pageSize) { + + if (pageSize > 0) { + GitHubRequest.Builder builder = request.toBuilder().with("per_page", pageSize); + request = builder.build(); + } + + if (!"GET".equals(request.method())) { + throw new IllegalArgumentException("Request method \"GET\" is required for page iterator."); + } + + return new GitHubPageIterator<>(client, type, request); + } private final GitHubClient client; - private final Class type; + + /** + * When done iterating over pages, it is on rare occasions useful to be able to get information from the final + * response that was retrieved. + */ + private GitHubResponse finalResponse = null; /** * The page that will be returned when {@link #next()} is called. @@ -44,11 +77,7 @@ class GitHubPageIterator implements Iterator { */ private GitHubRequest nextRequest; - /** - * When done iterating over pages, it is on rare occasions useful to be able to get information from the final - * response that was retrieved. - */ - private GitHubResponse finalResponse = null; + private final Class type; private GitHubPageIterator(GitHubClient client, Class type, GitHubRequest request) { this.client = client; @@ -57,32 +86,15 @@ private GitHubPageIterator(GitHubClient client, Class type, GitHubRequest req } /** - * Loads paginated resources. + * On rare occasions the final response from iterating is needed. * - * @param - * type of each page (not the items in the page). - * @param client - * the {@link GitHubClient} from which to request responses - * @param type - * type of each page (not the items in the page). - * @param request - * the request - * @param pageSize - * the page size - * @return iterator + * @return the final response of the iterator. */ - static GitHubPageIterator create(GitHubClient client, Class type, GitHubRequest request, int pageSize) { - - if (pageSize > 0) { - GitHubRequest.Builder builder = request.toBuilder().with("per_page", pageSize); - request = builder.build(); - } - - if (!"GET".equals(request.method())) { - throw new IllegalArgumentException("Request method \"GET\" is required for page iterator."); + public GitHubResponse finalResponse() { + if (hasNext()) { + throw new GHException("Final response is not available until after iterator is done."); } - - return new GitHubPageIterator<>(client, type, request); + return finalResponse; } /** @@ -109,18 +121,6 @@ public T next() { return result; } - /** - * On rare occasions the final response from iterating is needed. - * - * @return the final response of the iterator. - */ - public GitHubResponse finalResponse() { - if (hasNext()) { - throw new GHException("Final response is not available until after iterator is done."); - } - return finalResponse; - } - /** * Fetch is called at the start of {@link #hasNext()} or {@link #next()} to fetch another page of data if it is * needed. diff --git a/src/main/java/org/kohsuke/github/GitHubRateLimitChecker.java b/src/main/java/org/kohsuke/github/GitHubRateLimitChecker.java index 576cd6a660..00ab62ef8b 100644 --- a/src/main/java/org/kohsuke/github/GitHubRateLimitChecker.java +++ b/src/main/java/org/kohsuke/github/GitHubRateLimitChecker.java @@ -32,11 +32,10 @@ */ class GitHubRateLimitChecker { - @Nonnull - private final RateLimitChecker core; + private static final Logger LOGGER = Logger.getLogger(GitHubRateLimitChecker.class.getName()); @Nonnull - private final RateLimitChecker search; + private final RateLimitChecker core; @Nonnull private final RateLimitChecker graphql; @@ -44,7 +43,8 @@ class GitHubRateLimitChecker { @Nonnull private final RateLimitChecker integrationManifest; - private static final Logger LOGGER = Logger.getLogger(GitHubRateLimitChecker.class.getName()); + @Nonnull + private final RateLimitChecker search; /** * Instantiates a new git hub rate limit checker. @@ -76,22 +76,29 @@ class GitHubRateLimitChecker { } /** - * Constructs a new {@link GitHubRateLimitChecker} with a new checker for a particular target. + * Gets the appropriate {@link RateLimitChecker} for a particular target. * - * Only one {@link RateLimitChecker} is allowed per target. + * Analogous with {@link GHRateLimit#getRecord(RateLimitTarget)}. * - * @param checker - * the {@link RateLimitChecker} to apply. * @param rateLimitTarget - * the {@link RateLimitTarget} for this checker. If {@link RateLimitTarget#NONE}, checker will be ignored - * and no change will be made. - * @return a new {@link GitHubRateLimitChecker} + * the rate limit to check + * @return the {@link RateLimitChecker} for a particular target */ - GitHubRateLimitChecker with(@Nonnull RateLimitChecker checker, @Nonnull RateLimitTarget rateLimitTarget) { - return new GitHubRateLimitChecker(rateLimitTarget == RateLimitTarget.CORE ? checker : core, - rateLimitTarget == RateLimitTarget.SEARCH ? checker : search, - rateLimitTarget == RateLimitTarget.GRAPHQL ? checker : graphql, - rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST ? checker : integrationManifest); + @Nonnull + private RateLimitChecker selectChecker(@Nonnull RateLimitTarget rateLimitTarget) { + if (rateLimitTarget == RateLimitTarget.NONE) { + return RateLimitChecker.NONE; + } else if (rateLimitTarget == RateLimitTarget.CORE) { + return core; + } else if (rateLimitTarget == RateLimitTarget.SEARCH) { + return search; + } else if (rateLimitTarget == RateLimitTarget.GRAPHQL) { + return graphql; + } else if (rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST) { + return integrationManifest; + } else { + throw new IllegalArgumentException("Unknown rate limit target: " + rateLimitTarget.toString()); + } } /** @@ -160,28 +167,21 @@ void checkRateLimit(GitHubClient client, @Nonnull RateLimitTarget rateLimitTarge } /** - * Gets the appropriate {@link RateLimitChecker} for a particular target. + * Constructs a new {@link GitHubRateLimitChecker} with a new checker for a particular target. * - * Analogous with {@link GHRateLimit#getRecord(RateLimitTarget)}. + * Only one {@link RateLimitChecker} is allowed per target. * + * @param checker + * the {@link RateLimitChecker} to apply. * @param rateLimitTarget - * the rate limit to check - * @return the {@link RateLimitChecker} for a particular target + * the {@link RateLimitTarget} for this checker. If {@link RateLimitTarget#NONE}, checker will be ignored + * and no change will be made. + * @return a new {@link GitHubRateLimitChecker} */ - @Nonnull - private RateLimitChecker selectChecker(@Nonnull RateLimitTarget rateLimitTarget) { - if (rateLimitTarget == RateLimitTarget.NONE) { - return RateLimitChecker.NONE; - } else if (rateLimitTarget == RateLimitTarget.CORE) { - return core; - } else if (rateLimitTarget == RateLimitTarget.SEARCH) { - return search; - } else if (rateLimitTarget == RateLimitTarget.GRAPHQL) { - return graphql; - } else if (rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST) { - return integrationManifest; - } else { - throw new IllegalArgumentException("Unknown rate limit target: " + rateLimitTarget.toString()); - } + GitHubRateLimitChecker with(@Nonnull RateLimitChecker checker, @Nonnull RateLimitTarget rateLimitTarget) { + return new GitHubRateLimitChecker(rateLimitTarget == RateLimitTarget.CORE ? checker : core, + rateLimitTarget == RateLimitTarget.SEARCH ? checker : search, + rateLimitTarget == RateLimitTarget.GRAPHQL ? checker : graphql, + rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST ? checker : integrationManifest); } } diff --git a/src/main/java/org/kohsuke/github/GitHubRateLimitHandler.java b/src/main/java/org/kohsuke/github/GitHubRateLimitHandler.java index ef7c662d3d..dd6149b1bd 100644 --- a/src/main/java/org/kohsuke/github/GitHubRateLimitHandler.java +++ b/src/main/java/org/kohsuke/github/GitHubRateLimitHandler.java @@ -24,6 +24,35 @@ */ public abstract class GitHubRateLimitHandler extends GitHubConnectorResponseErrorHandler { + /** + * Fail immediately. + */ + public static final GitHubRateLimitHandler FAIL = new GitHubRateLimitHandler() { + @Override + public void onError(GitHubConnectorResponse connectorResponse) throws IOException { + throw new HttpException("API rate limit reached", + connectorResponse.statusCode(), + connectorResponse.header("Status"), + connectorResponse.request().url().toString()) + .withResponseHeaderFields(connectorResponse.allHeaders()); + + } + }; + + /** + * Wait until the API abuse "wait time" is passed. + */ + public static final GitHubRateLimitHandler WAIT = new GitHubRateLimitHandler() { + @Override + public void onError(GitHubConnectorResponse connectorResponse) throws IOException { + try { + Thread.sleep(parseWaitTime(connectorResponse)); + } catch (InterruptedException ex) { + throw (InterruptedIOException) new InterruptedIOException().initCause(ex); + } + } + }; + /** * On a wait, even if the response suggests a very short wait, wait for a minimum duration. */ @@ -35,21 +64,6 @@ public abstract class GitHubRateLimitHandler extends GitHubConnectorResponseErro public GitHubRateLimitHandler() { } - /** - * Checks if is error. - * - * @param connectorResponse - * the connector response - * @return true, if is error - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Override - boolean isError(@NotNull GitHubConnectorResponse connectorResponse) throws IOException { - return connectorResponse.statusCode() == HTTP_FORBIDDEN - && "0".equals(connectorResponse.header("X-RateLimit-Remaining")); - } - /** * Called when the library encounters HTTP error indicating that the API rate limit has been exceeded. * @@ -68,18 +82,19 @@ boolean isError(@NotNull GitHubConnectorResponse connectorResponse) throws IOExc public abstract void onError(@Nonnull GitHubConnectorResponse connectorResponse) throws IOException; /** - * Wait until the API abuse "wait time" is passed. + * Checks if is error. + * + * @param connectorResponse + * the connector response + * @return true, if is error + * @throws IOException + * Signals that an I/O exception has occurred. */ - public static final GitHubRateLimitHandler WAIT = new GitHubRateLimitHandler() { - @Override - public void onError(GitHubConnectorResponse connectorResponse) throws IOException { - try { - Thread.sleep(parseWaitTime(connectorResponse)); - } catch (InterruptedException ex) { - throw (InterruptedIOException) new InterruptedIOException().initCause(ex); - } - } - }; + @Override + boolean isError(@NotNull GitHubConnectorResponse connectorResponse) throws IOException { + return connectorResponse.statusCode() == HTTP_FORBIDDEN + && "0".equals(connectorResponse.header("X-RateLimit-Remaining")); + } /* * Exposed for testability. Given an http response, find the rate limit reset header field and parse it. If no @@ -103,19 +118,4 @@ long parseWaitTime(GitHubConnectorResponse connectorResponse) { return Math.max(MINIMUM_RATE_LIMIT_RETRY_MILLIS, (Long.parseLong(v) - now.toInstant().getEpochSecond()) * 1000); } - /** - * Fail immediately. - */ - public static final GitHubRateLimitHandler FAIL = new GitHubRateLimitHandler() { - @Override - public void onError(GitHubConnectorResponse connectorResponse) throws IOException { - throw new HttpException("API rate limit reached", - connectorResponse.statusCode(), - connectorResponse.header("Status"), - connectorResponse.request().url().toString()) - .withResponseHeaderFields(connectorResponse.allHeaders()); - - } - }; - } diff --git a/src/main/java/org/kohsuke/github/GitHubRequest.java b/src/main/java/org/kohsuke/github/GitHubRequest.java index 2bb4a459a8..20cf1462a4 100644 --- a/src/main/java/org/kohsuke/github/GitHubRequest.java +++ b/src/main/java/org/kohsuke/github/GitHubRequest.java @@ -38,278 +38,29 @@ */ public class GitHubRequest implements GitHubConnectorRequest { - private static final Comparator nullableCaseInsensitiveComparator = Comparator - .nullsFirst(String.CASE_INSENSITIVE_ORDER); - - private static final List METHODS_WITHOUT_BODY = asList("GET", "DELETE"); - private final List args; - private final Map> headers; - private final Map injectedMappingValues; - private final String apiUrl; - private final String urlPath; - private final String method; - private final RateLimitTarget rateLimitTarget; - private final byte[] body; - private final boolean forceBody; - - private final URL url; - - @SuppressFBWarnings(value = { "CT_CONSTRUCTOR_THROW" }, justification = "Basic argument validation") - private GitHubRequest(@Nonnull List args, - @Nonnull Map> headers, - @Nonnull Map injectedMappingValues, - @Nonnull String apiUrl, - @Nonnull String urlPath, - @Nonnull String method, - @Nonnull RateLimitTarget rateLimitTarget, - @CheckForNull byte[] body, - boolean forceBody) { - this.args = Collections.unmodifiableList(new ArrayList<>(args)); - TreeMap> caseInsensitiveMap = new TreeMap<>(nullableCaseInsensitiveComparator); - for (Map.Entry> entry : headers.entrySet()) { - caseInsensitiveMap.put(entry.getKey(), Collections.unmodifiableList(new ArrayList<>(entry.getValue()))); - } - this.headers = Collections.unmodifiableMap(caseInsensitiveMap); - this.injectedMappingValues = Collections.unmodifiableMap(new LinkedHashMap<>(injectedMappingValues)); - this.apiUrl = apiUrl; - this.urlPath = urlPath; - this.method = method; - this.rateLimitTarget = rateLimitTarget; - this.body = body; - this.forceBody = forceBody; - String tailApiUrl = buildTailApiUrl(); - url = getApiURL(apiUrl, tailApiUrl); - } - - /** - * Create a new {@link Builder}. - * - * @return a new {@link Builder}. - */ - static Builder newBuilder() { - return new Builder<>(); - } - - /** - * Gets the final GitHub API URL. - * - * @param apiUrl - * the api url - * @param tailApiUrl - * the tail api url - * @return the api URL - * @throws GHException - * wrapping a {@link MalformedURLException} if the GitHub API URL cannot be constructed - */ - @Nonnull - static URL getApiURL(String apiUrl, String tailApiUrl) { - try { - if (!tailApiUrl.startsWith("/")) { - apiUrl = ""; - } else if ("github.com".equals(apiUrl)) { - // backward compatibility - apiUrl = GitHubClient.GITHUB_URL; - } - return new URI(apiUrl + tailApiUrl).toURL(); - } catch (Exception e) { - // The data going into constructing this URL should be controlled by the GitHub API framework, - // so a malformed URL here is a framework runtime error. - // All callers of this method ended up wrapping and throwing GHException, - // indicating the functionality should be moved to the common code path. - throw new GHException("Unable to build GitHub API URL", e); - } - } - - /** - * Transform Java Enum into Github constants given its conventions. - * - * @param en - * Enum to be transformed - * @return a String containing the value of a Github constant - */ - static String transformEnum(Enum en) { - // by convention Java constant names are upper cases, but github uses - // lower-case constants. GitHub also uses '-', which in Java we always - // replace with '_' - return en.toString().toLowerCase(Locale.ENGLISH).replace('_', '-'); - } - - /** - * The method for this request, such as "GET", "PATCH", or "DELETE". - * - * @return the request method. - */ - @Override - @Nonnull - public String method() { - return method; - } - - /** - * The rate limit target for this request. - * - * @return the rate limit to use for this request. - */ - @Nonnull - public RateLimitTarget rateLimitTarget() { - return rateLimitTarget; - } - - /** - * The arguments for this request. Depending on the {@link #method()} and {@code #inBody()} these maybe added to the - * url or to the request body. - * - * @return the list of arguments - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Already unmodifiable") - @Nonnull - public List args() { - return args; - } - /** - * The headers for this request. - * - * @return the {@link Map} of headers - */ - @Override - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable Map of unmodifiable lists") - @Nonnull - public Map> allHeaders() { - return headers; - } - - /** - * Gets the first value of a header field for this request. - * - * @param name - * the name of the header field. - * @return the value of the header field, or {@code null} if the header isn't set. - */ - @CheckForNull - public String header(String name) { - List values = headers.get(name); - if (values != null) { - return values.get(0); - } - return null; - } - - /** - * The headers for this request. - * - * @return the {@link Map} of headers - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Already unmodifiable") - @Nonnull - public Map injectedMappingValues() { - return injectedMappingValues; - } - - /** - * The base GitHub API URL for this request represented as a {@link String}. - * - * @return the url string - */ - @Nonnull - public String apiUrl() { - return apiUrl; - } - - /** - * The url path to be added to the {@link #apiUrl()} for this request. If this does not start with a "/", it instead - * represents the full url string for this request. - * - * @return a url path or full url string - */ - @Nonnull - public String urlPath() { - return urlPath; - } - - /** - * The content type to be sent by this request. - * - * @return the content type. - */ - @Override - public String contentType() { - return header("Content-type"); - } - - /** - * The {@link InputStream} to be sent as the body of this request. - * - * @return the {@link InputStream}. - */ - @Override - @CheckForNull - public InputStream body() { - return body != null ? new ByteArrayInputStream(body) : null; - } - - /** - * The {@link URL} for this request. This is the actual URL the {@link GitHubClient} will send this request to. - * - * @return the request {@link URL} - */ - @Override - @Nonnull - public URL url() { - return url; - } - - /** - * Whether arguments for this request should be included in the URL or in the body of the request. - * - * @return true if the arguments should be sent in the body of the request. + * The Class Entry. */ - @Override - public boolean hasBody() { - return forceBody || !METHODS_WITHOUT_BODY.contains(method); - } + protected static class Entry { - /** - * Create a {@link Builder} from this request. Initial values of the builder will be the same as this - * {@link GitHubRequest}. - * - * @return a {@link Builder} based on this request. - */ - Builder toBuilder() { - return new Builder<>(args, - headers, - injectedMappingValues, - apiUrl, - urlPath, - method, - rateLimitTarget, - body, - forceBody); - } + /** The key. */ + final String key; - private String buildTailApiUrl() { - String tailApiUrl = urlPath; - if (!hasBody() && !args.isEmpty() && tailApiUrl.startsWith("/")) { - try { - StringBuilder argString = new StringBuilder(); - boolean questionMarkFound = tailApiUrl.indexOf('?') != -1; - argString.append(questionMarkFound ? '&' : '?'); + /** The value. */ + final Object value; - for (Iterator it = args.listIterator(); it.hasNext();) { - Entry arg = it.next(); - argString.append(URLEncoder.encode(arg.key, StandardCharsets.UTF_8.name())); - argString.append('='); - argString.append(URLEncoder.encode(arg.value.toString(), StandardCharsets.UTF_8.name())); - if (it.hasNext()) { - argString.append('&'); - } - } - tailApiUrl += argString; - } catch (UnsupportedEncodingException e) { - throw new GHException("UTF-8 encoding required", e); - } + /** + * Instantiates a new entry. + * + * @param key + * the key + * @param value + * the value + */ + protected Entry(String key, Object value) { + this.key = key; + this.value = value; } - return tailApiUrl; } /** @@ -320,29 +71,30 @@ private String buildTailApiUrl() { */ static class Builder> { + /** + * The base GitHub API for this request. + */ + @Nonnull + private String apiUrl; + @Nonnull private final List args; + private byte[] body; + + private boolean forceBody; + /** * The header values for this request. */ @Nonnull private final Map> headers; - /** * Injected local data map */ @Nonnull private final Map injectedMappingValues; - /** - * The base GitHub API for this request. - */ - @Nonnull - private String apiUrl; - - @Nonnull - private String urlPath; /** * Request method. */ @@ -351,24 +103,8 @@ static class Builder> { @Nonnull private RateLimitTarget rateLimitTarget; - - private byte[] body; - private boolean forceBody; - - /** - * Create a new {@link GitHubRequest.Builder} - */ - protected Builder() { - this(new ArrayList<>(), - new TreeMap<>(nullableCaseInsensitiveComparator), - new LinkedHashMap<>(), - GitHubClient.GITHUB_URL, - "/", - "GET", - RateLimitTarget.CORE, - null, - false); - } + @Nonnull + private String urlPath; private Builder(@Nonnull List args, @Nonnull Map> headers, @@ -394,6 +130,21 @@ private Builder(@Nonnull List args, this.forceBody = forceBody; } + /** + * Create a new {@link GitHubRequest.Builder} + */ + protected Builder() { + this(new ArrayList<>(), + new TreeMap<>(nullableCaseInsensitiveComparator), + new LinkedHashMap<>(), + GitHubClient.GITHUB_URL, + "/", + "GET", + RateLimitTarget.CORE, + null, + false); + } + /** * Builds a {@link GitHubRequest} from this builder. * @@ -414,49 +165,42 @@ public GitHubRequest build() { } /** - * With header requester. + * Content type requester. * - * @param url - * the url + * @param contentType + * the content type * @return the request builder */ - public B withApiUrl(String url) { - this.apiUrl = url; + public B contentType(String contentType) { + this.setHeader("Content-type", contentType); return (B) this; } /** - * Removes the named request HTTP header. + * Small number of GitHub APIs use HTTP methods somewhat inconsistently, and use a body where it's not expected. + * Normally whether parameters go as query parameters or a body depends on the HTTP verb in use, but this method + * forces the parameters to be sent as a body. * - * @param name - * the name * @return the request builder */ - public B removeHeader(String name) { - headers.remove(name); + public B inBody() { + forceBody = true; return (B) this; } /** - * Sets the request HTTP header. - *

- * If a header of the same name is already set, this method overrides it. + * Object to inject into binding. * - * @param name - * the name * @param value * the value * @return the request builder */ - public B setHeader(String name, String value) { - List field = new ArrayList<>(); - field.add(value); - headers.put(name, field); - return (B) this; + public B injectMappingValue(@NonNull Object value) { + return injectMappingValue(value.getClass().getName(), value); } /** - * With header requester. + * Object to inject into binding. * * @param name * the name @@ -464,69 +208,67 @@ public B setHeader(String name, String value) { * the value * @return the request builder */ - public B withHeader(String name, String value) { - List field = headers.get(name); - if (field == null) { - setHeader(name, value); - } else { - field.add(value); - } + public B injectMappingValue(@NonNull String name, Object value) { + this.injectedMappingValues.put(name, value); return (B) this; } /** - * Object to inject into binding. + * Method requester. * - * @param value - * the value + * @param method + * the method * @return the request builder */ - public B injectMappingValue(@NonNull Object value) { - return injectMappingValue(value.getClass().getName(), value); + public B method(@Nonnull String method) { + this.method = method; + return (B) this; } /** - * Object to inject into binding. + * Method requester. * - * @param name - * the name - * @param value - * the value + * @param rateLimitTarget + * the rate limit target for this request. Default is {@link RateLimitTarget#CORE}. * @return the request builder */ - public B injectMappingValue(@NonNull String name, Object value) { - this.injectedMappingValues.put(name, value); + public B rateLimit(@Nonnull RateLimitTarget rateLimitTarget) { + this.rateLimitTarget = rateLimitTarget; return (B) this; } /** - * With preview. + * Removes all arg entries for a specific key. * - * @param name - * the name - * @return the b + * @param key + * the key + * @return the request builder */ - public B withAccept(String name) { - return withHeader("Accept", name); + public B remove(String key) { + for (int index = 0; index < args.size();) { + if (args.get(index).key.equals(key)) { + args.remove(index); + } else { + index++; + } + } + return (B) this; } /** - * With requester. + * Removes the named request HTTP header. * - * @param map - * map of key value pairs to add + * @param name + * the name * @return the request builder */ - public B with(Map map) { - for (Map.Entry entry : map.entrySet()) { - with(entry.getKey(), entry.getValue()); - } - + public B removeHeader(String name) { + headers.remove(name); return (B) this; } /** - * With requester. + * Unlike {@link #with(String, String)}, overrides the existing value. * * @param key * the key @@ -534,21 +276,58 @@ public B with(Map map) { * the value * @return the request builder */ - public B with(String key, int value) { - return with(key, (Object) value); + public B set(String key, Object value) { + remove(key); + return with(key, value); + } /** - * With requester. + * Sets the request HTTP header. + *

+ * If a header of the same name is already set, this method overrides it. * - * @param key - * the key + * @param name + * the name * @param value * the value * @return the request builder */ - public B with(String key, long value) { - return with(key, (Object) value); + public B setHeader(String name, String value) { + List field = new ArrayList<>(); + field.add(value); + headers.put(name, field); + return (B) this; + } + + /** + * With requester. + * + * @param body + * the body + * @return the request builder + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public B with(@WillClose InputStream body) throws IOException { + this.body = IOUtils.toByteArray(body); + IOUtils.closeQuietly(body); + return (B) this; + } + + /** + * With requester. + * + * @param map + * map of key value pairs to add + * @return the request builder + */ + public B with(Map map) { + for (Map.Entry entry : map.entrySet()) { + with(entry.getKey(), entry.getValue()); + } + + return (B) this; } /** @@ -560,7 +339,7 @@ public B with(String key, long value) { * the value * @return the request builder */ - public B with(String key, boolean value) { + public B with(String key, Collection value) { return with(key, (Object) value); } @@ -588,7 +367,7 @@ public B with(String key, Enum e) { * the value * @return the request builder */ - public B with(String key, String value) { + public B with(String key, Map value) { return with(key, (Object) value); } @@ -601,8 +380,11 @@ public B with(String key, String value) { * the value * @return the request builder */ - public B with(String key, Collection value) { - return with(key, (Object) value); + public B with(String key, Object value) { + if (value != null) { + args.add(new Entry(key, value)); + } + return (B) this; } /** @@ -614,27 +396,25 @@ public B with(String key, Collection value) { * the value * @return the request builder */ - public B with(String key, Map value) { + public B with(String key, String value) { return with(key, (Object) value); } /** * With requester. * - * @param body - * the body + * @param key + * the key + * @param value + * the value * @return the request builder - * @throws IOException - * Signals that an I/O exception has occurred. */ - public B with(@WillClose InputStream body) throws IOException { - this.body = IOUtils.toByteArray(body); - IOUtils.closeQuietly(body); - return (B) this; + public B with(String key, boolean value) { + return with(key, (Object) value); } /** - * With nullable requester. + * With requester. * * @param key * the key @@ -642,9 +422,8 @@ public B with(@WillClose InputStream body) throws IOException { * the value * @return the request builder */ - public B withNullable(String key, Object value) { - args.add(new Entry(key, value)); - return (B) this; + public B with(String key, int value) { + return with(key, (Object) value); } /** @@ -656,190 +435,411 @@ public B withNullable(String key, Object value) { * the value * @return the request builder */ - public B with(String key, Object value) { - if (value != null) { - args.add(new Entry(key, value)); - } - return (B) this; + public B with(String key, long value) { + return with(key, (Object) value); } /** - * Unlike {@link #with(String, String)}, overrides the existing value. + * With preview. * - * @param key - * the key - * @param value - * the value - * @return the request builder + * @param name + * the name + * @return the b */ - public B set(String key, Object value) { - remove(key); - return with(key, value); + public B withAccept(String name) { + return withHeader("Accept", name); + } + /** + * With header requester. + * + * @param url + * the url + * @return the request builder + */ + public B withApiUrl(String url) { + this.apiUrl = url; + return (B) this; } /** - * Removes all arg entries for a specific key. + * With header requester. * - * @param key - * the key + * @param name + * the name + * @param value + * the value * @return the request builder */ - public B remove(String key) { - for (int index = 0; index < args.size();) { - if (args.get(index).key.equals(key)) { - args.remove(index); - } else { - index++; - } + public B withHeader(String name, String value) { + List field = headers.get(name); + if (field == null) { + setHeader(name, value); + } else { + field.add(value); } return (B) this; } /** - * Method requester. + * With nullable requester. * - * @param method - * the method + * @param key + * the key + * @param value + * the value * @return the request builder */ - public B method(@Nonnull String method) { - this.method = method; + public B withNullable(String key, Object value) { + args.add(new Entry(key, value)); return (B) this; } /** - * Method requester. + * Path component of api URL. Appended to api url. + *

+ * If urlPath starts with a slash, it will be URI encoded as a path. If it starts with anything else, it will be + * used as is. * - * @param rateLimitTarget - * the rate limit target for this request. Default is {@link RateLimitTarget#CORE}. + * @param urlPath + * the url path + * @param urlPathItems + * the content type * @return the request builder */ - public B rateLimit(@Nonnull RateLimitTarget rateLimitTarget) { - this.rateLimitTarget = rateLimitTarget; + public B withUrlPath(@Nonnull String urlPath, @Nonnull String... urlPathItems) { + // full url may be set and reset as needed + if (urlPathItems.length == 0 && !urlPath.startsWith("/")) { + return setRawUrlPath(urlPath); + } + + // Once full url is set, do not allow path setting + if (!this.urlPath.startsWith("/")) { + throw new GHException("Cannot append to url path after setting a full url"); + } + + String tailUrlPath = urlPath; + if (urlPathItems.length != 0) { + tailUrlPath += "/" + String.join("/", urlPathItems); + } + + tailUrlPath = StringUtils.prependIfMissing(tailUrlPath, "/"); + + this.urlPath = urlPathEncode(tailUrlPath); return (B) this; } /** - * Content type requester. + * NOT FOR PUBLIC USE. Do not make this method public. + *

+ * Sets the path component of api URL without URI encoding. + *

+ * Should only be used when passing a literal URL field from a GHObject, such as {@link GHContent#refresh()} or + * when needing to set query parameters on requests methods that don't usually have them, such as + * {@link GHRelease#uploadAsset(String, InputStream, String)}. * - * @param contentType + * @param rawUrlPath * the content type * @return the request builder */ - public B contentType(String contentType) { - this.setHeader("Content-type", contentType); + B setRawUrlPath(@Nonnull String rawUrlPath) { + Objects.requireNonNull(rawUrlPath); + // This method should only work for full urls, which must start with "http" + if (!rawUrlPath.startsWith("http")) { + throw new GHException("Raw URL must start with 'http'"); + } + this.urlPath = rawUrlPath; return (B) this; } + } + private static final List METHODS_WITHOUT_BODY = asList("GET", "DELETE"); + private static final Comparator nullableCaseInsensitiveComparator = Comparator + .nullsFirst(String.CASE_INSENSITIVE_ORDER); + /** + * Encode the path to url safe string. + * + * @param value + * string to be path encoded. + * @return The encoded string. + */ + private static String urlPathEncode(String value) { + try { + return new URI(null, null, value, null, null).toASCIIString(); + } catch (URISyntaxException ex) { + throw new AssertionError(ex); + } + } + /** + * Gets the final GitHub API URL. + * + * @param apiUrl + * the api url + * @param tailApiUrl + * the tail api url + * @return the api URL + * @throws GHException + * wrapping a {@link MalformedURLException} if the GitHub API URL cannot be constructed + */ + @Nonnull + static URL getApiURL(String apiUrl, String tailApiUrl) { + try { + if (!tailApiUrl.startsWith("/")) { + apiUrl = ""; + } else if ("github.com".equals(apiUrl)) { + // backward compatibility + apiUrl = GitHubClient.GITHUB_URL; + } + return new URI(apiUrl + tailApiUrl).toURL(); + } catch (Exception e) { + // The data going into constructing this URL should be controlled by the GitHub API framework, + // so a malformed URL here is a framework runtime error. + // All callers of this method ended up wrapping and throwing GHException, + // indicating the functionality should be moved to the common code path. + throw new GHException("Unable to build GitHub API URL", e); + } + } + /** + * Create a new {@link Builder}. + * + * @return a new {@link Builder}. + */ + static Builder newBuilder() { + return new Builder<>(); + } + /** + * Transform Java Enum into Github constants given its conventions. + * + * @param en + * Enum to be transformed + * @return a String containing the value of a Github constant + */ + static String transformEnum(Enum en) { + // by convention Java constant names are upper cases, but github uses + // lower-case constants. GitHub also uses '-', which in Java we always + // replace with '_' + return en.toString().toLowerCase(Locale.ENGLISH).replace('_', '-'); + } + private final String apiUrl; + private final List args; + private final byte[] body; + + private final boolean forceBody; + + private final Map> headers; + + private final Map injectedMappingValues; + + private final String method; + + private final RateLimitTarget rateLimitTarget; + + private final URL url; + + private final String urlPath; + + @SuppressFBWarnings(value = { "CT_CONSTRUCTOR_THROW" }, justification = "Basic argument validation") + private GitHubRequest(@Nonnull List args, + @Nonnull Map> headers, + @Nonnull Map injectedMappingValues, + @Nonnull String apiUrl, + @Nonnull String urlPath, + @Nonnull String method, + @Nonnull RateLimitTarget rateLimitTarget, + @CheckForNull byte[] body, + boolean forceBody) { + this.args = Collections.unmodifiableList(new ArrayList<>(args)); + TreeMap> caseInsensitiveMap = new TreeMap<>(nullableCaseInsensitiveComparator); + for (Map.Entry> entry : headers.entrySet()) { + caseInsensitiveMap.put(entry.getKey(), Collections.unmodifiableList(new ArrayList<>(entry.getValue()))); + } + this.headers = Collections.unmodifiableMap(caseInsensitiveMap); + this.injectedMappingValues = Collections.unmodifiableMap(new LinkedHashMap<>(injectedMappingValues)); + this.apiUrl = apiUrl; + this.urlPath = urlPath; + this.method = method; + this.rateLimitTarget = rateLimitTarget; + this.body = body; + this.forceBody = forceBody; + String tailApiUrl = buildTailApiUrl(); + url = getApiURL(apiUrl, tailApiUrl); + } - /** - * NOT FOR PUBLIC USE. Do not make this method public. - *

- * Sets the path component of api URL without URI encoding. - *

- * Should only be used when passing a literal URL field from a GHObject, such as {@link GHContent#refresh()} or - * when needing to set query parameters on requests methods that don't usually have them, such as - * {@link GHRelease#uploadAsset(String, InputStream, String)}. - * - * @param rawUrlPath - * the content type - * @return the request builder - */ - B setRawUrlPath(@Nonnull String rawUrlPath) { - Objects.requireNonNull(rawUrlPath); - // This method should only work for full urls, which must start with "http" - if (!rawUrlPath.startsWith("http")) { - throw new GHException("Raw URL must start with 'http'"); - } - this.urlPath = rawUrlPath; - return (B) this; - } + /** + * The headers for this request. + * + * @return the {@link Map} of headers + */ + @Override + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable Map of unmodifiable lists") + @Nonnull + public Map> allHeaders() { + return headers; + } - /** - * Path component of api URL. Appended to api url. - *

- * If urlPath starts with a slash, it will be URI encoded as a path. If it starts with anything else, it will be - * used as is. - * - * @param urlPath - * the url path - * @param urlPathItems - * the content type - * @return the request builder - */ - public B withUrlPath(@Nonnull String urlPath, @Nonnull String... urlPathItems) { - // full url may be set and reset as needed - if (urlPathItems.length == 0 && !urlPath.startsWith("/")) { - return setRawUrlPath(urlPath); - } + /** + * The base GitHub API URL for this request represented as a {@link String}. + * + * @return the url string + */ + @Nonnull + public String apiUrl() { + return apiUrl; + } - // Once full url is set, do not allow path setting - if (!this.urlPath.startsWith("/")) { - throw new GHException("Cannot append to url path after setting a full url"); - } + /** + * The arguments for this request. Depending on the {@link #method()} and {@code #inBody()} these maybe added to the + * url or to the request body. + * + * @return the list of arguments + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Already unmodifiable") + @Nonnull + public List args() { + return args; + } - String tailUrlPath = urlPath; - if (urlPathItems.length != 0) { - tailUrlPath += "/" + String.join("/", urlPathItems); - } + /** + * The {@link InputStream} to be sent as the body of this request. + * + * @return the {@link InputStream}. + */ + @Override + @CheckForNull + public InputStream body() { + return body != null ? new ByteArrayInputStream(body) : null; + } - tailUrlPath = StringUtils.prependIfMissing(tailUrlPath, "/"); + /** + * The content type to be sent by this request. + * + * @return the content type. + */ + @Override + public String contentType() { + return header("Content-type"); + } - this.urlPath = urlPathEncode(tailUrlPath); - return (B) this; - } + /** + * Whether arguments for this request should be included in the URL or in the body of the request. + * + * @return true if the arguments should be sent in the body of the request. + */ + @Override + public boolean hasBody() { + return forceBody || !METHODS_WITHOUT_BODY.contains(method); + } - /** - * Small number of GitHub APIs use HTTP methods somewhat inconsistently, and use a body where it's not expected. - * Normally whether parameters go as query parameters or a body depends on the HTTP verb in use, but this method - * forces the parameters to be sent as a body. - * - * @return the request builder - */ - public B inBody() { - forceBody = true; - return (B) this; + /** + * Gets the first value of a header field for this request. + * + * @param name + * the name of the header field. + * @return the value of the header field, or {@code null} if the header isn't set. + */ + @CheckForNull + public String header(String name) { + List values = headers.get(name); + if (values != null) { + return values.get(0); } + return null; } /** - * The Class Entry. + * The headers for this request. + * + * @return the {@link Map} of headers */ - protected static class Entry { + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Already unmodifiable") + @Nonnull + public Map injectedMappingValues() { + return injectedMappingValues; + } - /** The key. */ - final String key; + /** + * The method for this request, such as "GET", "PATCH", or "DELETE". + * + * @return the request method. + */ + @Override + @Nonnull + public String method() { + return method; + } - /** The value. */ - final Object value; + /** + * The rate limit target for this request. + * + * @return the rate limit to use for this request. + */ + @Nonnull + public RateLimitTarget rateLimitTarget() { + return rateLimitTarget; + } - /** - * Instantiates a new entry. - * - * @param key - * the key - * @param value - * the value - */ - protected Entry(String key, Object value) { - this.key = key; - this.value = value; - } + /** + * The {@link URL} for this request. This is the actual URL the {@link GitHubClient} will send this request to. + * + * @return the request {@link URL} + */ + @Override + @Nonnull + public URL url() { + return url; } /** - * Encode the path to url safe string. + * The url path to be added to the {@link #apiUrl()} for this request. If this does not start with a "/", it instead + * represents the full url string for this request. * - * @param value - * string to be path encoded. - * @return The encoded string. + * @return a url path or full url string */ - private static String urlPathEncode(String value) { - try { - return new URI(null, null, value, null, null).toASCIIString(); - } catch (URISyntaxException ex) { - throw new AssertionError(ex); + @Nonnull + public String urlPath() { + return urlPath; + } + + private String buildTailApiUrl() { + String tailApiUrl = urlPath; + if (!hasBody() && !args.isEmpty() && tailApiUrl.startsWith("/")) { + try { + StringBuilder argString = new StringBuilder(); + boolean questionMarkFound = tailApiUrl.indexOf('?') != -1; + argString.append(questionMarkFound ? '&' : '?'); + + for (Iterator it = args.listIterator(); it.hasNext();) { + Entry arg = it.next(); + argString.append(URLEncoder.encode(arg.key, StandardCharsets.UTF_8.name())); + argString.append('='); + argString.append(URLEncoder.encode(arg.value.toString(), StandardCharsets.UTF_8.name())); + if (it.hasNext()) { + argString.append('&'); + } + } + tailApiUrl += argString; + } catch (UnsupportedEncodingException e) { + throw new GHException("UTF-8 encoding required", e); + } } + return tailApiUrl; + } + + /** + * Create a {@link Builder} from this request. Initial values of the builder will be the same as this + * {@link GitHubRequest}. + * + * @return a {@link Builder} based on this request. + */ + Builder toBuilder() { + return new Builder<>(args, + headers, + injectedMappingValues, + apiUrl, + urlPath, + method, + rateLimitTarget, + body, + forceBody); } } diff --git a/src/main/java/org/kohsuke/github/GitHubResponse.java b/src/main/java/org/kohsuke/github/GitHubResponse.java index defc094b64..8ac65391f7 100644 --- a/src/main/java/org/kohsuke/github/GitHubResponse.java +++ b/src/main/java/org/kohsuke/github/GitHubResponse.java @@ -35,40 +35,36 @@ class GitHubResponse { private static final Logger LOGGER = Logger.getLogger(GitHubResponse.class.getName()); - private final int statusCode; - - @Nonnull - private final Map> headers; - - @CheckForNull - private final T body; - /** - * Instantiates a new git hub response. + * Gets the body of the response as a {@link String}. * - * @param response - * the response - * @param body - * the body + * @param connectorResponse + * the response to read + * @return the body of the response as a {@link String}. + * @throws IOException + * if an I/O Exception occurs. */ - GitHubResponse(GitHubResponse response, @CheckForNull T body) { - this.statusCode = response.statusCode(); - this.headers = response.headers; - this.body = body; + @Nonnull + static String getBodyAsString(GitHubConnectorResponse connectorResponse) throws IOException { + InputStream inputStream = connectorResponse.bodyStream(); + try (InputStreamReader r = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { + return IOUtils.toString(r); + } } /** - * Instantiates a new git hub response. + * Gets the body of the response as a {@link String}. * * @param connectorResponse - * the connector response - * @param body - * the body + * the response to read + * @return the body of the response as a {@link String}. */ - GitHubResponse(GitHubConnectorResponse connectorResponse, @CheckForNull T body) { - this.statusCode = connectorResponse.statusCode(); - this.headers = connectorResponse.allHeaders(); - this.body = body; + static String getBodyAsStringOrNull(GitHubConnectorResponse connectorResponse) { + try { + return getBodyAsString(connectorResponse); + } catch (IOException e) { + } + return null; } /** @@ -136,57 +132,49 @@ static T parseBody(GitHubConnectorResponse connectorResponse, T instance) th } } - /** - * Gets the body of the response as a {@link String}. - * - * @param connectorResponse - * the response to read - * @return the body of the response as a {@link String}. - * @throws IOException - * if an I/O Exception occurs. - */ + @CheckForNull + private final T body; + @Nonnull - static String getBodyAsString(GitHubConnectorResponse connectorResponse) throws IOException { - InputStream inputStream = connectorResponse.bodyStream(); - try (InputStreamReader r = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { - return IOUtils.toString(r); - } - } + private final Map> headers; + + private final int statusCode; /** - * Gets the body of the response as a {@link String}. + * Instantiates a new git hub response. * * @param connectorResponse - * the response to read - * @return the body of the response as a {@link String}. + * the connector response + * @param body + * the body */ - static String getBodyAsStringOrNull(GitHubConnectorResponse connectorResponse) { - try { - return getBodyAsString(connectorResponse); - } catch (IOException e) { - } - return null; + GitHubResponse(GitHubConnectorResponse connectorResponse, @CheckForNull T body) { + this.statusCode = connectorResponse.statusCode(); + this.headers = connectorResponse.allHeaders(); + this.body = body; } /** - * The status code for this response. + * Instantiates a new git hub response. * - * @return the status code for this response. + * @param response + * the response + * @param body + * the body */ - public int statusCode() { - return statusCode; + GitHubResponse(GitHubResponse response, @CheckForNull T body) { + this.statusCode = response.statusCode(); + this.headers = response.headers; + this.body = body; } /** - * The headers for this response. + * The body of the response parsed as a {@code T}. * - * @param field - * the field - * @return the headers for this response. + * @return body of the response */ - @Nonnull - public List headers(String field) { - return headers.get(field); + public T body() { + return body; } /** @@ -207,12 +195,24 @@ public String header(String name) { } /** - * The body of the response parsed as a {@code T}. + * The headers for this response. * - * @return body of the response + * @param field + * the field + * @return the headers for this response. */ - public T body() { - return body; + @Nonnull + public List headers(String field) { + return headers.get(field); + } + + /** + * The status code for this response. + * + * @return the status code for this response. + */ + public int statusCode() { + return statusCode; } } diff --git a/src/main/java/org/kohsuke/github/GitHubSanityCachedValue.java b/src/main/java/org/kohsuke/github/GitHubSanityCachedValue.java index 8b5de1874b..b82ae79e6c 100644 --- a/src/main/java/org/kohsuke/github/GitHubSanityCachedValue.java +++ b/src/main/java/org/kohsuke/github/GitHubSanityCachedValue.java @@ -10,41 +10,41 @@ */ class GitHubSanityCachedValue { - private final Object lock = new Object(); private long lastQueriedAtEpochSeconds = 0; private T lastResult = null; + private final Object lock = new Object(); /** * Gets the value from the cache or calls the supplier if the cache is empty or out of date. * + * @param isExpired + * a supplier that returns true if the cached value is no longer valid. * @param query * a supplier the returns an updated value. Only called if the cache is empty or out of date. * @return the value from the cache or the value returned from the supplier. * @throws E * the exception thrown by the supplier if it fails. */ - T get(SupplierThrows query) throws E { - return get((value) -> Boolean.FALSE, query); + T get(Function isExpired, SupplierThrows query) throws E { + synchronized (lock) { + if (Instant.now().getEpochSecond() > lastQueriedAtEpochSeconds || isExpired.apply(lastResult)) { + lastResult = query.get(); + lastQueriedAtEpochSeconds = Instant.now().getEpochSecond(); + } + } + return lastResult; } /** * Gets the value from the cache or calls the supplier if the cache is empty or out of date. * - * @param isExpired - * a supplier that returns true if the cached value is no longer valid. * @param query * a supplier the returns an updated value. Only called if the cache is empty or out of date. * @return the value from the cache or the value returned from the supplier. * @throws E * the exception thrown by the supplier if it fails. */ - T get(Function isExpired, SupplierThrows query) throws E { - synchronized (lock) { - if (Instant.now().getEpochSecond() > lastQueriedAtEpochSeconds || isExpired.apply(lastResult)) { - lastResult = query.get(); - lastQueriedAtEpochSeconds = Instant.now().getEpochSecond(); - } - } - return lastResult; + T get(SupplierThrows query) throws E { + return get((value) -> Boolean.FALSE, query); } } diff --git a/src/main/java/org/kohsuke/github/GitUser.java b/src/main/java/org/kohsuke/github/GitUser.java index d906bc519a..65308ec7a9 100644 --- a/src/main/java/org/kohsuke/github/GitUser.java +++ b/src/main/java/org/kohsuke/github/GitUser.java @@ -1,7 +1,9 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.time.Instant; import java.util.Date; import javax.annotation.CheckForNull; @@ -17,16 +19,24 @@ */ @SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, justification = "JSON API") -public class GitUser { +public class GitUser extends GitHubBridgeAdapterObject { private String name, email, date, username; /** - * Gets the git user name for an author or committer on a git commit. + * Instantiates a new git user. + */ + public GitUser() { + // Empty constructor for Jackson binding + } + + /** + * Gets date. * - * @return Human readable name of the user, such as "Kohsuke Kawaguchi" + * @return Commit Date. */ - public String getName() { - return name; + @WithBridgeMethods(value = Date.class, adapterMethod = "instantToDate") + public Instant getDate() { + return GitHubClient.parseInstant(date); } /** @@ -39,28 +49,21 @@ public String getEmail() { } /** - * Gets username. Note: it presents only in events. + * Gets the git user name for an author or committer on a git commit. * - * @return GitHub username + * @return Human readable name of the user, such as "Kohsuke Kawaguchi" */ - @CheckForNull - public String getUsername() { - return username; + public String getName() { + return name; } /** - * Gets date. + * Gets username. Note: it presents only in events. * - * @return Commit Date. - */ - public Date getDate() { - return GitHubClient.parseDate(date); - } - - /** - * Instantiates a new git user. + * @return GitHub username */ - public GitUser() { - // Empty constructor for Jackson binding + @CheckForNull + public String getUsername() { + return username; } } diff --git a/src/main/java/org/kohsuke/github/HttpException.java b/src/main/java/org/kohsuke/github/HttpException.java index 3ced606965..c321683f5b 100644 --- a/src/main/java/org/kohsuke/github/HttpException.java +++ b/src/main/java/org/kohsuke/github/HttpException.java @@ -27,6 +27,20 @@ public class HttpException extends GHIOException { /** The message for this exception. */ private final String url; + /** + * Instantiates a new Http exception. + * + * @param connectorResponse + * the connector response to base this on + */ + public HttpException(GitHubConnectorResponse connectorResponse) { + this(GitHubResponse.getBodyAsStringOrNull(connectorResponse), + connectorResponse.statusCode(), + connectorResponse.header("Status"), + connectorResponse.request().url().toString()); + this.responseHeaderFields = connectorResponse.allHeaders(); + } + /** * Instantiates a new Http exception. * @@ -114,20 +128,6 @@ public HttpException(int responseCode, String responseMessage, @CheckForNull URL this(responseCode, responseMessage, url == null ? null : url.toString(), cause); } - /** - * Instantiates a new Http exception. - * - * @param connectorResponse - * the connector response to base this on - */ - public HttpException(GitHubConnectorResponse connectorResponse) { - this(GitHubResponse.getBodyAsStringOrNull(connectorResponse), - connectorResponse.statusCode(), - connectorResponse.header("Status"), - connectorResponse.request().url().toString()); - this.responseHeaderFields = connectorResponse.allHeaders(); - } - /** * Http response code of the request that cause the exception. * diff --git a/src/main/java/org/kohsuke/github/MarkdownMode.java b/src/main/java/org/kohsuke/github/MarkdownMode.java index 35a9e41c04..58245a93ba 100644 --- a/src/main/java/org/kohsuke/github/MarkdownMode.java +++ b/src/main/java/org/kohsuke/github/MarkdownMode.java @@ -11,17 +11,17 @@ * @see GHRepository#renderMarkdown(String, MarkdownMode) GHRepository#renderMarkdown(String, MarkdownMode) */ public enum MarkdownMode { - /** - * Render a document as plain Markdown, just like README files are rendered. - */ - MARKDOWN, /** * Render a document as user-content, e.g. like user comments or issues are rendered. In GFM mode, hard line breaks * are always taken into account, and issue and user mentions are linked accordingly. * * @see GHRepository#renderMarkdown(String, MarkdownMode) */ - GFM; + GFM, + /** + * Render a document as plain Markdown, just like README files are rendered. + */ + MARKDOWN; /** * To string. diff --git a/src/main/java/org/kohsuke/github/PagedIterable.java b/src/main/java/org/kohsuke/github/PagedIterable.java index 7dc17aa0eb..a916af8009 100644 --- a/src/main/java/org/kohsuke/github/PagedIterable.java +++ b/src/main/java/org/kohsuke/github/PagedIterable.java @@ -32,31 +32,6 @@ public abstract class PagedIterable implements Iterable { public PagedIterable() { } - /** - * Sets the pagination size. - * - *

- * When set to non-zero, each API call will retrieve this many entries. - * - * @param size - * the size - * @return the paged iterable - */ - public PagedIterable withPageSize(int size) { - this.pageSize = size; - return this; - } - - /** - * Returns an iterator over elements of type {@code T}. - * - * @return an Iterator. - */ - @Nonnull - public final PagedIterator iterator() { - return _iterator(pageSize); - } - /** * Iterator over page items. * @@ -68,37 +43,13 @@ public final PagedIterator iterator() { public abstract PagedIterator _iterator(int pageSize); /** - * Eagerly walk {@link PagedIterator} and return the result in an array. + * Returns an iterator over elements of type {@code T}. * - * @param iterator - * the {@link PagedIterator} to read - * @return an array of all elements from the {@link PagedIterator} - * @throws IOException - * if an I/O exception occurs. + * @return an Iterator. */ - protected T[] toArray(final PagedIterator iterator) throws IOException { - try { - ArrayList pages = new ArrayList<>(); - int totalSize = 0; - T[] item; - do { - item = iterator.nextPageArray(); - totalSize += Array.getLength(item); - pages.add(item); - } while (iterator.hasNext()); - - Class type = (Class) item.getClass(); - - return concatenatePages(type, pages, totalSize); - } catch (GHException e) { - // if there was an exception inside the iterator it is wrapped as a GHException - // if the wrapped exception is an IOException, throw that - if (e.getCause() instanceof IOException) { - throw (IOException) e.getCause(); - } else { - throw e; - } - } + @Nonnull + public final PagedIterator iterator() { + return _iterator(pageSize); } /** @@ -137,6 +88,21 @@ public Set toSet() throws IOException { return Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(this.toArray()))); } + /** + * Sets the pagination size. + * + *

+ * When set to non-zero, each API call will retrieve this many entries. + * + * @param size + * the size + * @return the paged iterable + */ + public PagedIterable withPageSize(int size) { + this.pageSize = size; + return this; + } + /** * Concatenates a list of arrays into a single array. * @@ -162,4 +128,38 @@ private T[] concatenatePages(Class type, List pages, int totalLength) return result; } + /** + * Eagerly walk {@link PagedIterator} and return the result in an array. + * + * @param iterator + * the {@link PagedIterator} to read + * @return an array of all elements from the {@link PagedIterator} + * @throws IOException + * if an I/O exception occurs. + */ + protected T[] toArray(final PagedIterator iterator) throws IOException { + try { + ArrayList pages = new ArrayList<>(); + int totalSize = 0; + T[] item; + do { + item = iterator.nextPageArray(); + totalSize += Array.getLength(item); + pages.add(item); + } while (iterator.hasNext()); + + Class type = (Class) item.getClass(); + + return concatenatePages(type, pages, totalSize); + } catch (GHException e) { + // if there was an exception inside the iterator it is wrapped as a GHException + // if the wrapped exception is an IOException, throw that + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } else { + throw e; + } + } + } + } diff --git a/src/main/java/org/kohsuke/github/PagedIterator.java b/src/main/java/org/kohsuke/github/PagedIterator.java index 73c8bf1a2d..ac6e54e826 100644 --- a/src/main/java/org/kohsuke/github/PagedIterator.java +++ b/src/main/java/org/kohsuke/github/PagedIterator.java @@ -26,13 +26,6 @@ */ public class PagedIterator implements Iterator { - /** The base. */ - @Nonnull - protected final Iterator base; - - @CheckForNull - private final Consumer itemInitializer; - /** * Current batch of items. Each time {@link #next()} is called the next item in this array will be returned. After * the last item of the array is returned, when {@link #next()} is called again, a new page of items will be fetched @@ -42,6 +35,9 @@ public class PagedIterator implements Iterator { */ private T[] currentPage; + @CheckForNull + private final Consumer itemInitializer; + /** * The index of the next item on the page, the item that will be returned when {@link #next()} is called. * @@ -49,6 +45,10 @@ public class PagedIterator implements Iterator { */ private int nextItemIndex; + /** The base. */ + @Nonnull + protected final Iterator base; + /** * Instantiates a new paged iterator. * @@ -62,21 +62,6 @@ public class PagedIterator implements Iterator { this.itemInitializer = itemInitializer; } - /** - * This poorly named method, initializes items with local data after they are fetched. It is up to the implementer - * to decide what local data to apply. - * - * @param page - * the page of items to be initialized - */ - protected void wrapUp(T[] page) { - if (itemInitializer != null) { - for (T item : page) { - itemInitializer.accept(item); - } - } - } - /** * {@inheritDoc} */ @@ -94,6 +79,15 @@ public T next() { return currentPage[nextItemIndex++]; } + /** + * Gets the next page worth of data. + * + * @return the list + */ + public List nextPage() { + return Arrays.asList(nextPageArray()); + } + /** * Fetch is called at the start of {@link #next()} or {@link #hasNext()} to fetch another page of data if it is * needed and available. @@ -123,12 +117,18 @@ private void fetch() { } /** - * Gets the next page worth of data. + * This poorly named method, initializes items with local data after they are fetched. It is up to the implementer + * to decide what local data to apply. * - * @return the list + * @param page + * the page of items to be initialized */ - public List nextPage() { - return Arrays.asList(nextPageArray()); + protected void wrapUp(T[] page) { + if (itemInitializer != null) { + for (T item : page) { + itemInitializer.accept(item); + } + } } /** diff --git a/src/main/java/org/kohsuke/github/PagedSearchIterable.java b/src/main/java/org/kohsuke/github/PagedSearchIterable.java index 65f7442a2a..8c6d00a26d 100644 --- a/src/main/java/org/kohsuke/github/PagedSearchIterable.java +++ b/src/main/java/org/kohsuke/github/PagedSearchIterable.java @@ -19,17 +19,17 @@ "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" }, justification = "Constructed by JSON API") public class PagedSearchIterable extends PagedIterable { - private final transient GitHub root; + private final Class> receiverType; private final GitHubRequest request; - private final Class> receiverType; - /** * As soon as we have any result fetched, it's set here so that we can report the total count. */ private SearchResult result; + private final transient GitHub root; + /** * Instantiates a new paged search iterable. * @@ -47,15 +47,18 @@ public class PagedSearchIterable extends PagedIterable { } /** - * With page size. + * Iterator. * - * @param size - * the size - * @return the paged search iterable + * @param pageSize + * the page size + * @return the paged iterator */ + @Nonnull @Override - public PagedSearchIterable withPageSize(int size) { - return (PagedSearchIterable) super.withPageSize(size); + public PagedIterator _iterator(int pageSize) { + final Iterator adapter = adapt( + GitHubPageIterator.create(root.getClient(), receiverType, request, pageSize)); + return new PagedIterator(adapter, null); } /** @@ -65,7 +68,7 @@ public PagedSearchIterable withPageSize(int size) { */ public int getTotalCount() { populate(); - return result.total_count; + return result.totalCount; } /** @@ -75,27 +78,24 @@ public int getTotalCount() { */ public boolean isIncomplete() { populate(); - return result.incomplete_results; - } - - private void populate() { - if (result == null) - iterator().hasNext(); + return result.incompleteResults; } /** - * Iterator. + * With page size. * - * @param pageSize - * the page size - * @return the paged iterator + * @param size + * the size + * @return the paged search iterable */ - @Nonnull @Override - public PagedIterator _iterator(int pageSize) { - final Iterator adapter = adapt( - GitHubPageIterator.create(root.getClient(), receiverType, request, pageSize)); - return new PagedIterator(adapter, null); + public PagedSearchIterable withPageSize(int size) { + return (PagedSearchIterable) super.withPageSize(size); + } + + private void populate() { + if (result == null) + iterator().hasNext(); } /** diff --git a/src/main/java/org/kohsuke/github/RateLimitChecker.java b/src/main/java/org/kohsuke/github/RateLimitChecker.java index 5b848cd17e..15b5f0e58d 100644 --- a/src/main/java/org/kohsuke/github/RateLimitChecker.java +++ b/src/main/java/org/kohsuke/github/RateLimitChecker.java @@ -1,5 +1,6 @@ package org.kohsuke.github; +import java.util.Date; import java.util.logging.Level; import java.util.logging.Logger; @@ -23,17 +24,58 @@ public abstract class RateLimitChecker { /** - * Create default RateLimitChecker instance + * A {@link RateLimitChecker} with a simple number as the limit. */ - public RateLimitChecker() { - } + public static class LiteralValue extends RateLimitChecker { + private final int sleepAtOrBelow; - private static final Logger LOGGER = Logger.getLogger(RateLimitChecker.class.getName()); + /** + * Instantiates a new literal value. + * + * @param sleepAtOrBelow + * the sleep at or below + */ + public LiteralValue(int sleepAtOrBelow) { + if (sleepAtOrBelow < 0) { + // ignore negative numbers + sleepAtOrBelow = 0; + } + this.sleepAtOrBelow = sleepAtOrBelow; + } + + /** + * Check rate limit. + * + * @param record + * the record + * @param count + * the count + * @return true, if successful + * @throws InterruptedException + * the interrupted exception + */ + @Override + protected boolean checkRateLimit(GHRateLimit.Record record, long count) throws InterruptedException { + if (record.getRemaining() <= sleepAtOrBelow) { + return sleepUntilReset(record); + } + return false; + } + + } /** The Constant NONE. */ public static final RateLimitChecker NONE = new RateLimitChecker() { }; + private static final Logger LOGGER = Logger.getLogger(RateLimitChecker.class.getName()); + + /** + * Create default RateLimitChecker instance + */ + public RateLimitChecker() { + } + /** * Decides whether the current request exceeds the allowed "rate limit" budget. If this determines the rate limit * will be exceeded, this method should sleep for some amount of time and must return {@code true}. Implementers are @@ -79,13 +121,13 @@ protected boolean checkRateLimit(GHRateLimit.Record rateLimitRecord, long count) */ protected final boolean sleepUntilReset(GHRateLimit.Record record) throws InterruptedException { // Sleep until reset - long sleepMilliseconds = record.getResetDate().getTime() - System.currentTimeMillis(); + long sleepMilliseconds = record.getResetInstant().toEpochMilli() - System.currentTimeMillis(); if (sleepMilliseconds > 0) { String message = String.format( "GitHub API - Current quota has %d remaining of %d. Waiting for quota to reset at %tT.", record.getRemaining(), record.getLimit(), - record.getResetDate()); + Date.from(record.getResetInstant())); LOGGER.log(Level.INFO, message); @@ -95,45 +137,4 @@ protected final boolean sleepUntilReset(GHRateLimit.Record record) throws Interr return false; } - /** - * A {@link RateLimitChecker} with a simple number as the limit. - */ - public static class LiteralValue extends RateLimitChecker { - private final int sleepAtOrBelow; - - /** - * Instantiates a new literal value. - * - * @param sleepAtOrBelow - * the sleep at or below - */ - public LiteralValue(int sleepAtOrBelow) { - if (sleepAtOrBelow < 0) { - // ignore negative numbers - sleepAtOrBelow = 0; - } - this.sleepAtOrBelow = sleepAtOrBelow; - } - - /** - * Check rate limit. - * - * @param record - * the record - * @param count - * the count - * @return true, if successful - * @throws InterruptedException - * the interrupted exception - */ - @Override - protected boolean checkRateLimit(GHRateLimit.Record record, long count) throws InterruptedException { - if (record.getRemaining() <= sleepAtOrBelow) { - return sleepUntilReset(record); - } - return false; - } - - } - } diff --git a/src/main/java/org/kohsuke/github/RateLimitTarget.java b/src/main/java/org/kohsuke/github/RateLimitTarget.java index 5fba008fed..4f87995276 100644 --- a/src/main/java/org/kohsuke/github/RateLimitTarget.java +++ b/src/main/java/org/kohsuke/github/RateLimitTarget.java @@ -12,11 +12,6 @@ public enum RateLimitTarget { */ CORE, - /** - * Selects or updates the {@link GHRateLimit#getSearch()} record. - */ - SEARCH, - /** * Selects or updates the {@link GHRateLimit#getGraphQL()} record. */ @@ -33,5 +28,10 @@ public enum RateLimitTarget { * This request uses no rate limit. If the response header includes rate limit information, it will apply to * {@link #CORE}. */ - NONE + NONE, + + /** + * Selects or updates the {@link GHRateLimit#getSearch()} record. + */ + SEARCH } diff --git a/src/main/java/org/kohsuke/github/Reactable.java b/src/main/java/org/kohsuke/github/Reactable.java index 309f7d29b2..be7ab7b399 100644 --- a/src/main/java/org/kohsuke/github/Reactable.java +++ b/src/main/java/org/kohsuke/github/Reactable.java @@ -9,13 +9,6 @@ * @author Kohsuke Kawaguchi */ public interface Reactable { - /** - * List all the reactions left to this object. - * - * @return the paged iterable - */ - PagedIterable listReactions(); - /** * Leaves a reaction to this object. * @@ -36,4 +29,11 @@ public interface Reactable { * the io exception */ void deleteReaction(GHReaction reaction) throws IOException; + + /** + * List all the reactions left to this object. + * + * @return the paged iterable + */ + PagedIterable listReactions(); } diff --git a/src/main/java/org/kohsuke/github/ReactionContent.java b/src/main/java/org/kohsuke/github/ReactionContent.java index 15d3197d15..c0361d9b0a 100644 --- a/src/main/java/org/kohsuke/github/ReactionContent.java +++ b/src/main/java/org/kohsuke/github/ReactionContent.java @@ -13,29 +13,45 @@ */ public enum ReactionContent { - /** The plus one. */ - PLUS_ONE("+1"), - - /** The minus one. */ - MINUS_ONE("-1"), - - /** The laugh. */ - LAUGH("laugh"), - /** The confused. */ CONFUSED("confused"), + /** The eyes. */ + EYES("eyes"), + /** The heart. */ HEART("heart"), /** The hooray. */ HOORAY("hooray"), + /** The laugh. */ + LAUGH("laugh"), + + /** The minus one. */ + MINUS_ONE("-1"), + + /** The plus one. */ + PLUS_ONE("+1"), + /** The rocket. */ - ROCKET("rocket"), + ROCKET("rocket"); - /** The eyes. */ - EYES("eyes"); + /** + * For content reaction content. + * + * @param content + * the content + * @return the reaction content + */ + @JsonCreator + public static ReactionContent forContent(String content) { + for (ReactionContent c : ReactionContent.values()) { + if (c.getContent().equals(content)) + return c; + } + return null; + } private final String content; @@ -58,20 +74,4 @@ public enum ReactionContent { public String getContent() { return content; } - - /** - * For content reaction content. - * - * @param content - * the content - * @return the reaction content - */ - @JsonCreator - public static ReactionContent forContent(String content) { - for (ReactionContent c : ReactionContent.values()) { - if (c.getContent().equals(content)) - return c; - } - return null; - } } diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java index ad65f1a2d1..95f0366ebd 100644 --- a/src/main/java/org/kohsuke/github/Requester.java +++ b/src/main/java/org/kohsuke/github/Requester.java @@ -27,6 +27,7 @@ import org.apache.commons.io.IOUtils; import org.kohsuke.github.connector.GitHubConnectorResponse; import org.kohsuke.github.function.InputStreamFunction; +import org.kohsuke.github.internal.graphql.response.GHGraphQLResponse; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -44,6 +45,25 @@ */ class Requester extends GitHubRequest.Builder { + /** + * Helper function to make it easy to pull streams. + * + * Copies an input stream to an in-memory input stream. The performance on this is not great but + * {@link GitHubConnectorResponse#bodyStream()} is closed at the end of every call to + * {@link GitHubClient#sendRequest(GitHubRequest, GitHubClient.BodyHandler)}, so any reads to the original input + * stream must be completed before then. There are a number of deprecated methods that return {@link InputStream}. + * This method keeps all of them using the same code path. + * + * @param inputStream + * the input stream to be copied + * @return an in-memory copy of the passed input stream + * @throws IOException + * if an error occurs while copying the stream + */ + @NonNull public static InputStream copyInputStream(InputStream inputStream) throws IOException { + return new ByteArrayInputStream(IOUtils.toByteArray(inputStream)); + } + /** The client. */ /* private */ final transient GitHubClient client; @@ -58,18 +78,6 @@ class Requester extends GitHubRequest.Builder { this.withApiUrl(client.getApiUrl()); } - /** - * Sends a request to the specified URL and checks that it is successful. - * - * @throws IOException - * the io exception - */ - public void send() throws IOException { - // Send expects there to be some body response, but doesn't care what it is. - // If there isn't a body, this will throw. - client.sendRequest(this, (connectorResponse) -> GitHubResponse.getBodyAsString(connectorResponse)); - } - /** * Sends a request and parses the response into the given type via databinding. * @@ -87,20 +95,24 @@ public T fetch(@Nonnull Class type) throws IOException { } /** - * Like {@link #fetch(Class)} but updates an existing object instead of creating a new instance. + * Sends a request and parses the response into the given type via databinding in GraphQL response. * * @param * the type parameter - * @param existingInstance - * the existing instance - * @return the updated instance + * @param type + * the type + * @return an instance of {@code GHGraphQLResponse} * @throws IOException - * the io exception + * if the server returns 4xx/5xx responses. */ - public T fetchInto(@Nonnull T existingInstance) throws IOException { - return client - .sendRequest(this, (connectorResponse) -> GitHubResponse.parseBody(connectorResponse, existingInstance)) - .body(); + public , S> S fetchGraphQL(@Nonnull Class type) throws IOException { + T response = fetch(type); + + if (!response.isSuccessful()) { + throw new IOException("GraphQL request failed by:" + response.getErrorMessages()); + } + + return response.getData(); } /** @@ -115,6 +127,23 @@ public int fetchHttpStatusCode() throws IOException { return client.sendRequest(build(), null).statusCode(); } + /** + * Like {@link #fetch(Class)} but updates an existing object instead of creating a new instance. + * + * @param + * the type parameter + * @param existingInstance + * the existing instance + * @return the updated instance + * @throws IOException + * the io exception + */ + public T fetchInto(@Nonnull T existingInstance) throws IOException { + return client + .sendRequest(this, (connectorResponse) -> GitHubResponse.parseBody(connectorResponse, existingInstance)) + .body(); + } + /** * Response input stream. There are scenarios where direct stream reading is needed, however it is better to use * {@link #fetch(Class)} where possible. @@ -132,23 +161,25 @@ public T fetchStream(@Nonnull InputStreamFunction handler) throws IOExcep } /** - * Helper function to make it easy to pull streams. + * Sends a request to the specified URL and checks that it is successful. * - * Copies an input stream to an in-memory input stream. The performance on this is not great but - * {@link GitHubConnectorResponse#bodyStream()} is closed at the end of every call to - * {@link GitHubClient#sendRequest(GitHubRequest, GitHubClient.BodyHandler)}, so any reads to the original input - * stream must be completed before then. There are a number of deprecated methods that return {@link InputStream}. - * This method keeps all of them using the same code path. + * @throws IOException + * the io exception + */ + public void send() throws IOException { + // Send expects there to be some body response, but doesn't care what it is. + // If there isn't a body, this will throw. + client.sendRequest(this, (connectorResponse) -> GitHubResponse.getBodyAsString(connectorResponse)); + } + + /** + * Sends a GraphQL request with no response * - * @param inputStream - * the input stream to be copied - * @return an in-memory copy of the passed input stream * @throws IOException - * if an error occurs while copying the stream + * the io exception */ - @NonNull - public static InputStream copyInputStream(InputStream inputStream) throws IOException { - return new ByteArrayInputStream(IOUtils.toByteArray(inputStream)); + public void sendGraphQL() throws IOException { + fetchGraphQL(GHGraphQLResponse.ObjectResponse.class); } /** diff --git a/src/main/java/org/kohsuke/github/SearchResult.java b/src/main/java/org/kohsuke/github/SearchResult.java index ca2f621df7..fe7e350439 100644 --- a/src/main/java/org/kohsuke/github/SearchResult.java +++ b/src/main/java/org/kohsuke/github/SearchResult.java @@ -10,11 +10,11 @@ */ abstract class SearchResult { - /** The total count. */ - int total_count; - /** The incomplete results. */ - boolean incomplete_results; + boolean incompleteResults; + + /** The total count. */ + int totalCount; /** * Wraps up the retrieved object and return them. Only called once. diff --git a/src/main/java/org/kohsuke/github/authorization/AppInstallationAuthorizationProvider.java b/src/main/java/org/kohsuke/github/authorization/AppInstallationAuthorizationProvider.java index 7ad33ede46..112a0b727c 100644 --- a/src/main/java/org/kohsuke/github/authorization/AppInstallationAuthorizationProvider.java +++ b/src/main/java/org/kohsuke/github/authorization/AppInstallationAuthorizationProvider.java @@ -7,8 +7,8 @@ import org.kohsuke.github.GitHub; import java.io.IOException; -import java.time.Duration; import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.Objects; import javax.annotation.Nonnull; @@ -18,6 +18,23 @@ */ public class AppInstallationAuthorizationProvider extends GitHub.DependentAuthorizationProvider { + /** + * Provides an interface that returns an app to be used by an AppInstallationAuthorizationProvider + */ + @FunctionalInterface + public interface AppInstallationProvider { + /** + * Provides a GHAppInstallation for the given GHApp + * + * @param app + * The GHApp to use + * @return The GHAppInstallation + * @throws IOException + * on error + */ + GHAppInstallation getAppInstallation(GHApp app) throws IOException; + } + private final AppInstallationProvider appInstallationProvider; private String authorization; @@ -57,24 +74,7 @@ private String refreshToken() throws IOException { GitHub gitHub = this.gitHub(); GHAppInstallation installationByOrganization = appInstallationProvider.getAppInstallation(gitHub.getApp()); GHAppInstallationToken ghAppInstallationToken = installationByOrganization.createToken().create(); - this.validUntil = ghAppInstallationToken.getExpiresAt().toInstant().minus(Duration.ofMinutes(5)); + this.validUntil = ghAppInstallationToken.getExpiresAt().minus(5, ChronoUnit.MINUTES); return Objects.requireNonNull(ghAppInstallationToken.getToken()); } - - /** - * Provides an interface that returns an app to be used by an AppInstallationAuthorizationProvider - */ - @FunctionalInterface - public interface AppInstallationProvider { - /** - * Provides a GHAppInstallation for the given GHApp - * - * @param app - * The GHApp to use - * @return The GHAppInstallation - * @throws IOException - * on error - */ - GHAppInstallation getAppInstallation(GHApp app) throws IOException; - } } diff --git a/src/main/java/org/kohsuke/github/authorization/ImmutableAuthorizationProvider.java b/src/main/java/org/kohsuke/github/authorization/ImmutableAuthorizationProvider.java index 09ee6ae467..e236d1c66e 100644 --- a/src/main/java/org/kohsuke/github/authorization/ImmutableAuthorizationProvider.java +++ b/src/main/java/org/kohsuke/github/authorization/ImmutableAuthorizationProvider.java @@ -7,16 +7,53 @@ */ public class ImmutableAuthorizationProvider implements AuthorizationProvider { - private final String authorization; + /** + * An internal class representing all user-related credentials, which are credentials that have a login or should + * query the user endpoint for the login matching this credential. + * + * @see org.kohsuke.github.authorization.UserAuthorizationProvider UserAuthorizationProvider + */ + private static class UserProvider extends ImmutableAuthorizationProvider implements UserAuthorizationProvider { + + private final String login; + + UserProvider(String authorization) { + this(authorization, null); + } + + UserProvider(String authorization, String login) { + super(authorization); + this.login = login; + } + + @CheckForNull + @Override + public String getLogin() { + return login; + } + + } /** - * ImmutableAuthorizationProvider constructor + * Builds and returns a {@link AuthorizationProvider} from a given App Installation Token * - * @param authorization - * the authorization string + * @param appInstallationToken + * A string containing the GitHub App installation token + * @return the configured Builder from given GitHub App installation token. */ - public ImmutableAuthorizationProvider(String authorization) { - this.authorization = authorization; + public static AuthorizationProvider fromAppInstallationToken(String appInstallationToken) { + return fromOauthToken(appInstallationToken, ""); + } + + /** + * Builds and returns a {@link AuthorizationProvider} from a given jwtToken + * + * @param jwtToken + * The JWT token + * @return a correctly configured {@link AuthorizationProvider} that will always return the same provided jwtToken + */ + public static AuthorizationProvider fromJwtToken(String jwtToken) { + return new ImmutableAuthorizationProvider(String.format("Bearer %s", jwtToken)); } /** @@ -46,57 +83,20 @@ public static AuthorizationProvider fromOauthToken(String oauthAccessToken, Stri return new UserProvider(String.format("token %s", oauthAccessToken), login); } - /** - * Builds and returns a {@link AuthorizationProvider} from a given App Installation Token - * - * @param appInstallationToken - * A string containing the GitHub App installation token - * @return the configured Builder from given GitHub App installation token. - */ - public static AuthorizationProvider fromAppInstallationToken(String appInstallationToken) { - return fromOauthToken(appInstallationToken, ""); - } + private final String authorization; /** - * Builds and returns a {@link AuthorizationProvider} from a given jwtToken + * ImmutableAuthorizationProvider constructor * - * @param jwtToken - * The JWT token - * @return a correctly configured {@link AuthorizationProvider} that will always return the same provided jwtToken + * @param authorization + * the authorization string */ - public static AuthorizationProvider fromJwtToken(String jwtToken) { - return new ImmutableAuthorizationProvider(String.format("Bearer %s", jwtToken)); + public ImmutableAuthorizationProvider(String authorization) { + this.authorization = authorization; } @Override public String getEncodedAuthorization() { return this.authorization; } - - /** - * An internal class representing all user-related credentials, which are credentials that have a login or should - * query the user endpoint for the login matching this credential. - * - * @see org.kohsuke.github.authorization.UserAuthorizationProvider UserAuthorizationProvider - */ - private static class UserProvider extends ImmutableAuthorizationProvider implements UserAuthorizationProvider { - - private final String login; - - UserProvider(String authorization) { - this(authorization, null); - } - - UserProvider(String authorization, String login) { - super(authorization); - this.login = login; - } - - @CheckForNull - @Override - public String getLogin() { - return login; - } - - } } diff --git a/src/main/java/org/kohsuke/github/connector/GitHubConnector.java b/src/main/java/org/kohsuke/github/connector/GitHubConnector.java index a9f2f1da1e..94df7869f4 100644 --- a/src/main/java/org/kohsuke/github/connector/GitHubConnector.java +++ b/src/main/java/org/kohsuke/github/connector/GitHubConnector.java @@ -13,25 +13,6 @@ @FunctionalInterface public interface GitHubConnector { - /** - * Sends a request and retrieves a raw response for processing. - * - * Implementers of {@link GitHubConnector#send(GitHubConnectorRequest)} process the information from a - * {@link GitHubConnectorRequest} to open an HTTP connection and retrieve a raw response. They then return a class - * that extends {@link GitHubConnectorResponse} corresponding their response data. - * - * Clients should not implement their own {@link GitHubConnectorRequest}. The {@link GitHubConnectorRequest} - * provided by the caller of {@link GitHubConnector#send(GitHubConnectorRequest)} should be passed to the - * constructor of {@link GitHubConnectorResponse}. - * - * @param connectorRequest - * the request data to be sent. - * @return a GitHubConnectorResponse for the request - * @throws IOException - * if there is an I/O error - */ - GitHubConnectorResponse send(GitHubConnectorRequest connectorRequest) throws IOException; - /** * Default implementation used when connector is not set by user. * @@ -51,4 +32,23 @@ public GitHubConnectorResponse send(GitHubConnectorRequest connectorRequest) thr throw new GHIOException("Offline"); } }; + + /** + * Sends a request and retrieves a raw response for processing. + * + * Implementers of {@link GitHubConnector#send(GitHubConnectorRequest)} process the information from a + * {@link GitHubConnectorRequest} to open an HTTP connection and retrieve a raw response. They then return a class + * that extends {@link GitHubConnectorResponse} corresponding their response data. + * + * Clients should not implement their own {@link GitHubConnectorRequest}. The {@link GitHubConnectorRequest} + * provided by the caller of {@link GitHubConnector#send(GitHubConnectorRequest)} should be passed to the + * constructor of {@link GitHubConnectorResponse}. + * + * @param connectorRequest + * the request data to be sent. + * @return a GitHubConnectorResponse for the request + * @throws IOException + * if there is an I/O error + */ + GitHubConnectorResponse send(GitHubConnectorRequest connectorRequest) throws IOException; } diff --git a/src/main/java/org/kohsuke/github/connector/GitHubConnectorRequest.java b/src/main/java/org/kohsuke/github/connector/GitHubConnectorRequest.java index e00d59dcec..59edf79dc5 100644 --- a/src/main/java/org/kohsuke/github/connector/GitHubConnectorRequest.java +++ b/src/main/java/org/kohsuke/github/connector/GitHubConnectorRequest.java @@ -23,16 +23,6 @@ */ public interface GitHubConnectorRequest { - /** - * The request method for this request. - * - * For example, {@code GET} or {@code PATCH}. - * - * @return the request method. - */ - @Nonnull - String method(); - /** * All request headers for this request. * @@ -42,14 +32,12 @@ public interface GitHubConnectorRequest { Map> allHeaders(); /** - * Gets the value contained in a header field. + * Gets the request body as an InputStream. * - * @param name - * the name of the field. - * @return the value contained in that field, or {@code null} if not present. + * @return the request body as an InputStream. */ @CheckForNull - String header(String name); + InputStream body(); /** * Get the content type for the body of this request. @@ -60,25 +48,37 @@ public interface GitHubConnectorRequest { String contentType(); /** - * Gets the request body as an InputStream. + * Gets whether the request has information in {@link #body()} that needs to be sent. * - * @return the request body as an InputStream. + * @return true, if the body is not null. Otherwise, false. + */ + boolean hasBody(); + + /** + * Gets the value contained in a header field. + * + * @param name + * the name of the field. + * @return the value contained in that field, or {@code null} if not present. */ @CheckForNull - InputStream body(); + String header(String name); /** - * Gets the url for this request. + * The request method for this request. * - * @return the url for this request. + * For example, {@code GET} or {@code PATCH}. + * + * @return the request method. */ @Nonnull - URL url(); + String method(); /** - * Gets whether the request has information in {@link #body()} that needs to be sent. + * Gets the url for this request. * - * @return true, if the body is not null. Otherwise, false. + * @return the url for this request. */ - boolean hasBody(); + @Nonnull + URL url(); } diff --git a/src/main/java/org/kohsuke/github/connector/GitHubConnectorResponse.java b/src/main/java/org/kohsuke/github/connector/GitHubConnectorResponse.java index b71fc8abc5..a17e3a4e92 100644 --- a/src/main/java/org/kohsuke/github/connector/GitHubConnectorResponse.java +++ b/src/main/java/org/kohsuke/github/connector/GitHubConnectorResponse.java @@ -7,15 +7,25 @@ import java.io.Closeable; import java.io.IOException; import java.io.InputStream; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; import java.util.zip.GZIPInputStream; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; +import static java.net.HttpURLConnection.HTTP_OK; + /** * Response information supplied when a response is received and before the body is processed. *

+ * During a request to GitHub, {@link GitHubConnector#send(GitHubConnectorRequest)} returns a + * {@link GitHubConnectorResponse}. This is processed to create a GitHubResponse. + *

* Instances of this class are closed once the response is done being processed. This means that {@link #bodyStream()} * will not be readable after a call is completed. * @@ -26,15 +36,45 @@ */ public abstract class GitHubConnectorResponse implements Closeable { + /** + * A ByteArrayResponse class + * + * @deprecated Inherit directly from {@link GitHubConnectorResponse}. + */ + @Deprecated + public abstract static class ByteArrayResponse extends GitHubConnectorResponse { + + /** + * Constructor for ByteArray Response + * + * @param request + * the request + * @param statusCode + * the status code + * @param headers + * the headers + */ + protected ByteArrayResponse(@Nonnull GitHubConnectorRequest request, + int statusCode, + @Nonnull Map> headers) { + super(request, statusCode, headers); + } + } + private static final Comparator nullableCaseInsensitiveComparator = Comparator .nullsFirst(String.CASE_INSENSITIVE_ORDER); - private final int statusCode; - - @Nonnull - private final GitHubConnectorRequest request; + private byte[] bodyBytes = null; + private InputStream bodyStream = null; + private boolean bodyStreamCalled = false; @Nonnull private final Map> headers; + private boolean isBodyStreamRereadable; + private boolean isClosed = false; + @Nonnull + private final GitHubConnectorRequest request; + + private final int statusCode; /** * GitHubConnectorResponse constructor @@ -58,6 +98,79 @@ protected GitHubConnectorResponse(@Nonnull GitHubConnectorRequest request, caseInsensitiveMap.put(entry.getKey(), Collections.unmodifiableList(new ArrayList<>(entry.getValue()))); } this.headers = Collections.unmodifiableMap(caseInsensitiveMap); + this.isBodyStreamRereadable = false; + } + + /** + * The headers for this response. + * + * @return the headers for this response. + */ + @Nonnull + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable map of unmodifiable lists") + public Map> allHeaders() { + return headers; + } + + /** + * The response body as an {@link InputStream}. + * + * When {@link #isBodyStreamRereadable} is false, {@link #bodyStream()} can only be called once and the returned + * stream should be assumed to be read-once and not resetable. This is the default behavior for HTTP_OK responses + * and significantly reduces memory usage. + * + * When {@link #isBodyStreamRereadable} is true, {@link #bodyStream()} can be called be called multiple times. The + * full stream data is read into a byte array during the first call. Each call returns a new stream backed by the + * same byte array. This uses more memory, but is required to enable rereading the body stream during trace logging, + * debugging, and error responses. + * + * @return the response body + * @throws IOException + * if response stream is null or an I/O Exception occurs. + */ + @Nonnull + public InputStream bodyStream() throws IOException { + synchronized (this) { + if (isClosed) { + throw new IOException("Response is closed"); + } + + if (bodyStreamCalled) { + if (!isBodyStreamRereadable()) { + throw new IOException("Response body not rereadable"); + } + } else { + bodyStream = wrapStream(rawBodyStream()); + bodyStreamCalled = true; + } + + if (bodyStream == null) { + throw new IOException("Response body missing, stream null"); + } else if (!isBodyStreamRereadable()) { + return bodyStream; + } + + // Load rereadable byte array + if (bodyBytes == null) { + bodyBytes = IOUtils.toByteArray(bodyStream); + // Close the raw body stream after successfully reading + IOUtils.closeQuietly(bodyStream); + } + + return new ByteArrayInputStream(bodyBytes); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void close() throws IOException { + synchronized (this) { + IOUtils.closeQuietly(bodyStream); + isClosed = true; + this.bodyBytes = null; + } } /** @@ -77,25 +190,71 @@ public String header(String name) { } /** - * The response body as an {@link InputStream}. + * The body stream rereadable state. * - * @return the response body - * @throws IOException - * if response stream is null or an I/O Exception occurs. + * Body stream defaults to read once for HTTP_OK responses (to reduce memory usage). For non-HTTP_OK responses, body + * stream is switched to rereadable (in-memory byte array) for error processing. + * + * Calling {@link #setBodyStreamRereadable()} will force {@link #isBodyStreamRereadable} to be true for this + * response regardless of {@link #statusCode} value. + * + * @return true when body stream is rereadable. */ - @Nonnull - public abstract InputStream bodyStream() throws IOException; + public boolean isBodyStreamRereadable() { + synchronized (this) { + return isBodyStreamRereadable || statusCode != HTTP_OK; + } + } /** - * Gets the {@link GitHubConnectorRequest} for this response. + * Parse a header value as a signed decimal integer. * - * @return the {@link GitHubConnectorRequest} for this response. + * @param name + * the header field to parse + * @return integer value of the header field + * @throws NumberFormatException + * if the header is missing or does not contain a parsable integer. + */ + public final int parseInt(String name) throws NumberFormatException { + try { + String headerValue = header(name); + return Integer.parseInt(headerValue); + } catch (NumberFormatException e) { + throw new NumberFormatException(name + ": " + e.getMessage()); + } + } + + /** + * Gets the {@link GitHubConnector} for this response. + * + * @return the {@link GitHubConnector} for this response. */ @Nonnull public GitHubConnectorRequest request() { return request; } + /** + * Force body stream to rereadable regardless of status code. + * + * Calling {@link #setBodyStreamRereadable()} will force {@link #isBodyStreamRereadable} to be true for this + * response regardless of {@link #statusCode} value. + * + * This is required to support body value logging during low-level tracing but should be avoided in general since it + * consumes significantly more memory. + * + * Will throw runtime exception if a non-rereadable body stream has already been returned from + * {@link #bodyStream()}. + */ + public void setBodyStreamRereadable() { + synchronized (this) { + if (bodyStreamCalled && !isBodyStreamRereadable()) { + throw new RuntimeException("bodyStream() already called in read-once mode"); + } + isBodyStreamRereadable = true; + } + } + /** * The status code for this response. * @@ -106,15 +265,20 @@ public int statusCode() { } /** - * The headers for this response. + * Get the raw implementation specific body stream for this response. * - * @return the headers for this response. + * This method will only be called once to completion. If an exception is thrown by this method, it may be called + * multiple times. + * + * The stream returned from this method will be closed when the response is closed or sooner. Inheriting classes do + * not need to close it. + * + * @return the stream for the raw response + * @throws IOException + * if an I/O Exception occurs. */ - @Nonnull - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable map of unmodifiable lists") - public Map> allHeaders() { - return headers; - } + @CheckForNull + protected abstract InputStream rawBodyStream() throws IOException; /** * Handles wrapping the body stream if indicated by the "Content-Encoding" header. @@ -134,95 +298,4 @@ protected InputStream wrapStream(InputStream stream) throws IOException { throw new UnsupportedOperationException("Unexpected Content-Encoding: " + encoding); } - - /** - * Parse a header value as a signed decimal integer. - * - * @param name - * the header field to parse - * @return integer value of the header field - * @throws NumberFormatException - * if the header is missing or does not contain a parsable integer. - */ - public final int parseInt(String name) throws NumberFormatException { - try { - String headerValue = header(name); - return Integer.parseInt(headerValue); - } catch (NumberFormatException e) { - throw new NumberFormatException(name + ": " + e.getMessage()); - } - } - - /** - * A ByteArrayResponse class - */ - public abstract static class ByteArrayResponse extends GitHubConnectorResponse { - - private boolean inputStreamRead = false; - private byte[] inputBytes = null; - private boolean isClosed = false; - - /** - * Constructor for ByteArray Response - * - * @param request - * the request - * @param statusCode - * the status code - * @param headers - * the headers - */ - protected ByteArrayResponse(@Nonnull GitHubConnectorRequest request, - int statusCode, - @Nonnull Map> headers) { - super(request, statusCode, headers); - } - - /** - * {@inheritDoc} - */ - @Override - @Nonnull - public InputStream bodyStream() throws IOException { - if (isClosed) { - throw new IOException("Response is closed"); - } - synchronized (this) { - if (!inputStreamRead) { - InputStream rawStream = rawBodyStream(); - try (InputStream stream = wrapStream(rawStream)) { - if (stream != null) { - inputBytes = IOUtils.toByteArray(stream); - } - } - inputStreamRead = true; - } - } - - if (inputBytes == null) { - throw new IOException("Response body missing, stream null"); - } - - return new ByteArrayInputStream(inputBytes); - } - - /** - * Get the raw implementation specific body stream for this response. - * - * This method will only be called once to completion. If an exception is thrown, it may be called multiple - * times. - * - * @return the stream for the raw response - * @throws IOException - * if an I/O Exception occurs. - */ - @CheckForNull - protected abstract InputStream rawBodyStream() throws IOException; - - @Override - public void close() throws IOException { - isClosed = true; - this.inputBytes = null; - } - } } diff --git a/src/main/java/org/kohsuke/github/example/dataobject/ReadOnlyObjects.java b/src/main/java/org/kohsuke/github/example/dataobject/ReadOnlyObjects.java index d9b0f0e2a7..8a0b8dbb4b 100644 --- a/src/main/java/org/kohsuke/github/example/dataobject/ReadOnlyObjects.java +++ b/src/main/java/org/kohsuke/github/example/dataobject/ReadOnlyObjects.java @@ -31,12 +31,6 @@ */ public final class ReadOnlyObjects { - /** - * Placeholder constructor. - */ - public ReadOnlyObjects() { - } - /** * All GHMeta data objects should expose these values. * @@ -44,18 +38,11 @@ public ReadOnlyObjects() { */ public interface GHMetaExample { /** - * Is verifiable password authentication boolean. - * - * @return the boolean - */ - boolean isVerifiablePasswordAuthentication(); - - /** - * Gets hooks. + * Gets api. * - * @return the hooks + * @return the api */ - List getHooks(); + List getApi(); /** * Gets git. @@ -65,18 +52,18 @@ public interface GHMetaExample { List getGit(); /** - * Gets web. + * Gets hooks. * - * @return the web + * @return the hooks */ - List getWeb(); + List getHooks(); /** - * Gets api. + * Gets importer. * - * @return the api + * @return the importer */ - List getApi(); + List getImporter(); /** * Gets pages. @@ -86,300 +73,190 @@ public interface GHMetaExample { List getPages(); /** - * Gets importer. + * Gets web. * - * @return the importer + * @return the web */ - List getImporter(); + List getWeb(); + + /** + * Is verifiable password authentication boolean. + * + * @return the boolean + */ + boolean isVerifiablePasswordAuthentication(); } /** - * This version uses public getters and setters and leaves it up to Jackson how it wants to fill them. + * This version uses only public getters and returns unmodifiable lists and has final fields *

* Pro: *

    - *
  • Easy to create
  • - *
  • Not much code
  • - *
  • Minimal annotations
  • + *
  • Moderate amount of code
  • + *
  • More annotations
  • + *
  • Fields final and lists unmodifiable
  • *
* Con: *
    - *
  • Exposes public setters for fields that should not be changed, flagged by spotbugs
  • - *
  • Lists modifiable when they should not be changed
  • - *
  • Jackson generally doesn't call the setters, it just sets the fields directly
  • + *
  • Extra allocations - default array lists will be replaced by Jackson (yes, even though they are final)
  • + *
  • Added constructor is annoying
  • + *
  • If this object could be refreshed or populated, then the final is misleading (and possibly buggy)
  • *
* - * @author Paulo Miguel Almeida + * @author Liam Newman * @see org.kohsuke.github.GHMeta */ - public static class GHMetaPublic implements GHMetaExample { - - /** - * Create default GHMetaPublic instance - */ - public GHMetaPublic() { - } - - @JsonProperty("verifiable_password_authentication") - private boolean verifiablePasswordAuthentication; - private List hooks; - private List git; - private List web; - private List api; - private List pages; - private List importer; + public static class GHMetaGettersFinal implements GHMetaExample { - public boolean isVerifiablePasswordAuthentication() { - return verifiablePasswordAuthentication; - } + private final List api = new ArrayList<>(); + private final List git = new ArrayList<>(); + private final List hooks = new ArrayList<>(); + private final List importer = new ArrayList<>(); + private final List pages = new ArrayList<>(); + private final boolean verifiablePasswordAuthentication; + private final List web = new ArrayList<>(); - /** - * Sets verifiable password authentication. - * - * @param verifiablePasswordAuthentication - * the verifiable password authentication - */ - public void setVerifiablePasswordAuthentication(boolean verifiablePasswordAuthentication) { + @JsonCreator + private GHMetaGettersFinal( + @JsonProperty("verifiable_password_authentication") boolean verifiablePasswordAuthentication) { + // boolean fields when final seem to be really final, so we have to switch to constructor this.verifiablePasswordAuthentication = verifiablePasswordAuthentication; } - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") - public List getHooks() { - return hooks; - } - - /** - * Sets hooks. - * - * @param hooks - * the hooks - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") - public void setHooks(List hooks) { - this.hooks = hooks; + public List getApi() { + return Collections.unmodifiableList(api); } - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") public List getGit() { - return git; - } - - /** - * Sets git. - * - * @param git - * the git - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") - public void setGit(List git) { - this.git = git; - } - - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") - public List getWeb() { - return web; - } - - /** - * Sets web. - * - * @param web - * the web - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") - public void setWeb(List web) { - this.web = web; + return Collections.unmodifiableList(git); } - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") - public List getApi() { - return api; + public List getHooks() { + return Collections.unmodifiableList(hooks); } - /** - * Sets api. - * - * @param api - * the api - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") - public void setApi(List api) { - this.api = api; + public List getImporter() { + return Collections.unmodifiableList(importer); } - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") public List getPages() { - return pages; - } - - /** - * Sets pages. - * - * @param pages - * the pages - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") - public void setPages(List pages) { - this.pages = pages; + return Collections.unmodifiableList(pages); } - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") - public List getImporter() { - return importer; + public List getWeb() { + return Collections.unmodifiableList(web); } - /** - * Sets importer. - * - * @param importer - * the importer - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") - public void setImporter(List importer) { - this.importer = importer; + public boolean isVerifiablePasswordAuthentication() { + return verifiablePasswordAuthentication; } - } /** - * This version uses public getters and shows that package or private setters both can be used by jackson. You can - * check this by running in debug and setting break points in the setters. - * + * This version uses only public getters and returns unmodifiable lists *

* Pro: *

    - *
  • Easy to create
  • - *
  • Not much code
  • - *
  • Some annotations
  • + *
  • Fields final and lists unmodifiable
  • + *
  • Construction behavior can be controlled - if values depended on each other or needed to be set in a specific + * order, this could do that.
  • + *
  • JsonProrperty "required" works on JsonCreator constructors - lets annotation define required values
  • *
* Con: *
    - *
  • Exposes some package setters for fields that should not be changed, better than public
  • - *
  • Lists modifiable when they should not be changed
  • + *
  • There is no way you'd know about this without some research
  • + *
  • Specific annotations needed
  • + *
  • Nonnull annotations are misleading - null value is not checked even for "required" constructor + * parameters
  • + *
  • Brittle and verbose - not friendly to large number of fields
  • *
* * @author Liam Newman * @see org.kohsuke.github.GHMeta */ - public static class GHMetaPackage implements GHMetaExample { - - /** - * Create default GHMetaPackage instance - */ - public GHMetaPackage() { - } - - private boolean verifiablePasswordAuthentication; - private List hooks; - private List git; - private List web; - private List api; - private List pages; - - /** - * Missing {@link JsonProperty} or having it on the field will cause Jackson to ignore getters and setters. - */ - @JsonProperty - private List importer; - - @JsonProperty("verifiable_password_authentication") - public boolean isVerifiablePasswordAuthentication() { - return verifiablePasswordAuthentication; - } - - private void setVerifiablePasswordAuthentication(boolean verifiablePasswordAuthentication) { - this.verifiablePasswordAuthentication = verifiablePasswordAuthentication; - } + public static class GHMetaGettersFinalCreator implements GHMetaExample { - @JsonProperty - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") - public List getHooks() { - return hooks; - } + private final List api; + private final List git; + private final List hooks; + private final List importer; + private final List pages; + private final boolean verifiablePasswordAuthentication; + private final List web; /** - * Setters can be private (or package local) and will still be called by Jackson. The {@link JsonProperty} can - * got on the getter or setter and still work. * * @param hooks - * list of hooks - */ - private void setHooks(List hooks) { - this.hooks = hooks; - } - - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") - public List getGit() { - return git; - } - - /** - * Since we mostly use Jackson for deserialization, {@link JsonSetter} is also okay, but {@link JsonProperty} is - * preferred. - * + * the hooks - required property works, but only on creator json properties like this, ignores + * Nonnull, checked manually * @param git - * list of git addresses + * the git list - required property works, but only on creator json properties like this, misleading + * Nonnull annotation + * @param web + * the web list - misleading Nonnull annotation + * @param api + * the api list - misleading Nonnull annotation + * @param pages + * the pages list - misleading Nonnull annotation + * @param importer + * the importer list - misleading Nonnull annotation + * @param verifiablePasswordAuthentication + * true or false */ - @JsonSetter - void setGit(List git) { - this.git = git; - } + @JsonCreator + private GHMetaGettersFinalCreator(@Nonnull @JsonProperty(value = "hooks", required = true) List hooks, + @Nonnull @JsonProperty(value = "git", required = true) List git, + @Nonnull @JsonProperty("web") List web, + @Nonnull @JsonProperty("api") List api, + @Nonnull @JsonProperty("pages") List pages, + @Nonnull @JsonProperty("importer") List importer, + @JsonProperty("verifiable_password_authentication") boolean verifiablePasswordAuthentication) { - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") - public List getWeb() { - return web; - } + // to ensure a value is actually not null we still have to do a null check + Objects.requireNonNull(hooks); - /** - * The {@link JsonProperty} can got on the getter or setter and still work. - * - * @param web - * list of web addresses - */ - void setWeb(List web) { - this.web = web; + this.verifiablePasswordAuthentication = verifiablePasswordAuthentication; + this.hooks = Collections.unmodifiableList(hooks); + this.git = Collections.unmodifiableList(git); + this.web = Collections.unmodifiableList(web); + this.api = Collections.unmodifiableList(api); + this.pages = Collections.unmodifiableList(pages); + this.importer = Collections.unmodifiableList(importer); } - @JsonProperty - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable but spotbugs doesn't detect") public List getApi() { return api; } - void setApi(List api) { - this.api = api; - } - - @JsonProperty - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") - public List getPages() { - return pages; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable but spotbugs doesn't detect") + public List getGit() { + return git; } - void setPages(List pages) { - this.pages = pages; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable but spotbugs doesn't detect") + public List getHooks() { + return hooks; } - /** - * Missing {@link JsonProperty} or having it on the field will cause Jackson to ignore getters and setters. - * - * @return list of importer addresses - */ - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable but spotbugs doesn't detect") public List getImporter() { return importer; } - /** - * Missing {@link JsonProperty} or having it on the field will cause Jackson to ignore getters and setters. - * - * @param importer - * list of importer addresses - */ - void setImporter(List importer) { - this.importer = importer; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable but spotbugs doesn't detect") + public List getPages() { + return pages; } + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable but spotbugs doesn't detect") + public List getWeb() { + return web; + } + + public boolean isVerifiablePasswordAuthentication() { + return verifiablePasswordAuthentication; + } } /** @@ -406,222 +283,345 @@ void setImporter(List importer) { */ public static class GHMetaGettersUnmodifiable implements GHMetaExample { + private List api; + + private List git; + private List hooks; /** - * Create default GHMetaGettersUnmodifiable instance + * If this were an optional member, we could fill it with an empty list by default. */ - public GHMetaGettersUnmodifiable() { - } - + private List importer = new ArrayList<>(); + private List pages; @JsonProperty("verifiable_password_authentication") private boolean verifiablePasswordAuthentication; - private List hooks; - private List git; private List web; - private List api; - private List pages; /** - * If this were an optional member, we could fill it with an empty list by default. + * Create default GHMetaGettersUnmodifiable instance */ - private List importer = new ArrayList<>(); - - public boolean isVerifiablePasswordAuthentication() { - return verifiablePasswordAuthentication; + public GHMetaGettersUnmodifiable() { } - public List getHooks() { - return Collections.unmodifiableList(hooks); + public List getApi() { + return Collections.unmodifiableList(api); } public List getGit() { return Collections.unmodifiableList(git); } - public List getWeb() { - return Collections.unmodifiableList(web); + public List getHooks() { + return Collections.unmodifiableList(hooks); } - public List getApi() { - return Collections.unmodifiableList(api); + public List getImporter() { + return Collections.unmodifiableList(importer); } public List getPages() { return Collections.unmodifiableList(pages); } - public List getImporter() { - return Collections.unmodifiableList(importer); + public List getWeb() { + return Collections.unmodifiableList(web); + } + + public boolean isVerifiablePasswordAuthentication() { + return verifiablePasswordAuthentication; } } /** - * This version uses only public getters and returns unmodifiable lists and has final fields + * This version uses public getters and shows that package or private setters both can be used by jackson. You can + * check this by running in debug and setting break points in the setters. + * *

* Pro: *

    - *
  • Moderate amount of code
  • - *
  • More annotations
  • - *
  • Fields final and lists unmodifiable
  • + *
  • Easy to create
  • + *
  • Not much code
  • + *
  • Some annotations
  • *
* Con: *
    - *
  • Extra allocations - default array lists will be replaced by Jackson (yes, even though they are final)
  • - *
  • Added constructor is annoying
  • - *
  • If this object could be refreshed or populated, then the final is misleading (and possibly buggy)
  • + *
  • Exposes some package setters for fields that should not be changed, better than public
  • + *
  • Lists modifiable when they should not be changed
  • *
* * @author Liam Newman * @see org.kohsuke.github.GHMeta */ - public static class GHMetaGettersFinal implements GHMetaExample { + public static class GHMetaPackage implements GHMetaExample { - private final boolean verifiablePasswordAuthentication; - private final List hooks = new ArrayList<>(); - private final List git = new ArrayList<>(); - private final List web = new ArrayList<>(); - private final List api = new ArrayList<>(); - private final List pages = new ArrayList<>(); - private final List importer = new ArrayList<>(); + private List api; - @JsonCreator - private GHMetaGettersFinal( - @JsonProperty("verifiable_password_authentication") boolean verifiablePasswordAuthentication) { - // boolean fields when final seem to be really final, so we have to switch to constructor - this.verifiablePasswordAuthentication = verifiablePasswordAuthentication; + private List git; + private List hooks; + /** + * Missing {@link JsonProperty} or having it on the field will cause Jackson to ignore getters and setters. + */ + @JsonProperty + private List importer; + private List pages; + private boolean verifiablePasswordAuthentication; + private List web; + + /** + * Create default GHMetaPackage instance + */ + public GHMetaPackage() { } - public boolean isVerifiablePasswordAuthentication() { - return verifiablePasswordAuthentication; + @JsonProperty + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") + public List getApi() { + return api; } + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") + public List getGit() { + return git; + } + + @JsonProperty + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") public List getHooks() { - return Collections.unmodifiableList(hooks); + return hooks; } - public List getGit() { - return Collections.unmodifiableList(git); + /** + * Missing {@link JsonProperty} or having it on the field will cause Jackson to ignore getters and setters. + * + * @return list of importer addresses + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") + public List getImporter() { + return importer; + } + + @JsonProperty + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") + public List getPages() { + return pages; } + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") public List getWeb() { - return Collections.unmodifiableList(web); + return web; } - public List getApi() { - return Collections.unmodifiableList(api); + @JsonProperty("verifiable_password_authentication") + public boolean isVerifiablePasswordAuthentication() { + return verifiablePasswordAuthentication; } - public List getPages() { - return Collections.unmodifiableList(pages); + /** + * Setters can be private (or package local) and will still be called by Jackson. The {@link JsonProperty} can + * got on the getter or setter and still work. + * + * @param hooks + * list of hooks + */ + private void setHooks(List hooks) { + this.hooks = hooks; } - public List getImporter() { - return Collections.unmodifiableList(importer); + private void setVerifiablePasswordAuthentication(boolean verifiablePasswordAuthentication) { + this.verifiablePasswordAuthentication = verifiablePasswordAuthentication; + } + + void setApi(List api) { + this.api = api; + } + + /** + * Since we mostly use Jackson for deserialization, {@link JsonSetter} is also okay, but {@link JsonProperty} is + * preferred. + * + * @param git + * list of git addresses + */ + @JsonSetter + void setGit(List git) { + this.git = git; + } + + /** + * Missing {@link JsonProperty} or having it on the field will cause Jackson to ignore getters and setters. + * + * @param importer + * list of importer addresses + */ + void setImporter(List importer) { + this.importer = importer; + } + + void setPages(List pages) { + this.pages = pages; + } + + /** + * The {@link JsonProperty} can got on the getter or setter and still work. + * + * @param web + * list of web addresses + */ + void setWeb(List web) { + this.web = web; } + } /** - * This version uses only public getters and returns unmodifiable lists + * This version uses public getters and setters and leaves it up to Jackson how it wants to fill them. *

* Pro: *

    - *
  • Fields final and lists unmodifiable
  • - *
  • Construction behavior can be controlled - if values depended on each other or needed to be set in a specific - * order, this could do that.
  • - *
  • JsonProrperty "required" works on JsonCreator constructors - lets annotation define required values
  • + *
  • Easy to create
  • + *
  • Not much code
  • + *
  • Minimal annotations
  • *
* Con: *
    - *
  • There is no way you'd know about this without some research
  • - *
  • Specific annotations needed
  • - *
  • Nonnull annotations are misleading - null value is not checked even for "required" constructor - * parameters
  • - *
  • Brittle and verbose - not friendly to large number of fields
  • + *
  • Exposes public setters for fields that should not be changed, flagged by spotbugs
  • + *
  • Lists modifiable when they should not be changed
  • + *
  • Jackson generally doesn't call the setters, it just sets the fields directly
  • *
* - * @author Liam Newman + * @author Paulo Miguel Almeida * @see org.kohsuke.github.GHMeta */ - public static class GHMetaGettersFinalCreator implements GHMetaExample { + public static class GHMetaPublic implements GHMetaExample { - private final boolean verifiablePasswordAuthentication; - private final List hooks; - private final List git; - private final List web; - private final List api; - private final List pages; - private final List importer; + private List api; + private List git; + private List hooks; + private List importer; + private List pages; + @JsonProperty("verifiable_password_authentication") + private boolean verifiablePasswordAuthentication; + private List web; /** - * - * @param hooks - * the hooks - required property works, but only on creator json properties like this, ignores - * Nonnull, checked manually - * @param git - * the git list - required property works, but only on creator json properties like this, misleading - * Nonnull annotation - * @param web - * the web list - misleading Nonnull annotation - * @param api - * the api list - misleading Nonnull annotation - * @param pages - * the pages list - misleading Nonnull annotation - * @param importer - * the importer list - misleading Nonnull annotation - * @param verifiablePasswordAuthentication - * true or false + * Create default GHMetaPublic instance */ - @JsonCreator - private GHMetaGettersFinalCreator(@Nonnull @JsonProperty(value = "hooks", required = true) List hooks, - @Nonnull @JsonProperty(value = "git", required = true) List git, - @Nonnull @JsonProperty("web") List web, - @Nonnull @JsonProperty("api") List api, - @Nonnull @JsonProperty("pages") List pages, - @Nonnull @JsonProperty("importer") List importer, - @JsonProperty("verifiable_password_authentication") boolean verifiablePasswordAuthentication) { - - // to ensure a value is actually not null we still have to do a null check - Objects.requireNonNull(hooks); + public GHMetaPublic() { + } - this.verifiablePasswordAuthentication = verifiablePasswordAuthentication; - this.hooks = Collections.unmodifiableList(hooks); - this.git = Collections.unmodifiableList(git); - this.web = Collections.unmodifiableList(web); - this.api = Collections.unmodifiableList(api); - this.pages = Collections.unmodifiableList(pages); - this.importer = Collections.unmodifiableList(importer); + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") + public List getApi() { + return api; } - public boolean isVerifiablePasswordAuthentication() { - return verifiablePasswordAuthentication; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") + public List getGit() { + return git; } - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable but spotbugs doesn't detect") + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") public List getHooks() { return hooks; } - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable but spotbugs doesn't detect") - public List getGit() { - return git; + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") + public List getImporter() { + return importer; } - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable but spotbugs doesn't detect") + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") + public List getPages() { + return pages; + } + + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") public List getWeb() { return web; } - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable but spotbugs doesn't detect") - public List getApi() { - return api; + public boolean isVerifiablePasswordAuthentication() { + return verifiablePasswordAuthentication; } - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable but spotbugs doesn't detect") - public List getPages() { - return pages; + /** + * Sets api. + * + * @param api + * the api + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") + public void setApi(List api) { + this.api = api; } - @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable but spotbugs doesn't detect") - public List getImporter() { - return importer; + /** + * Sets git. + * + * @param git + * the git + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") + public void setGit(List git) { + this.git = git; + } + + /** + * Sets hooks. + * + * @param hooks + * the hooks + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") + public void setHooks(List hooks) { + this.hooks = hooks; + } + + /** + * Sets importer. + * + * @param importer + * the importer + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") + public void setImporter(List importer) { + this.importer = importer; } + + /** + * Sets pages. + * + * @param pages + * the pages + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") + public void setPages(List pages) { + this.pages = pages; + } + + /** + * Sets verifiable password authentication. + * + * @param verifiablePasswordAuthentication + * the verifiable password authentication + */ + public void setVerifiablePasswordAuthentication(boolean verifiablePasswordAuthentication) { + this.verifiablePasswordAuthentication = verifiablePasswordAuthentication; + } + + /** + * Sets web. + * + * @param web + * the web + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") + public void setWeb(List web) { + this.web = web; + } + + } + + /** + * Placeholder constructor. + */ + public ReadOnlyObjects() { } } diff --git a/src/main/java/org/kohsuke/github/extras/HttpClientGitHubConnector.java b/src/main/java/org/kohsuke/github/extras/HttpClientGitHubConnector.java index 52f7e610d7..56c05aae93 100644 --- a/src/main/java/org/kohsuke/github/extras/HttpClientGitHubConnector.java +++ b/src/main/java/org/kohsuke/github/extras/HttpClientGitHubConnector.java @@ -27,6 +27,34 @@ @SuppressFBWarnings(value = { "CT_CONSTRUCTOR_THROW" }, justification = "Basic validation") public class HttpClientGitHubConnector implements GitHubConnector { + /** + * Initial response information when a response is initially received and before the body is processed. + * + * Implementation specific to {@link HttpResponse}. + */ + private static class HttpClientGitHubConnectorResponse extends GitHubConnectorResponse { + + @Nonnull + private final HttpResponse response; + + protected HttpClientGitHubConnectorResponse(@Nonnull GitHubConnectorRequest request, + @Nonnull HttpResponse response) { + super(request, response.statusCode(), response.headers().map()); + this.response = response; + } + + @Override + public void close() throws IOException { + super.close(); + } + + @CheckForNull + @Override + protected InputStream rawBodyStream() throws IOException { + return response.body(); + } + } + private final HttpClient client; /** @@ -87,33 +115,4 @@ public GitHubConnectorResponse send(GitHubConnectorRequest connectorRequest) thr throw (InterruptedIOException) new InterruptedIOException(e.getMessage()).initCause(e); } } - - /** - * Initial response information when a response is initially received and before the body is processed. - * - * Implementation specific to {@link HttpResponse}. - */ - private static class HttpClientGitHubConnectorResponse extends GitHubConnectorResponse.ByteArrayResponse { - - @Nonnull - private final HttpResponse response; - - protected HttpClientGitHubConnectorResponse(@Nonnull GitHubConnectorRequest request, - @Nonnull HttpResponse response) { - super(request, response.statusCode(), response.headers().map()); - this.response = response; - } - - @CheckForNull - @Override - protected InputStream rawBodyStream() throws IOException { - return response.body(); - } - - @Override - public void close() throws IOException { - super.close(); - IOUtils.closeQuietly(response.body()); - } - } } diff --git a/src/main/java/org/kohsuke/github/extras/authorization/JWTTokenProvider.java b/src/main/java/org/kohsuke/github/extras/authorization/JWTTokenProvider.java index eae8a8abca..ba7d38b325 100644 --- a/src/main/java/org/kohsuke/github/extras/authorization/JWTTokenProvider.java +++ b/src/main/java/org/kohsuke/github/extras/authorization/JWTTokenProvider.java @@ -27,18 +27,50 @@ @SuppressFBWarnings(value = { "CT_CONSTRUCTOR_THROW" }, justification = "TODO") public class JWTTokenProvider implements AuthorizationProvider { - private final PrivateKey privateKey; + /** + * Convert a PKCS#8 formatted private key in string format into a java PrivateKey + * + * @param key + * PCKS#8 string + * @return private key + * @throws GeneralSecurityException + * if we couldn't parse the string + */ + private static PrivateKey getPrivateKeyFromString(final String key) throws GeneralSecurityException { + if (key.contains(" RSA ")) { + throw new InvalidKeySpecException( + "Private key must be a PKCS#8 formatted string, to convert it from PKCS#1 use: " + + "openssl pkcs8 -topk8 -inform PEM -outform PEM -in current-key.pem -out new-key.pem -nocrypt"); + } - @Nonnull - private Instant validUntil = Instant.MIN; + // Remove all comments and whitespace from PEM + // such as "-----BEGIN PRIVATE KEY-----" and newlines + String privateKeyContent = key.replaceAll("(?m)^--.*", "").replaceAll("\\s", ""); - private String authorization; + KeyFactory kf = KeyFactory.getInstance("RSA"); + + try { + byte[] decode = Base64.getDecoder().decode(privateKeyContent); + PKCS8EncodedKeySpec keySpecPKCS8 = new PKCS8EncodedKeySpec(decode); + + return kf.generatePrivate(keySpecPKCS8); + } catch (IllegalArgumentException e) { + throw new InvalidKeySpecException("Failed to decode private key: " + e.getMessage(), e); + } + } /** * The identifier for the application */ private final String applicationId; + private String authorization; + + private final PrivateKey privateKey; + + @Nonnull + private Instant validUntil = Instant.MIN; + /** * Create a JWTTokenProvider * @@ -76,13 +108,12 @@ public JWTTokenProvider(String applicationId, Path keyPath) throws GeneralSecuri * * @param applicationId * the application id - * @param keyString - * the key string - * @throws GeneralSecurityException - * when an error occurs + * @param privateKey + * the private key */ - public JWTTokenProvider(String applicationId, String keyString) throws GeneralSecurityException { - this(applicationId, getPrivateKeyFromString(keyString)); + public JWTTokenProvider(String applicationId, PrivateKey privateKey) { + this.privateKey = privateKey; + this.applicationId = applicationId; } /** @@ -90,12 +121,13 @@ public JWTTokenProvider(String applicationId, String keyString) throws GeneralSe * * @param applicationId * the application id - * @param privateKey - * the private key + * @param keyString + * the key string + * @throws GeneralSecurityException + * when an error occurs */ - public JWTTokenProvider(String applicationId, PrivateKey privateKey) { - this.privateKey = privateKey; - this.applicationId = applicationId; + public JWTTokenProvider(String applicationId, String keyString) throws GeneralSecurityException { + this(applicationId, getPrivateKeyFromString(keyString)); } /** {@inheritDoc} */ @@ -110,54 +142,6 @@ public String getEncodedAuthorization() throws IOException { } } - /** - * Indicates whether the token considered valid. - * - *

- * This is not the same as whether the token is expired. The token is considered not valid before it actually - * expires to prevent access denied errors. - * - *

- * Made internal for testing - * - * @return false if the token has been refreshed within the required window, otherwise true - */ - boolean isNotValid() { - return Instant.now().isAfter(validUntil); - } - - /** - * Convert a PKCS#8 formatted private key in string format into a java PrivateKey - * - * @param key - * PCKS#8 string - * @return private key - * @throws GeneralSecurityException - * if we couldn't parse the string - */ - private static PrivateKey getPrivateKeyFromString(final String key) throws GeneralSecurityException { - if (key.contains(" RSA ")) { - throw new InvalidKeySpecException( - "Private key must be a PKCS#8 formatted string, to convert it from PKCS#1 use: " - + "openssl pkcs8 -topk8 -inform PEM -outform PEM -in current-key.pem -out new-key.pem -nocrypt"); - } - - // Remove all comments and whitespace from PEM - // such as "-----BEGIN PRIVATE KEY-----" and newlines - String privateKeyContent = key.replaceAll("(?m)^--.*", "").replaceAll("\\s", ""); - - KeyFactory kf = KeyFactory.getInstance("RSA"); - - try { - byte[] decode = Base64.getDecoder().decode(privateKeyContent); - PKCS8EncodedKeySpec keySpecPKCS8 = new PKCS8EncodedKeySpec(decode); - - return kf.generatePrivate(keySpecPKCS8); - } catch (IllegalArgumentException e) { - throw new InvalidKeySpecException("Failed to decode private key: " + e.getMessage(), e); - } - } - private String refreshJWT() { Instant now = Instant.now(); @@ -177,4 +161,20 @@ private String refreshJWT() { Instant getIssuedAt(Instant now) { return now.minus(Duration.ofMinutes(2)); } + + /** + * Indicates whether the token considered valid. + * + *

+ * This is not the same as whether the token is expired. The token is considered not valid before it actually + * expires to prevent access denied errors. + * + *

+ * Made internal for testing + * + * @return false if the token has been refreshed within the required window, otherwise true + */ + boolean isNotValid() { + return Instant.now().isAfter(validUntil); + } } diff --git a/src/main/java/org/kohsuke/github/extras/authorization/JwtBuilderUtil.java b/src/main/java/org/kohsuke/github/extras/authorization/JwtBuilderUtil.java index 81aa4b1272..a5535b4973 100644 --- a/src/main/java/org/kohsuke/github/extras/authorization/JwtBuilderUtil.java +++ b/src/main/java/org/kohsuke/github/extras/authorization/JwtBuilderUtil.java @@ -25,77 +25,6 @@ */ final class JwtBuilderUtil { - private static final Logger LOGGER = Logger.getLogger(JwtBuilderUtil.class.getName()); - - private static IJwtBuilder builder; - - /** - * Build a JWT. - * - * @param issuedAt - * issued at - * @param expiration - * expiration - * @param applicationId - * application id - * @param privateKey - * private key - * @return JWT - */ - static String buildJwt(Instant issuedAt, Instant expiration, String applicationId, PrivateKey privateKey) { - if (builder == null) { - createBuilderImpl(issuedAt, expiration, applicationId, privateKey); - } - return builder.buildJwt(issuedAt, expiration, applicationId, privateKey); - } - - private static void createBuilderImpl(Instant issuedAt, - Instant expiration, - String applicationId, - PrivateKey privateKey) { - // Figure out which builder to use and cache it. We don't worry about thread safety here because we're fine if - // the builder is assigned multiple times. The end result will be the same. - try { - builder = new DefaultBuilderImpl(); - } catch (NoSuchMethodError | NoClassDefFoundError e) { - LOGGER.warning( - "You are using an outdated version of the io.jsonwebtoken:jjwt-* suite. v0.12.x or later is recommended."); - - try { - ReflectionBuilderImpl reflectionBuider = new ReflectionBuilderImpl(); - // Build a JWT to eagerly check for any reflection errors. - reflectionBuider.buildJwtWithReflection(issuedAt, expiration, applicationId, privateKey); - - builder = reflectionBuider; - } catch (ReflectiveOperationException re) { - throw new GHException( - "Could not build JWT using reflection on io.jsonwebtoken:jjwt-* suite." - + "The minimum supported version is v0.11.x, v0.12.x or later is recommended.", - re); - } - } - } - - /** - * IJwtBuilder interface to isolate loading of JWT classes allowing us to catch and handle linkage errors. - */ - interface IJwtBuilder { - /** - * Build a JWT. - * - * @param issuedAt - * issued at - * @param expiration - * expiration - * @param applicationId - * application id - * @param privateKey - * private key - * @return JWT - */ - String buildJwt(Instant issuedAt, Instant expiration, String applicationId, PrivateKey privateKey); - } - /** * A class to isolate loading of JWT classes allowing us to catch and handle linkage errors. * @@ -123,6 +52,8 @@ public String buildJwt(Instant issuedAt, Instant expiration, String applicationI SignatureAlgorithm rs256 = Jwts.SIG.RS256; JwtBuilder jwtBuilder = Jwts.builder(); + // jjwt uses the legacy java date-time api + // see https://github.com/jwtk/jjwt/issues/235 for future support for java 8 date-time api jwtBuilder = jwtBuilder.issuedAt(Date.from(issuedAt)) .expiration(Date.from(expiration)) .issuer(applicationId) @@ -137,19 +68,28 @@ public String buildJwt(Instant issuedAt, Instant expiration, String applicationI */ private static final class ReflectionBuilderImpl implements IJwtBuilder { - private Method setIssuedAtMethod; + @SuppressWarnings("unchecked") + private static > T createEnumInstance(Class type, String name) { + return Enum.valueOf((Class) type, name); + } + private Enum rs256SignatureAlgorithm; + private Method serializeToJsonMethod; private Method setExpirationMethod; + private Method setIssuedAtMethod; private Method setIssuerMethod; - private Enum rs256SignatureAlgorithm; + private Method signWithMethod; - private Method serializeToJsonMethod; ReflectionBuilderImpl() throws ReflectiveOperationException { JwtBuilder jwtBuilder = Jwts.builder(); Class jwtReflectionClass = jwtBuilder.getClass(); + // jjwt uses the legacy java date-time api + // see https://github.com/jwtk/jjwt/issues/235 for future support for java 8 date-time api + // noinspection UseOfObsoleteDateTimeApi setIssuedAtMethod = jwtReflectionClass.getMethod("setIssuedAt", Date.class); setIssuerMethod = jwtReflectionClass.getMethod("setIssuer", String.class); + // noinspection UseOfObsoleteDateTimeApi setExpirationMethod = jwtReflectionClass.getMethod("setExpiration", Date.class); Class signatureAlgorithmClass = Class.forName("io.jsonwebtoken.SignatureAlgorithm"); rs256SignatureAlgorithm = createEnumInstance(signatureAlgorithmClass, "RS256"); @@ -186,6 +126,8 @@ private String buildJwtWithReflection(Instant issuedAt, PrivateKey privateKey) throws IllegalAccessException, InvocationTargetException { JwtBuilder jwtBuilder = Jwts.builder(); Object builderObj = jwtBuilder; + // jjwt uses the legacy java date-time api + // see https://github.com/jwtk/jjwt/issues/235 for future support for java 8 date-time api builderObj = setIssuedAtMethod.invoke(builderObj, Date.from(issuedAt)); builderObj = setExpirationMethod.invoke(builderObj, Date.from(expiration)); builderObj = setIssuerMethod.invoke(builderObj, applicationId); @@ -193,10 +135,76 @@ private String buildJwtWithReflection(Instant issuedAt, builderObj = serializeToJsonMethod.invoke(builderObj, new JacksonSerializer<>()); return ((JwtBuilder) builderObj).compact(); } + } - @SuppressWarnings("unchecked") - private static > T createEnumInstance(Class type, String name) { - return Enum.valueOf((Class) type, name); + /** + * IJwtBuilder interface to isolate loading of JWT classes allowing us to catch and handle linkage errors. + */ + interface IJwtBuilder { + /** + * Build a JWT. + * + * @param issuedAt + * issued at + * @param expiration + * expiration + * @param applicationId + * application id + * @param privateKey + * private key + * @return JWT + */ + String buildJwt(Instant issuedAt, Instant expiration, String applicationId, PrivateKey privateKey); + } + + private static final Logger LOGGER = Logger.getLogger(JwtBuilderUtil.class.getName()); + + private static IJwtBuilder builder; + + private static void createBuilderImpl(Instant issuedAt, + Instant expiration, + String applicationId, + PrivateKey privateKey) { + // Figure out which builder to use and cache it. We don't worry about thread safety here because we're fine if + // the builder is assigned multiple times. The end result will be the same. + try { + builder = new DefaultBuilderImpl(); + } catch (NoSuchMethodError | NoClassDefFoundError e) { + LOGGER.warning( + "You are using an outdated version of the io.jsonwebtoken:jjwt-* suite. v0.12.x or later is recommended."); + + try { + ReflectionBuilderImpl reflectionBuider = new ReflectionBuilderImpl(); + // Build a JWT to eagerly check for any reflection errors. + reflectionBuider.buildJwtWithReflection(issuedAt, expiration, applicationId, privateKey); + + builder = reflectionBuider; + } catch (ReflectiveOperationException re) { + throw new GHException( + "Could not build JWT using reflection on io.jsonwebtoken:jjwt-* suite." + + "The minimum supported version is v0.11.x, v0.12.x or later is recommended.", + re); + } + } + } + + /** + * Build a JWT. + * + * @param issuedAt + * issued at + * @param expiration + * expiration + * @param applicationId + * application id + * @param privateKey + * private key + * @return JWT + */ + static String buildJwt(Instant issuedAt, Instant expiration, String applicationId, PrivateKey privateKey) { + if (builder == null) { + createBuilderImpl(issuedAt, expiration, applicationId, privateKey); } + return builder.buildJwt(issuedAt, expiration, applicationId, privateKey); } } diff --git a/src/main/java/org/kohsuke/github/extras/okhttp3/OkHttpGitHubConnector.java b/src/main/java/org/kohsuke/github/extras/okhttp3/OkHttpGitHubConnector.java index d61fde220b..304db22b33 100644 --- a/src/main/java/org/kohsuke/github/extras/okhttp3/OkHttpGitHubConnector.java +++ b/src/main/java/org/kohsuke/github/extras/okhttp3/OkHttpGitHubConnector.java @@ -2,7 +2,6 @@ import okhttp3.*; import org.apache.commons.io.IOUtils; -import org.kohsuke.github.*; import org.kohsuke.github.connector.GitHubConnector; import org.kohsuke.github.connector.GitHubConnectorRequest; import org.kohsuke.github.connector.GitHubConnectorResponse; @@ -27,11 +26,44 @@ * @author Liam Newman */ public class OkHttpGitHubConnector implements GitHubConnector { + /** + * Initial response information when a response is initially received and before the body is processed. + * + * Implementation specific to {@link okhttp3.Response}. + */ + private static class OkHttpGitHubConnectorResponse extends GitHubConnectorResponse { + + @Nonnull + private final Response response; + + OkHttpGitHubConnectorResponse(@Nonnull GitHubConnectorRequest request, @Nonnull Response response) { + super(request, response.code(), response.headers().toMultimap()); + this.response = response; + } + + @Override + public void close() throws IOException { + super.close(); + response.close(); + } + + @CheckForNull + @Override + protected InputStream rawBodyStream() throws IOException { + ResponseBody body = response.body(); + if (body != null) { + return body.byteStream(); + } else { + return null; + } + } + } private static final String HEADER_NAME = "Cache-Control"; - private final String maxAgeHeaderValue; private final OkHttpClient client; + private final String maxAgeHeaderValue; + /** * Instantiates a new Ok http connector. * @@ -98,37 +130,4 @@ public GitHubConnectorResponse send(GitHubConnectorRequest request) throws IOExc private List TlsConnectionSpecs() { return Arrays.asList(ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT); } - - /** - * Initial response information when a response is initially received and before the body is processed. - * - * Implementation specific to {@link okhttp3.Response}. - */ - private static class OkHttpGitHubConnectorResponse extends GitHubConnectorResponse.ByteArrayResponse { - - @Nonnull - private final Response response; - - OkHttpGitHubConnectorResponse(@Nonnull GitHubConnectorRequest request, @Nonnull Response response) { - super(request, response.code(), response.headers().toMultimap()); - this.response = response; - } - - @CheckForNull - @Override - protected InputStream rawBodyStream() throws IOException { - ResponseBody body = response.body(); - if (body != null) { - return body.byteStream(); - } else { - return null; - } - } - - @Override - public void close() throws IOException { - super.close(); - response.close(); - } - } } diff --git a/src/main/java/org/kohsuke/github/internal/DefaultGitHubConnector.java b/src/main/java/org/kohsuke/github/internal/DefaultGitHubConnector.java index d7cb0b7522..5cf79548ad 100644 --- a/src/main/java/org/kohsuke/github/internal/DefaultGitHubConnector.java +++ b/src/main/java/org/kohsuke/github/internal/DefaultGitHubConnector.java @@ -14,9 +14,6 @@ */ public final class DefaultGitHubConnector { - private DefaultGitHubConnector() { - } - /** * Creates a {@link GitHubConnector} that will be used as the default connector. * @@ -47,4 +44,7 @@ static GitHubConnector create(String defaultConnectorProperty) { "Property 'test.github.connector' must reference a valid built-in connector - okhttp, httpclient, or default."); } } + + private DefaultGitHubConnector() { + } } diff --git a/src/main/java/org/kohsuke/github/internal/EnumUtils.java b/src/main/java/org/kohsuke/github/internal/EnumUtils.java index 9c4253b3cc..94f867333a 100644 --- a/src/main/java/org/kohsuke/github/internal/EnumUtils.java +++ b/src/main/java/org/kohsuke/github/internal/EnumUtils.java @@ -11,10 +11,8 @@ public final class EnumUtils { private static final Logger LOGGER = Logger.getLogger(EnumUtils.class.getName()); /** - * Returns an enum value matching the value if found, null if the value is null and {@code defaultEnum} if the value - * cannot be matched to a value of the enum. - *

- * The value is converted to uppercase before being matched to the enum values. + * Returns an enum value matching the value if found, {@code defaultEnum} if the value is null or cannot be matched + * to a value of the enum. * * @param * the type of the enum @@ -24,18 +22,26 @@ public final class EnumUtils { * the value to interpret * @param defaultEnum * the default enum value if the value doesn't match one of the enum value - * @return an enum value or null + * @return an enum value */ - public static > E getNullableEnumOrDefault(Class enumClass, String value, E defaultEnum) { - if (value == null) { - return null; + public static > E getEnumOrDefault(Class enumClass, String value, E defaultEnum) { + try { + if (value != null) { + return Enum.valueOf(enumClass, value.toUpperCase(Locale.ROOT)); + } + } catch (IllegalArgumentException e) { } - return getEnumOrDefault(enumClass, value, defaultEnum); + + LOGGER.warning("Unknown value " + value + " for enum class " + enumClass.getName() + ", defaulting to " + + defaultEnum.name()); + return defaultEnum; } /** - * Returns an enum value matching the value if found, {@code defaultEnum} if the value is null or cannot be matched - * to a value of the enum. + * Returns an enum value matching the value if found, null if the value is null and {@code defaultEnum} if the value + * cannot be matched to a value of the enum. + *

+ * The value is converted to uppercase before being matched to the enum values. * * @param * the type of the enum @@ -45,19 +51,13 @@ public static > E getNullableEnumOrDefault(Class enumClass, * the value to interpret * @param defaultEnum * the default enum value if the value doesn't match one of the enum value - * @return an enum value + * @return an enum value or null */ - public static > E getEnumOrDefault(Class enumClass, String value, E defaultEnum) { - try { - if (value != null) { - return Enum.valueOf(enumClass, value.toUpperCase(Locale.ROOT)); - } - } catch (IllegalArgumentException e) { + public static > E getNullableEnumOrDefault(Class enumClass, String value, E defaultEnum) { + if (value == null) { + return null; } - - LOGGER.warning("Unknown value " + value + " for enum class " + enumClass.getName() + ", defaulting to " - + defaultEnum.name()); - return defaultEnum; + return getEnumOrDefault(enumClass, value, defaultEnum); } private EnumUtils() { diff --git a/src/main/java/org/kohsuke/github/internal/graphql/response/GHGraphQLResponse.java b/src/main/java/org/kohsuke/github/internal/graphql/response/GHGraphQLResponse.java new file mode 100644 index 0000000000..07d012caee --- /dev/null +++ b/src/main/java/org/kohsuke/github/internal/graphql/response/GHGraphQLResponse.java @@ -0,0 +1,106 @@ +package org.kohsuke.github.internal.graphql.response; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * A response of GraphQL. + *

+ * This class is used to parse the response of GraphQL. + *

+ * + * @param + * the type of data + */ +public class GHGraphQLResponse { + + /** + * A GraphQL response with basic Object data type. + */ + public static class ObjectResponse extends GHGraphQLResponse { + /** + * ObjectResponse constructor. + * + * @param data + * GraphQL success response + * @param errors + * GraphQL failure response, This will be empty if not fail + */ + @JsonCreator + @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") + public ObjectResponse(@JsonProperty("data") Object data, @JsonProperty("errors") List errors) { + super(data, errors); + } + } + + /** + * A error of GraphQL response. Minimum implementation for GraphQL error. + */ + @SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD", "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" }, + justification = "JSON API") + private static class GraphQLError { + private String message; + + public String getMessage() { + return message; + } + } + + private final T data; + + private final List errors; + + /** + * GHGraphQLResponse constructor + * + * @param data + * GraphQL success response + * @param errors + * GraphQL failure response, This will be empty if not fail + */ + @JsonCreator + @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") + public GHGraphQLResponse(@JsonProperty("data") T data, @JsonProperty("errors") List errors) { + if (errors == null) { + errors = Collections.emptyList(); + } + this.data = data; + this.errors = Collections.unmodifiableList(errors); + } + + /** + * Get response data. + * + * @return GraphQL success response + */ + public T getData() { + if (!isSuccessful()) { + throw new RuntimeException("Response not successful, data invalid"); + } + + return data; + } + + /** + * Get response error message. + * + * @return GraphQL error messages from Github Response. Empty list when no errors occurred. + */ + public List getErrorMessages() { + return errors.stream().map(GraphQLError::getMessage).collect(Collectors.toList()); + } + + /** + * Is response succesful. + * + * @return request is succeeded. True when error list is empty. + */ + public boolean isSuccessful() { + return errors.isEmpty(); + } +} diff --git a/src/main/resources/META-INF/native-image/org.kohsuke/github-api/reflect-config.json b/src/main/resources/META-INF/native-image/org.kohsuke/github-api/reflect-config.json index 4d691214ec..52af02a2a4 100644 --- a/src/main/resources/META-INF/native-image/org.kohsuke/github-api/reflect-config.json +++ b/src/main/resources/META-INF/native-image/org.kohsuke/github-api/reflect-config.json @@ -1844,6 +1844,51 @@ "allPublicClasses": true, "allDeclaredClasses": true }, + { + "name": "org.kohsuke.github.GHContentBuilder$UserInfo", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.GHContentDeleter", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.GHContentDeleter$UserInfo", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, { "name": "org.kohsuke.github.GHContentSearchBuilder", "allPublicFields": true, @@ -1889,6 +1934,36 @@ "allPublicClasses": true, "allDeclaredClasses": true }, + { + "name": "org.kohsuke.github.GHContentUpdater", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.GHContentUpdater$UserInfo", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, { "name": "org.kohsuke.github.GHContentUpdateResponse", "allPublicFields": true, @@ -4334,6 +4409,21 @@ "allPublicClasses": true, "allDeclaredClasses": true }, + { + "name": "org.kohsuke.github.GHPullRequestReview$ReviewComment", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, { "name": "org.kohsuke.github.GHPullRequestReviewBuilder", "allPublicFields": true, @@ -5474,6 +5564,96 @@ "allPublicClasses": true, "allDeclaredClasses": true }, + { + "name": "org.kohsuke.github.GHSBOM", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.GHSBOM$CreationInfo", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.GHSBOM$ExternalRef", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.GHSBOM$Package", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.GHSBOM$Relationship", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.GHSBOMExportResult", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, { "name": "org.kohsuke.github.GHSearchBuilder", "allPublicFields": true, @@ -6703,5 +6883,126 @@ "allDeclaredMethods": true, "allPublicClasses": true, "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.GHPullRequest$EnablePullRequestAutoMergeResponse", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.GHPullRequest$EnablePullRequestAutoMergeResponse$EnablePullRequestAutoMerge", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.GHPullRequest$EnablePullRequestAutoMergeResponse$EnablePullRequestAutoMerge$EnablePullRequestAutoMergePullRequest", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.internal.graphql.response.GHGraphQLResponse", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.internal.graphql.response.GHGraphQLResponse$GraphQLError", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.internal.graphql.response.GHGraphQLResponse$ObjectResponse", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.GHAutolink", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.GitHubBridgeAdapterObject", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true } + ] diff --git a/src/main/resources/META-INF/native-image/org.kohsuke/github-api/serialization-config.json b/src/main/resources/META-INF/native-image/org.kohsuke/github-api/serialization-config.json index 3e80bb939b..b4c8f92359 100644 --- a/src/main/resources/META-INF/native-image/org.kohsuke/github-api/serialization-config.json +++ b/src/main/resources/META-INF/native-image/org.kohsuke/github-api/serialization-config.json @@ -371,6 +371,15 @@ { "name": "org.kohsuke.github.GHContentBuilder" }, + { + "name": "org.kohsuke.github.GHContentBuilder$UserInfo" + }, + { + "name": "org.kohsuke.github.GHContentDeleter" + }, + { + "name": "org.kohsuke.github.GHContentDeleter$UserInfo" + }, { "name": "org.kohsuke.github.GHContentSearchBuilder" }, @@ -380,6 +389,12 @@ { "name": "org.kohsuke.github.GHContentSearchBuilder$Sort" }, + { + "name": "org.kohsuke.github.GHContentUpdater" + }, + { + "name": "org.kohsuke.github.GHContentUpdater$UserInfo" + }, { "name": "org.kohsuke.github.GHContentUpdateResponse" }, @@ -869,6 +884,9 @@ { "name": "org.kohsuke.github.GHPullRequestReview" }, + { + "name": "org.kohsuke.github.GHPullRequestReview$ReviewComment" + }, { "name": "org.kohsuke.github.GHPullRequestReviewBuilder" }, @@ -1097,6 +1115,24 @@ { "name": "org.kohsuke.github.GHRequestedAction" }, + { + "name": "org.kohsuke.github.GHSBOM" + }, + { + "name": "org.kohsuke.github.GHSBOM$CreationInfo" + }, + { + "name": "org.kohsuke.github.GHSBOM$ExternalRef" + }, + { + "name": "org.kohsuke.github.GHSBOM$Package" + }, + { + "name": "org.kohsuke.github.GHSBOM$Relationship" + }, + { + "name": "org.kohsuke.github.GHSBOMExportResult" + }, { "name": "org.kohsuke.github.GHSearchBuilder" }, @@ -1342,5 +1378,29 @@ }, { "name": "org.kohsuke.github.SkipFromToString" + }, + { + "name": "org.kohsuke.github.GHPullRequest$EnablePullRequestAutoMergeResponse" + }, + { + "name": "org.kohsuke.github.GHPullRequest$EnablePullRequestAutoMergeResponse$EnablePullRequestAutoMerge" + }, + { + "name": "org.kohsuke.github.GHPullRequest$EnablePullRequestAutoMergeResponse$EnablePullRequestAutoMerge$EnablePullRequestAutoMergePullRequest" + }, + { + "name": "org.kohsuke.github.internal.graphql.response.GHGraphQLResponse" + }, + { + "name": "org.kohsuke.github.internal.graphql.response.GHGraphQLResponse$GraphQLError" + }, + { + "name": "org.kohsuke.github.internal.graphql.response.GHGraphQLResponse$ObjectResponse" + }, + { + "name": "org.kohsuke.github.GHAutolink" + }, + { + "name": "org.kohsuke.github.GitHubBridgeAdapterObject" } ] diff --git a/src/site/site.xml b/src/site/site.xml index a8cd5da479..589f3007e6 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -2,7 +2,7 @@ GitHub API for Java - https://github-api.kohsuke.org/ + https://hub4j.github.io/github-api/ org.kohsuke diff --git a/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java b/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java index da7d7f2f96..8a90c4f458 100644 --- a/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java +++ b/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java @@ -32,12 +32,12 @@ public class AbstractGHAppInstallationTest extends AbstractGitHubWireMockTest { private static String ENV_GITHUB_APP_ORG = "GITHUB_APP_ORG"; private static String ENV_GITHUB_APP_REPO = "GITHUB_APP_REPO"; - private static String TEST_APP_ID_1 = "82994"; - private static String TEST_APP_ID_2 = "83009"; - private static String TEST_APP_ID_3 = "89368"; private static String PRIVATE_KEY_FILE_APP_1 = "/ghapi-test-app-1.private-key.pem"; private static String PRIVATE_KEY_FILE_APP_2 = "/ghapi-test-app-2.private-key.pem"; private static String PRIVATE_KEY_FILE_APP_3 = "/ghapi-test-app-3.private-key.pem"; + private static String TEST_APP_ID_1 = "82994"; + private static String TEST_APP_ID_2 = "83009"; + private static String TEST_APP_ID_3 = "89368"; /** The jwt provider 1. */ protected final AuthorizationProvider jwtProvider1; diff --git a/src/test/java/org/kohsuke/github/AbstractGitHubWireMockTest.java b/src/test/java/org/kohsuke/github/AbstractGitHubWireMockTest.java index 7cea51bda0..0b52a9d49e 100644 --- a/src/test/java/org/kohsuke/github/AbstractGitHubWireMockTest.java +++ b/src/test/java/org/kohsuke/github/AbstractGitHubWireMockTest.java @@ -19,7 +19,6 @@ import java.io.IOException; import java.util.*; -import static org.hamcrest.Matchers.*; import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; @@ -31,7 +30,42 @@ */ public abstract class AbstractGitHubWireMockTest { - private final GitHubBuilder githubBuilder = createGitHubBuilder(); + /** + * The Class TemplatingHelper. + */ + protected static class TemplatingHelper { + + /** The test start date. */ + @SuppressWarnings("UseOfObsoleteDateTimeApi") + public Date testStartDate = new Date(); + + /** + * Instantiate TemplatingHelper + */ + public TemplatingHelper() { + } + + /** + * New response transformer. + * + * @return the response template transformer + */ + public ResponseTemplateTransformer newResponseTransformer() { + // noinspection UnqualifiedFieldAccess + testStartDate = new Date(); + return ResponseTemplateTransformer.builder() + .global(true) + .maxCacheEntries(0L) + .helper("testStartDate", new Helper<>() { + private HandlebarsCurrentDateHelper helper = new HandlebarsCurrentDateHelper(); + @Override + public Object apply(final Object context, final Options options) throws IOException { + return this.helper.apply(TemplatingHelper.this.testStartDate, options); + } + }) + .build(); + } + } /** The Constant GITHUB_API_TEST_ORG. */ final static String GITHUB_API_TEST_ORG = "hub4j-test-org"; @@ -42,46 +76,63 @@ public abstract class AbstractGitHubWireMockTest { /** The Constant STUBBED_USER_PASSWORD. */ final static String STUBBED_USER_PASSWORD = "placeholder-password"; - /** The use default git hub. */ - protected boolean useDefaultGitHub = true; - - /** The temp git hub repositories. */ - protected final Set tempGitHubRepositories = new HashSet<>(); - /** - * {@link GitHub} instance for use during test. Traffic will be part of snapshot when taken. + * Assert that. + * + * @param + * the generic type + * @param reason + * the reason + * @param actual + * the actual + * @param matcher + * the matcher */ - protected GitHub gitHub; - - private GitHub nonRecordingGitHub; - - /** The base files class path. */ - protected final String baseFilesClassPath = this.getClass().getName().replace('.', '/'); - - /** The base record path. */ - protected final String baseRecordPath = "src/test/resources/" + baseFilesClassPath + "/wiremock"; + public static void assertThat(String reason, T actual, Matcher matcher) { + MatcherAssert.assertThat(reason, actual, matcher); + } - /** The mock git hub. */ - @Rule - public final GitHubWireMockRule mockGitHub; + /** + * Assert that. + * + * @param reason + * the reason + * @param assertion + * the assertion + */ + public static void assertThat(String reason, boolean assertion) { + MatcherAssert.assertThat(reason, assertion); + } - /** The templating. */ - protected final TemplatingHelper templating = new TemplatingHelper(); + /** + * Assert that. + * + * @param + * the generic type + * @param actual + * the actual + * @param matcher + * the matcher + */ + public static void assertThat(T actual, Matcher matcher) { + MatcherAssert.assertThat("", actual, matcher); + } /** - * Instantiates a new abstract git hub wire mock test. + * Fail. */ - public AbstractGitHubWireMockTest() { - mockGitHub = new GitHubWireMockRule(this.getWireMockOptions()); + public static void fail() { + Assert.fail(); } /** - * Gets the wire mock options. + * Fail. * - * @return the wire mock options + * @param reason + * the reason */ - protected WireMockConfiguration getWireMockOptions() { - return WireMockConfiguration.options().dynamicPort().usingFilesUnderDirectory(baseRecordPath); + public static void fail(String reason) { + Assert.fail(reason); } private static GitHubBuilder createGitHubBuilder() { @@ -115,21 +166,80 @@ private static GitHubBuilder createGitHubBuilder() { } /** - * Gets the git hub builder. + * Gets the user. * - * @return the git hub builder + * @param gitHub + * the git hub + * @return the user */ - protected GitHubBuilder getGitHubBuilder() { - GitHubBuilder builder = githubBuilder.clone(); + protected static GHUser getUser(GitHub gitHub) { + try { + return gitHub.getMyself(); + } catch (IOException e) { + throw new RuntimeException(e.getMessage(), e); + } + } - if (!mockGitHub.isUseProxy()) { - // This sets the user and password to a placeholder for wiremock testing - // This makes the tests believe they are running with permissions - // The recorded stubs will behave like they running with permissions - builder.withOAuthToken(STUBBED_USER_PASSWORD, STUBBED_USER_LOGIN); + /** The mock git hub. */ + @Rule + public final GitHubWireMockRule mockGitHub; + + private final GitHubBuilder githubBuilder = createGitHubBuilder(); + + private GitHub nonRecordingGitHub; + + /** The base files class path. */ + protected final String baseFilesClassPath = this.getClass().getName().replace('.', '/'); + + /** The base record path. */ + protected final String baseRecordPath = "src/test/resources/" + baseFilesClassPath + "/wiremock"; + + /** + * {@link GitHub} instance for use during test. Traffic will be part of snapshot when taken. + */ + protected GitHub gitHub; + + /** The temp git hub repositories. */ + protected final Set tempGitHubRepositories = new HashSet<>(); + + /** The templating. */ + protected final TemplatingHelper templating = new TemplatingHelper(); + + /** The use default git hub. */ + protected boolean useDefaultGitHub = true; + + /** + * Instantiates a new abstract git hub wire mock test. + */ + public AbstractGitHubWireMockTest() { + mockGitHub = new GitHubWireMockRule(this.getWireMockOptions()); + } + + /** + * Cleanup temp repositories. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Before + @After + public void cleanupTempRepositories() throws IOException { + if (mockGitHub.isUseProxy()) { + for (String fullName : tempGitHubRepositories) { + cleanupRepository(fullName); + } } + } - return builder; + /** + * {@link GitHub} instance for use before/after test. Traffic will not be part of snapshot when taken. Should only + * be used when isUseProxy() or isTakeSnapShot(). + * + * @return a github instance after checking Authentication + */ + public GitHub getNonRecordingGitHub() { + verifyAuthenticated(nonRecordingGitHub); + return nonRecordingGitHub; } /** @@ -153,60 +263,59 @@ public void wireMockSetup() throws Exception { } } - /** - * Snapshot not allowed. - */ - protected void snapshotNotAllowed() { - assumeFalse("Test contains hand written mappings. Only valid when not taking a snapshot.", - mockGitHub.isTakeSnapshot()); - } + private GHCreateRepositoryBuilder getCreateBuilder(String name) throws IOException { + GitHub github = getNonRecordingGitHub(); - /** - * Require proxy. - * - * @param reason - * the reason - */ - protected void requireProxy(String reason) { - assumeTrue("Test only valid when proxying (-Dtest.github.useProxy to enable): " + reason, - mockGitHub.isUseProxy()); + if (mockGitHub.isTestWithOrg()) { + return github.getOrganization(GITHUB_API_TEST_ORG).createRepository(name); + } + + return github.createRepository(name); } - /** - * Verify authenticated. - * - * @param instance - * the instance - */ - protected void verifyAuthenticated(GitHub instance) { - assertThat( - "GitHub connection believes it is anonymous. Make sure you set GITHUB_OAUTH or both GITHUB_LOGIN and GITHUB_PASSWORD environment variables", - instance.isAnonymous(), - Matchers.is(false)); + private String getOrganization() throws IOException { + return mockGitHub.isTestWithOrg() ? GITHUB_API_TEST_ORG : gitHub.getMyself().getLogin(); } /** - * Gets the user. + * Cleanup repository. * - * @return the user + * @param fullName + * the full name + * @throws IOException + * Signals that an I/O exception has occurred. */ - protected GHUser getUser() { - return getUser(gitHub); + protected void cleanupRepository(String fullName) throws IOException { + if (mockGitHub.isUseProxy()) { + tempGitHubRepositories.add(fullName); + try { + GHRepository repository = getNonRecordingGitHub().getRepository(fullName); + if (repository != null) { + repository.delete(); + } + } catch (GHFileNotFoundException e) { + // Repo already deleted + } + + } } /** - * Gets the user. + * Gets the git hub builder. * - * @param gitHub - * the git hub - * @return the user + * @return the git hub builder */ - protected static GHUser getUser(GitHub gitHub) { - try { - return gitHub.getMyself(); - } catch (IOException e) { - throw new RuntimeException(e.getMessage(), e); + protected GitHubBuilder getGitHubBuilder() { + GitHubBuilder builder = githubBuilder.clone(); + + if (!mockGitHub.isUseProxy()) { + // This sets the user and password to a placeholder for wiremock testing + // This makes the tests believe they are running with permissions + // The recorded stubs will behave like they running with permissions + builder.withOAuthToken(STUBBED_USER_PASSWORD, STUBBED_USER_LOGIN); } + + return builder; } /** @@ -256,53 +365,21 @@ protected GHRepository getTempRepository(String name) throws IOException { } /** - * Cleanup temp repositories. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Before - @After - public void cleanupTempRepositories() throws IOException { - if (mockGitHub.isUseProxy()) { - for (String fullName : tempGitHubRepositories) { - cleanupRepository(fullName); - } - } - } - - /** - * Cleanup repository. + * Gets the user. * - * @param fullName - * the full name - * @throws IOException - * Signals that an I/O exception has occurred. + * @return the user */ - protected void cleanupRepository(String fullName) throws IOException { - if (mockGitHub.isUseProxy()) { - tempGitHubRepositories.add(fullName); - try { - GHRepository repository = getNonRecordingGitHub().getRepository(fullName); - if (repository != null) { - repository.delete(); - } - } catch (GHFileNotFoundException e) { - // Repo already deleted - } - - } + protected GHUser getUser() { + return getUser(gitHub); } /** - * {@link GitHub} instance for use before/after test. Traffic will not be part of snapshot when taken. Should only - * be used when isUseProxy() or isTakeSnapShot(). + * Gets the wire mock options. * - * @return a github instance after checking Authentication + * @return the wire mock options */ - public GitHub getNonRecordingGitHub() { - verifyAuthenticated(nonRecordingGitHub); - return nonRecordingGitHub; + protected WireMockConfiguration getWireMockOptions() { + return WireMockConfiguration.options().dynamicPort().usingFilesUnderDirectory(baseRecordPath); } /** @@ -317,112 +394,36 @@ protected void kohsuke() { // assumeTrue(login.equals("kohsuke") || login.equals("kohsuke2")); } - private GHCreateRepositoryBuilder getCreateBuilder(String name) throws IOException { - GitHub github = getNonRecordingGitHub(); - - if (mockGitHub.isTestWithOrg()) { - return github.getOrganization(GITHUB_API_TEST_ORG).createRepository(name); - } - - return github.createRepository(name); - } - - private String getOrganization() throws IOException { - return mockGitHub.isTestWithOrg() ? GITHUB_API_TEST_ORG : gitHub.getMyself().getLogin(); - } - /** - * Fail. - */ - public static void fail() { - Assert.fail(); - } - - /** - * Fail. + * Require proxy. * * @param reason * the reason */ - public static void fail(String reason) { - Assert.fail(reason); - } - - /** - * Assert that. - * - * @param - * the generic type - * @param actual - * the actual - * @param matcher - * the matcher - */ - public static void assertThat(T actual, Matcher matcher) { - MatcherAssert.assertThat("", actual, matcher); + protected void requireProxy(String reason) { + assumeTrue("Test only valid when proxying (-Dtest.github.useProxy to enable): " + reason, + mockGitHub.isUseProxy()); } /** - * Assert that. - * - * @param - * the generic type - * @param reason - * the reason - * @param actual - * the actual - * @param matcher - * the matcher + * Snapshot not allowed. */ - public static void assertThat(String reason, T actual, Matcher matcher) { - MatcherAssert.assertThat(reason, actual, matcher); + protected void snapshotNotAllowed() { + assumeFalse("Test contains hand written mappings. Only valid when not taking a snapshot.", + mockGitHub.isTakeSnapshot()); } /** - * Assert that. + * Verify authenticated. * - * @param reason - * the reason - * @param assertion - * the assertion - */ - public static void assertThat(String reason, boolean assertion) { - MatcherAssert.assertThat(reason, assertion); - } - - /** - * The Class TemplatingHelper. + * @param instance + * the instance */ - protected static class TemplatingHelper { - - /** The test start date. */ - public Date testStartDate = new Date(); - - /** - * Instantiate TemplatingHelper - */ - public TemplatingHelper() { - } - - /** - * New response transformer. - * - * @return the response template transformer - */ - public ResponseTemplateTransformer newResponseTransformer() { - testStartDate = new Date(); - return ResponseTemplateTransformer.builder() - .global(true) - .maxCacheEntries(0L) - .helper("testStartDate", new Helper() { - private HandlebarsCurrentDateHelper helper = new HandlebarsCurrentDateHelper(); - @Override - public Object apply(final Object context, final Options options) throws IOException { - return this.helper.apply(TemplatingHelper.this.testStartDate, options); - } - }) - .build(); - } + protected void verifyAuthenticated(GitHub instance) { + assertThat( + "GitHub connection believes it is anonymous. Make sure you set GITHUB_OAUTH or both GITHUB_LOGIN and GITHUB_PASSWORD environment variables", + instance.isAnonymous(), + Matchers.is(false)); } } diff --git a/src/test/java/org/kohsuke/github/AbuseLimitHandlerTest.java b/src/test/java/org/kohsuke/github/AbuseLimitHandlerTest.java index 2a723d14f3..2a36f2a58f 100644 --- a/src/test/java/org/kohsuke/github/AbuseLimitHandlerTest.java +++ b/src/test/java/org/kohsuke/github/AbuseLimitHandlerTest.java @@ -40,20 +40,23 @@ public class AbuseLimitHandlerTest extends AbstractGitHubWireMockTest { /** - * Instantiates a new abuse limit handler test. + * This is making an assertion about the behaviour of the mock, so it's useful for making sure we're on the right + * mock, but should not be used to validate assumptions about the behaviour of the actual GitHub API. */ - public AbuseLimitHandlerTest() { - useDefaultGitHub = false; + private static void checkErrorMessageMatches(GitHubConnectorResponse connectorResponse, String substring) + throws IOException { + try (InputStream errorStream = connectorResponse.bodyStream()) { + assertThat(errorStream, notNullValue()); + String errorString = IOUtils.toString(errorStream, StandardCharsets.UTF_8); + assertThat(errorString, containsString(substring)); + } } /** - * Gets the wire mock options. - * - * @return the wire mock options + * Instantiates a new abuse limit handler test. */ - @Override - protected WireMockConfiguration getWireMockOptions() { - return super.getWireMockOptions().extensions(templating.newResponseTransformer()); + public AbuseLimitHandlerTest() { + useDefaultGitHub = false; } /** @@ -386,19 +389,6 @@ public void onError(@NotNull GitHubConnectorResponse connectorResponse) throws I assertThat(mockGitHub.getRequestCount(), equalTo(3)); } - /** - * This is making an assertion about the behaviour of the mock, so it's useful for making sure we're on the right - * mock, but should not be used to validate assumptions about the behaviour of the actual GitHub API. - */ - private static void checkErrorMessageMatches(GitHubConnectorResponse connectorResponse, String substring) - throws IOException { - try (InputStream errorStream = connectorResponse.bodyStream()) { - assertThat(errorStream, notNullValue()); - String errorString = IOUtils.toString(errorStream, StandardCharsets.UTF_8); - assertThat(errorString, containsString(substring)); - } - } - /** * Tests the behavior of the GitHub API client when the abuse limit handler is set to WAIT then the handler waits * appropriately when secondary rate limits are encountered. @@ -582,4 +572,14 @@ public void onError(@NotNull GitHubConnectorResponse connectorResponse) throws I getTempRepository(); assertThat(mockGitHub.getRequestCount(), equalTo(3)); } + + /** + * Gets the wire mock options. + * + * @return the wire mock options + */ + @Override + protected WireMockConfiguration getWireMockOptions() { + return super.getWireMockOptions().extensions(templating.newResponseTransformer()); + } } diff --git a/src/test/java/org/kohsuke/github/AotIntegrationTest.java b/src/test/java/org/kohsuke/github/AotIntegrationTest.java index 8605d8aa46..5273f8ac9a 100644 --- a/src/test/java/org/kohsuke/github/AotIntegrationTest.java +++ b/src/test/java/org/kohsuke/github/AotIntegrationTest.java @@ -1,7 +1,7 @@ package org.kohsuke.github; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import org.junit.Test; import org.springframework.boot.test.context.SpringBootTest; @@ -39,12 +39,14 @@ public AotIntegrationTest() { */ @Test public void testIfAllRequiredClassesAreRegisteredForAot() throws IOException { + String artifactId = System.getProperty("test.projectArtifactId", "default"); + Stream providedReflectionConfigStreamOfNames = readAotConfigToStreamOfClassNames( - "./target/classes/META-INF/native-image/org.kohsuke/github-api/reflect-config.json"); + "./target/classes/META-INF/native-image/org.kohsuke/" + artifactId + "/reflect-config.json"); Stream providedNoReflectStreamOfNames = Files .lines(Path.of("./target/test-classes/no-reflect-and-serialization-list")); Stream providedSerializationStreamOfNames = readAotConfigToStreamOfClassNames( - "./target/classes/META-INF/native-image/org.kohsuke/github-api/serialization-config.json"); + "./target/classes/META-INF/native-image/org.kohsuke/" + artifactId + "/serialization-config.json"); Stream providedAotConfigClassNamesPart = Stream .concat(providedSerializationStreamOfNames, Stream.concat(providedReflectionConfigStreamOfNames, providedNoReflectStreamOfNames)) @@ -53,9 +55,11 @@ public void testIfAllRequiredClassesAreRegisteredForAot() throws IOException { .collect(Collectors.toList()); Stream generatedReflectConfigStreamOfClassNames = readAotConfigToStreamOfClassNames( - "./target/spring-aot/test/resources/META-INF/native-image/org.kohsuke/github-api/reflect-config.json"); + "./target/spring-aot/test/resources/META-INF/native-image/org.kohsuke/" + artifactId + + "/reflect-config.json"); Stream generatedSerializationStreamOfNames = readAotConfigToStreamOfClassNames( - "./target/spring-aot/test/resources/META-INF/native-image/org.kohsuke/github-api/serialization-config.json"); + "./target/spring-aot/test/resources/META-INF/native-image/org.kohsuke/" + artifactId + + "/serialization-config.json"); Stream generatedAotConfigClassNames = Stream.concat(generatedReflectConfigStreamOfClassNames, generatedSerializationStreamOfNames); @@ -76,7 +80,10 @@ public void testIfAllRequiredClassesAreRegisteredForAot() throws IOException { private Stream readAotConfigToStreamOfClassNames(String reflectionConfig) throws IOException { byte[] reflectionConfigFileAsBytes = Files.readAllBytes(Path.of(reflectionConfig)); - ArrayNode reflectConfigJsonArray = (ArrayNode) new ObjectMapper().readTree(reflectionConfigFileAsBytes); + // Test methods are allowed to directly use whatever jackson is available. + ArrayNode reflectConfigJsonArray = (ArrayNode) JsonMapper.builder() + .build() + .readTree(reflectionConfigFileAsBytes); return StreamSupport .stream(Spliterators.spliteratorUnknownSize(reflectConfigJsonArray.iterator(), Spliterator.ORDERED), false) diff --git a/src/test/java/org/kohsuke/github/AotTestRuntimeHints.java b/src/test/java/org/kohsuke/github/AotTestRuntimeHints.java index 9f184dee66..bc357abb57 100644 --- a/src/test/java/org/kohsuke/github/AotTestRuntimeHints.java +++ b/src/test/java/org/kohsuke/github/AotTestRuntimeHints.java @@ -20,12 +20,12 @@ */ public class AotTestRuntimeHints implements RuntimeHintsRegistrar { - private static final Logger LOGGER = Logger.getLogger(AotTestRuntimeHints.class.getName()); - private static final String CLASSPATH_IDENTIFIER = "/target/classes"; private static final String LOCATION_PATTERN_OF_ORG_KOHSUKE_GITHUB_CLASSES = "classpath*:org/kohsuke/github/**/*.class"; + private static final Logger LOGGER = Logger.getLogger(AotTestRuntimeHints.class.getName()); + /** * Default constructor. */ diff --git a/src/test/java/org/kohsuke/github/AppTest.java b/src/test/java/org/kohsuke/github/AppTest.java index ec2938ae02..0e2e46f5af 100755 --- a/src/test/java/org/kohsuke/github/AppTest.java +++ b/src/test/java/org/kohsuke/github/AppTest.java @@ -28,561 +28,663 @@ */ public class AppTest extends AbstractGitHubWireMockTest { + /** The Constant GITHUB_API_TEST_REPO. */ + static final String GITHUB_API_TEST_REPO = "github-api-test"; + /** * Create default AppTest instance */ public AppTest() { } - /** The Constant GITHUB_API_TEST_REPO. */ - static final String GITHUB_API_TEST_REPO = "github-api-test"; - /** - * Test repo CRUD. + * Blob. * * @throws Exception * the exception */ @Test - public void testRepoCRUD() throws Exception { - String targetName = "github-api-test-rename2"; - - cleanupUserRepository("github-api-test-rename"); - cleanupUserRepository(targetName); - - GHRepository r = gitHub.createRepository("github-api-test-rename") - .description("a test repository") - .homepage("http://github-api.kohsuke.org/") - .private_(false) - .create(); - - assertThat(r.hasIssues(), is(true)); - assertThat(r.hasWiki(), is(true)); - assertThat(r.hasDownloads(), is(true)); - assertThat(r.hasProjects(), is(true)); - - r.enableIssueTracker(false); - r.enableDownloads(false); - r.enableWiki(false); - r.enableProjects(false); - - r.renameTo(targetName); - - // local instance remains unchanged - assertThat(r.getName(), equalTo("github-api-test-rename")); - assertThat(r.hasIssues(), is(true)); - assertThat(r.hasWiki(), is(true)); - assertThat(r.hasDownloads(), is(true)); - assertThat(r.hasProjects(), is(true)); - - r = gitHub.getMyself().getRepository(targetName); + public void blob() throws Exception { + Assume.assumeFalse(SystemUtils.IS_OS_WINDOWS); - // values are updated - assertThat(r.hasIssues(), is(false)); - assertThat(r.hasWiki(), is(false)); - assertThat(r.hasDownloads(), is(false)); - assertThat(r.getName(), equalTo(targetName)); + GHRepository r = gitHub.getRepository("hub4j/github-api"); + String sha1 = "a12243f2fc5b8c2ba47dd677d0b0c7583539584d"; - assertThat(r.hasProjects(), is(false)); + verifyBlobContent(r.readBlob(sha1)); - r.delete(); + GHBlob blob = r.getBlob(sha1); + verifyBlobContent(blob.read()); + assertThat(blob.getSha(), is("a12243f2fc5b8c2ba47dd677d0b0c7583539584d")); + assertThat(blob.getSize(), is(1104L)); } /** - * Test repository with auto initialization CRUD. + * Check to string. * * @throws Exception * the exception */ + @Ignore("Needs mocking check") @Test - public void testRepositoryWithAutoInitializationCRUD() throws Exception { - String name = "github-api-test-autoinit"; - cleanupUserRepository(name); - GHRepository r = gitHub.createRepository(name) - .description("a test repository for auto init") - .homepage("http://github-api.kohsuke.org/") - .autoInit(true) - .create(); - if (mockGitHub.isUseProxy()) { - Thread.sleep(3000); - } - assertThat(r.getReadme(), notNullValue()); - - r.delete(); - } - - private void cleanupUserRepository(final String name) throws IOException { - if (mockGitHub.isUseProxy()) { - cleanupRepository(getUser(getNonRecordingGitHub()).getLogin() + "/" + name); - } - } - - /** - * Test credential valid. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Test - public void testCredentialValid() throws IOException { - assertThat(gitHub.isCredentialValid(), is(true)); - assertThat(gitHub.lastRateLimit().getCore(), not(instanceOf(GHRateLimit.UnknownLimitRecord.class))); - assertThat(gitHub.lastRateLimit().getCore().getLimit(), equalTo(5000)); - - gitHub = getGitHubBuilder().withOAuthToken("bogus", "user") - .withEndpoint(mockGitHub.apiServer().baseUrl()) - .build(); - assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT)); - assertThat(gitHub.isCredentialValid(), is(false)); - // For invalid credentials, we get a 401 but it includes anonymous rate limit headers - assertThat(gitHub.lastRateLimit().getCore(), not(instanceOf(GHRateLimit.UnknownLimitRecord.class))); - assertThat(gitHub.lastRateLimit().getCore().getLimit(), equalTo(60)); + public void checkToString() throws Exception { + // Just basic code coverage to make sure toString() doesn't blow up + GHUser u = gitHub.getUser("rails"); + // System.out.println(u); + GHRepository r = u.getRepository("rails"); + // System.out.println(r); + // System.out.println(r.getIssue(1)); } /** - * Test credential valid enterprise. + * Directory listing. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testCredentialValidEnterprise() throws IOException { - // Simulated GHE: getRateLimit returns 404 - assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT)); - assertThat(gitHub.lastRateLimit().getCore().isExpired(), is(true)); - assertThat(gitHub.isCredentialValid(), is(true)); - - // lastRateLimitUpdates because 404 still includes header rate limit info - assertThat(gitHub.lastRateLimit(), notNullValue()); - assertThat(gitHub.lastRateLimit(), not(equalTo(GHRateLimit.DEFAULT))); - assertThat(gitHub.lastRateLimit().getCore().isExpired(), is(false)); - - gitHub = getGitHubBuilder().withOAuthToken("bogus", "user") - .withEndpoint(mockGitHub.apiServer().baseUrl()) - .build(); - assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT)); - assertThat(gitHub.isCredentialValid(), is(false)); - // Simulated GHE: For invalid credentials, we get a 401 that does not include ratelimit info - assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT)); + public void directoryListing() throws IOException { + List children = gitHub.getRepository("jenkinsci/jenkins").getDirectoryContent("core"); + for (GHContent c : children) { + // System.out.println(c.getName()); + if (c.isDirectory()) { + for (GHContent d : c.listDirectoryContent()) { + // System.out.println(" " + d.getName()); + } + } + } } /** - * Test issue with no comment. + * List org memberships. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void testIssueWithNoComment() throws IOException { - GHRepository repository = gitHub.getRepository("kohsuke/test"); - GHIssue i = repository.getIssue(4); - List v = i.getComments(); - // System.out.println(v); - assertThat(v, is(empty())); + public void listOrgMemberships() throws Exception { + GHMyself me = gitHub.getMyself(); + for (GHMembership m : me.listOrgMemberships()) { + assertThat(m.getUser(), is((GHUser) me)); + assertThat(m.getState(), notNullValue()); + assertThat(m.getRole(), notNullValue()); + } } /** - * Test issue with comment. + * Notifications. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void testIssueWithComment() throws IOException { - GHRepository repository = gitHub.getRepository("kohsuke/test"); - GHIssue i = repository.getIssue(3); - List v = i.getComments(); - // System.out.println(v); - assertThat(v.size(), equalTo(3)); - assertThat(v.get(0).getHtmlUrl().toString(), - equalTo("https://github.com/kohsuke/test/issues/3#issuecomment-8547249")); - assertThat(v.get(0).getUrl().toString(), endsWith("/repos/kohsuke/test/issues/comments/8547249")); - assertThat(v.get(0).getNodeId(), equalTo("MDEyOklzc3VlQ29tbWVudDg1NDcyNDk=")); - assertThat(v.get(0).getParent().getNumber(), equalTo(3)); - assertThat(v.get(0).getParent().getId(), equalTo(6863845L)); - assertThat(v.get(0).getUser().getLogin(), equalTo("kohsuke")); - assertThat(v.get(0).listReactions().toList(), is(empty())); + public void notifications() throws Exception { + boolean found = false; + for (GHThread t : gitHub.listNotifications().since(0).nonBlocking(true).read(true)) { + if (!found) { + found = true; + // both read and unread are included + assertThat(t.getTitle(), is("Create a Jenkinsfile for Librecores CI in mor1kx")); + assertThat(t.getLastReadAt(), notNullValue()); + assertThat(t.isRead(), equalTo(true)); - assertThat(v.get(1).getHtmlUrl().toString(), - equalTo("https://github.com/kohsuke/test/issues/3#issuecomment-8547251")); - assertThat(v.get(1).getUrl().toString(), endsWith("/repos/kohsuke/test/issues/comments/8547251")); - assertThat(v.get(1).getNodeId(), equalTo("MDEyOklzc3VlQ29tbWVudDg1NDcyNTE=")); - assertThat(v.get(1).getParent().getNumber(), equalTo(3)); - assertThat(v.get(1).getUser().getLogin(), equalTo("kohsuke")); - List reactions = v.get(1).listReactions().toList(); - assertThat(reactions.size(), equalTo(3)); - assertThat(reactions.stream().map(item -> item.getContent()).collect(Collectors.toList()), - containsInAnyOrder(ReactionContent.EYES, ReactionContent.HOORAY, ReactionContent.ROCKET)); + t.markAsRead(); // test this by calling it once on old notfication + } + assertThat(t.getReason(), oneOf("subscribed", "mention", "review_requested", "comment")); + assertThat(t.getTitle(), notNullValue()); + assertThat(t.getLastCommentUrl(), notNullValue()); + assertThat(t.getRepository(), notNullValue()); + assertThat(t.getUpdatedAt(), notNullValue()); + assertThat(t.getType(), oneOf("Issue", "PullRequest")); - // TODO: Add comment CRUD test + // both thread an unread are included + // assertThat(t.getLastReadAt(), notNullValue()); + // assertThat(t.isRead(), equalTo(true)); - GHReaction reaction = null; - try { - reaction = v.get(1).createReaction(ReactionContent.CONFUSED); - v = i.getComments(); - reactions = v.get(1).listReactions().toList(); - assertThat(reactions.stream().map(item -> item.getContent()).collect(Collectors.toList()), - containsInAnyOrder(ReactionContent.CONFUSED, - ReactionContent.EYES, - ReactionContent.HOORAY, - ReactionContent.ROCKET)); + // Doesn't exist on threads but is part of GHObject. :( + assertThat(t.getCreatedAt(), nullValue()); - // test new delete reaction API - v.get(1).deleteReaction(reaction); - reaction = null; - v = i.getComments(); - reactions = v.get(1).listReactions().toList(); - assertThat(reactions.stream().map(item -> item.getContent()).collect(Collectors.toList()), - containsInAnyOrder(ReactionContent.EYES, ReactionContent.HOORAY, ReactionContent.ROCKET)); - } finally { - if (reaction != null) { - v.get(1).deleteReaction(reaction); - reaction = null; - } } + assertThat(found, is(true)); + gitHub.listNotifications().markAsRead(); + gitHub.listNotifications().iterator().next(); } /** - * Test create issue. + * Reactions. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void testCreateIssue() throws IOException { - GHUser u = getUser(); - GHRepository repository = getTestRepository(); - GHMilestone milestone = repository.createMilestone("Test Milestone Title3", "Test Milestone"); - GHIssue o = repository.createIssue("testing") - .body("this is body") - .assignee(u) - .label("bug") - .label("question") - .milestone(milestone) - .create(); - assertThat(o, notNullValue()); - assertThat(o.getBody(), equalTo("this is body")); + public void reactions() throws Exception { + GHIssue i = gitHub.getRepository("hub4j/github-api").getIssue(311); - // test locking - assertThat(o.isLocked(), is(false)); - o.lock(); - o = repository.getIssue(o.getNumber()); - assertThat(o.isLocked(), is(true)); - o.unlock(); - o = repository.getIssue(o.getNumber()); - assertThat(o.isLocked(), is(false)); + // cover issue methods + assertThat(i.getClosedAt(), equalTo(GitHubClient.parseInstant("2016-11-17T02:40:11Z"))); + assertThat(i.getHtmlUrl().toString(), endsWith("github-api/issues/311")); - o.close(); + List l; + // retrieval + l = i.listReactions().toList(); + assertThat(l.size(), equalTo(1)); + + assertThat(l.get(0).getUser().getLogin(), is("kohsuke")); + assertThat(l.get(0).getContent(), is(ReactionContent.HEART)); + + // CRUD + GHReaction a; + a = i.createReaction(ReactionContent.HOORAY); + assertThat(a.getUser().getLogin(), is(gitHub.getMyself().getLogin())); + assertThat(a.getContent(), is(ReactionContent.HOORAY)); + i.deleteReaction(a); + + l = i.listReactions().toList(); + assertThat(l.size(), equalTo(1)); + + a = i.createReaction(ReactionContent.PLUS_ONE); + assertThat(a.getUser().getLogin(), is(gitHub.getMyself().getLogin())); + assertThat(a.getContent(), is(ReactionContent.PLUS_ONE)); + + a = i.createReaction(ReactionContent.CONFUSED); + assertThat(a.getUser().getLogin(), is(gitHub.getMyself().getLogin())); + assertThat(a.getContent(), is(ReactionContent.CONFUSED)); + + a = i.createReaction(ReactionContent.EYES); + assertThat(a.getUser().getLogin(), is(gitHub.getMyself().getLogin())); + assertThat(a.getContent(), is(ReactionContent.EYES)); + + a = i.createReaction(ReactionContent.ROCKET); + assertThat(a.getUser().getLogin(), is(gitHub.getMyself().getLogin())); + assertThat(a.getContent(), is(ReactionContent.ROCKET)); + + l = i.listReactions().toList(); + assertThat(l.size(), equalTo(5)); + assertThat(l.get(0).getUser().getLogin(), is("kohsuke")); + assertThat(l.get(0).getContent(), is(ReactionContent.HEART)); + assertThat(l.get(1).getUser().getLogin(), is(gitHub.getMyself().getLogin())); + assertThat(l.get(1).getContent(), is(ReactionContent.PLUS_ONE)); + assertThat(l.get(2).getUser().getLogin(), is(gitHub.getMyself().getLogin())); + assertThat(l.get(2).getContent(), is(ReactionContent.CONFUSED)); + assertThat(l.get(3).getUser().getLogin(), is(gitHub.getMyself().getLogin())); + assertThat(l.get(3).getContent(), is(ReactionContent.EYES)); + assertThat(l.get(4).getUser().getLogin(), is(gitHub.getMyself().getLogin())); + assertThat(l.get(4).getContent(), is(ReactionContent.ROCKET)); + + i.deleteReaction(l.get(1)); + i.deleteReaction(l.get(2)); + i.deleteReaction(l.get(3)); + i.deleteReaction(l.get(4)); + + l = i.listReactions().toList(); + assertThat(l.size(), equalTo(1)); } /** - * Test create and list deployments. + * Test add deploy key. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testCreateAndListDeployments() throws IOException { - GHRepository repository = getTestRepository(); - GHDeployment deployment = repository.createDeployment("main") - .payload("{\"user\":\"atmos\",\"room_id\":123456}") - .description("question") - .environment("unittest") - .create(); + public void testAddDeployKey() throws IOException { + GHRepository myRepository = getTestRepository(); + final GHDeployKey newDeployKey = myRepository.addDeployKey("test", + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIATWwMLytklB44O66isWRKOB3Qd7Ysc7q7EyWTmT0bG9 test@example.com"); try { - assertThat(deployment.getCreator(), notNullValue()); - assertThat(deployment.getId(), notNullValue()); - List deployments = repository.listDeployments(null, "main", null, "unittest").toList(); - assertThat(deployments, notNullValue()); - assertThat(deployments, is(not(emptyIterable()))); - GHDeployment unitTestDeployment = deployments.get(0); - assertThat(unitTestDeployment.getEnvironment(), equalTo("unittest")); - assertThat(unitTestDeployment.getOriginalEnvironment(), equalTo("unittest")); - assertThat(unitTestDeployment.isProductionEnvironment(), equalTo(false)); - assertThat(unitTestDeployment.isTransientEnvironment(), equalTo(false)); - assertThat(unitTestDeployment.getRef(), equalTo("main")); + assertThat(newDeployKey.getId(), notNullValue()); + + GHDeployKey k = Iterables.find(myRepository.getDeployKeys(), new Predicate() { + public boolean apply(GHDeployKey deployKey) { + return newDeployKey.getId() == deployKey.getId() && !deployKey.isRead_only(); + } + }); + assertThat(k, notNullValue()); } finally { - // deployment.delete(); - assert true; + newDeployKey.delete(); } } /** - * Test get deployment statuses. + * Test add deploy key read-only. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testGetDeploymentStatuses() throws IOException { - GHRepository repository = getTestRepository(); - GHDeployment deployment = repository.createDeployment("main") - .description("question") - .payload("{\"user\":\"atmos\",\"room_id\":123456}") - .create(); + public void testAddDeployKeyAsReadOnly() throws IOException { + GHRepository myRepository = getTestRepository(); + final GHDeployKey newDeployKey = myRepository.addDeployKey("test", + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIATWwMLytklB44O66isWRKOB3Qd7Ysc7q7EyWTmT0bG9 test@example.com", + true); try { - GHDeploymentStatus ghDeploymentStatus = deployment.createStatus(GHDeploymentState.QUEUED) - .description("success") - .logUrl("http://www.github.com/logurl") - .environmentUrl("http://www.github.com/envurl") - .environment("new-ci-env") - .create(); - Iterable deploymentStatuses = deployment.listStatuses(); - assertThat(deploymentStatuses, notNullValue()); - assertThat(Iterables.size(deploymentStatuses), equalTo(1)); - GHDeploymentStatus actualStatus = Iterables.get(deploymentStatuses, 0); - assertThat(actualStatus.getId(), equalTo(ghDeploymentStatus.getId())); - assertThat(actualStatus.getState(), equalTo(ghDeploymentStatus.getState())); - assertThat(actualStatus.getLogUrl(), equalTo(ghDeploymentStatus.getLogUrl())); - assertThat(ghDeploymentStatus.getDeploymentUrl(), equalTo(deployment.getUrl())); - assertThat(ghDeploymentStatus.getRepositoryUrl(), equalTo(repository.getUrl())); + assertThat(newDeployKey.getId(), notNullValue()); + + GHDeployKey k = Iterables.find(myRepository.getDeployKeys(), new Predicate() { + public boolean apply(GHDeployKey deployKey) { + return newDeployKey.getId() == deployKey.getId() && deployKey.isRead_only(); + } + }); + assertThat(k, notNullValue()); } finally { - // deployment.delete(); - assert true; + newDeployKey.delete(); } } /** - * Test get issues. + * Test app. + */ + @Ignore("Needs mocking check") + @Test + public void testApp() { + // System.out.println(gitHub.getMyself().getEmails()); + + // GHRepository r = gitHub.getOrganization("jenkinsci").createRepository("kktest4", "Kohsuke's test", + // "http://kohsuke.org/", "Everyone", true); + // r.fork(); + + // tryDisablingIssueTrackers(gitHub); + + // tryDisablingWiki(gitHub); + + // GHPullRequest i = gitHub.getOrganization("jenkinsci").getRepository("sandbox").getPullRequest(1); + // for (GHIssueComment c : i.getComments()) + // // System.out.println(c); + // // System.out.println(i); + + // gitHub.getMyself().getRepository("perforce-plugin").setEmailServiceHook("kk@kohsuke.org"); + + // tryRenaming(gitHub); + // tryOrgFork(gitHub); + + // testOrganization(gitHub); + // testPostCommitHook(gitHub); + + // tryTeamCreation(gitHub); + + // t.add(gitHub.getMyself()); + // // System.out.println(t.getMembers()); + // t.remove(gitHub.getMyself()); + // // System.out.println(t.getMembers()); + + // GHRepository r = gitHub.getOrganization("HudsonLabs").createRepository("auto-test", "some description", + // "http://kohsuke.org/", "Plugin Developers", true); + + // r. + // GitHub hub = GitHub.connectAnonymously(); + //// hub.createRepository("test","test repository",null,true); + //// hub.getUserTest("kohsuke").getRepository("test").delete(); + // + // // System.out.println(hub.getUserTest("kohsuke").getRepository("hudson").getCollaborators()); + } + + /** + * Test branches. * * @throws Exception * the exception */ + @Ignore("Needs mocking check") @Test - public void testGetIssues() throws Exception { - List closedIssues = gitHub.getOrganization("hub4j") - .getRepository("github-api") - .getIssues(GHIssueState.CLOSED); - // prior to using PagedIterable GHRepository.getIssues(GHIssueState) would only retrieve 30 issues - assertThat(closedIssues.size(), greaterThan(150)); - String readRepoString = GitHub.getMappingObjectWriter().writeValueAsString(closedIssues.get(0)); + public void testBranches() throws Exception { + Map b = gitHub.getUser("jenkinsci").getRepository("jenkins").getBranches(); + // System.out.println(b); } /** - * Test query issues. + * Test check membership. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void testQueryIssues() throws IOException { - final GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("testQueryIssues"); - List openBugIssues = repo.queryIssues() - .milestone("1") - .creator(gitHub.getMyself().getLogin()) - .state(GHIssueState.OPEN) - .label("bug") - .pageSize(10) - .list() - .toList(); - GHIssue issueWithMilestone = openBugIssues.get(0); - assertThat(openBugIssues, is(not(empty()))); - assertThat(openBugIssues, hasSize(1)); - assertThat(issueWithMilestone.getTitle(), is("Issue with milestone")); - assertThat(issueWithMilestone.getAssignee().getLogin(), is("bloslo")); - assertThat(issueWithMilestone.getBody(), containsString("@bloslo")); + public void testCheckMembership() throws Exception { + kohsuke(); + GHOrganization j = gitHub.getOrganization("jenkinsci"); + GHUser kohsuke = gitHub.getUser("kohsuke"); + GHUser b = gitHub.getUser("b"); - List openIssuesWithAssignee = repo.queryIssues() - .assignee(gitHub.getMyself().getLogin()) - .state(GHIssueState.OPEN) - .list() - .toList(); - GHIssue issueWithAssignee = openIssuesWithAssignee.get(0); - assertThat(openIssuesWithAssignee, is(not(empty()))); - assertThat(openIssuesWithAssignee, hasSize(1)); - assertThat(issueWithAssignee.getLabels(), hasSize(2)); - assertThat(issueWithAssignee.getMilestone(), is(notNullValue())); + assertThat(j.hasMember(kohsuke), is(true)); + assertThat(j.hasMember(b), is(false)); - List allIssuesSince = repo.queryIssues() - .mentioned(gitHub.getMyself().getLogin()) - .state(GHIssueState.ALL) - .since(1632411646L) - .sort(GHIssueQueryBuilder.Sort.COMMENTS) - .direction(GHDirection.ASC) - .list() - .toList(); - GHIssue issueSince = allIssuesSince.get(3); - assertThat(allIssuesSince, is(not(empty()))); - assertThat(allIssuesSince, hasSize(4)); - assertThat(issueSince.getBody(), is("Test closed issue @bloslo")); - assertThat(issueSince.getState(), is(GHIssueState.CLOSED)); + assertThat(j.hasPublicMember(kohsuke), is(true)); + assertThat(j.hasPublicMember(b), is(false)); + } - List allIssuesWithLabels = repo.queryIssues() - .label("bug") - .label("test-label") - .state(GHIssueState.ALL) - .list() - .toList(); - GHIssue issueWithLabel = allIssuesWithLabels.get(0); - assertThat(allIssuesWithLabels, is(not(empty()))); - assertThat(allIssuesWithLabels, hasSize(5)); - assertThat(issueWithLabel.getComments(), hasSize(2)); - assertThat(issueWithLabel.getTitle(), is("Issue with comments")); + /** + * Test commit. + * + * @throws Exception + * the exception + */ + @Test + public void testCommit() throws Exception { + GHCommit commit = gitHub.getUser("jenkinsci") + .getRepository("jenkins") + .getCommit("08c1c9970af4d609ae754fbe803e06186e3206f7"); + assertThat(commit.getParents().size(), equalTo(1)); + assertThat(commit.listFiles().toList().size(), equalTo(1)); + assertThat(commit.getHtmlUrl().toString(), + equalTo("https://github.com/jenkinsci/jenkins/commit/08c1c9970af4d609ae754fbe803e06186e3206f7")); + assertThat(commit.getLinesAdded(), equalTo(40)); + assertThat(commit.getLinesChanged(), equalTo(48)); + assertThat(commit.getLinesDeleted(), equalTo(8)); + assertThat(commit.getParentSHA1s().size(), equalTo(1)); + assertThat(commit.getAuthoredDate(), equalTo(GitHubClient.parseInstant("2012-04-24T00:16:52Z"))); + assertThat(commit.getCommitDate(), equalTo(GitHubClient.parseInstant("2012-04-24T00:16:52Z"))); + assertThat(commit.getCommitShortInfo().getCommentCount(), equalTo(0)); + assertThat(commit.getCommitShortInfo().getAuthoredDate(), equalTo(commit.getAuthoredDate())); + assertThat(commit.getCommitShortInfo().getCommitDate(), equalTo(commit.getCommitDate())); + assertThat(commit.getCommitShortInfo().getMessage(), equalTo("creating an RC branch")); - List issuesWithLabelNull = repo.queryIssues().label(null).list().toList(); - GHIssue issueWithLabelNull = issuesWithLabelNull.get(2); - assertThat(issuesWithLabelNull, is(not(empty()))); - assertThat(issuesWithLabelNull, hasSize(6)); - assertThat(issueWithLabelNull.getTitle(), is("Closed issue")); - assertThat(issueWithLabelNull.getBody(), is("Test closed issue @bloslo")); - assertThat(issueWithLabelNull.getState(), is(GHIssueState.OPEN)); + File f = commit.listFiles().toList().get(0); + assertThat(f.getLinesChanged(), equalTo(48)); + assertThat(f.getLinesAdded(), equalTo(40)); + assertThat(f.getLinesDeleted(), equalTo(8)); + assertThat(f.getPreviousFilename(), nullValue()); + assertThat(f.getPatch(), startsWith("@@ -54,6 +54,14 @@\n")); + assertThat(f.getSha(), equalTo("04d3e54017542ad0ff46355eababacd4850ccba5")); + assertThat(f.getBlobUrl().toString(), + equalTo("https://github.com/jenkinsci/jenkins/blob/08c1c9970af4d609ae754fbe803e06186e3206f7/changelog.html")); + assertThat(f.getRawUrl().toString(), + equalTo("https://github.com/jenkinsci/jenkins/raw/08c1c9970af4d609ae754fbe803e06186e3206f7/changelog.html")); - List issuesWithLabelEmptyString = repo.queryIssues().label("").state(GHIssueState.ALL).list().toList(); - GHIssue issueWithLabelEmptyString = issuesWithLabelEmptyString.get(0); - assertThat(issuesWithLabelEmptyString, is(not(empty()))); - assertThat(issuesWithLabelEmptyString, hasSize(8)); - assertThat(issueWithLabelEmptyString.getTitle(), is("Closed issue")); - assertThat(issueWithLabelEmptyString.getBody(), is("Test closed issue @bloslo")); + assertThat(f.getStatus(), equalTo("modified")); + assertThat(f.getFileName(), equalTo("changelog.html")); + + // walk the tree + GHTree t = commit.getTree(); + assertThat(IOUtils.toString(t.getEntry("todo.txt").readAsBlob()), containsString("executor rendering")); + assertThat(t.getEntry("war").asTree(), notNullValue()); } - private GHRepository getTestRepository() throws IOException { - return getTempRepository(GITHUB_API_TEST_REPO); + /** + * Test commit comment. + * + * @throws Exception + * the exception + */ + @Test + public void testCommitComment() throws Exception { + GHRepository r = gitHub.getUser("jenkinsci").getRepository("jenkins"); + PagedIterable comments = r.listCommitComments(); + List batch = comments.iterator().nextPage(); + for (GHCommitComment comment : batch) { + // System.out.println(comment.getBody()); + assertThat(r, sameInstance(comment.getOwner())); + } } /** - * Test list issues. + * Test commit search. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testListIssues() throws IOException { - Iterable closedIssues = gitHub.getOrganization("hub4j") - .getRepository("github-api") - .queryIssues() - .state(GHIssueState.CLOSED) + public void testCommitSearch() throws IOException { + PagedSearchIterable r = gitHub.searchCommits() + .org("github-api") + .repo("github-api") + .author("kohsuke") + .sort(GHCommitSearchBuilder.Sort.COMMITTER_DATE) .list(); + assertThat(r.getTotalCount(), greaterThan(0)); - int x = 0; - for (GHIssue issue : closedIssues) { - assertThat(issue, notNullValue()); - x++; - } + GHCommit firstCommit = r.iterator().next(); + assertThat(firstCommit.listFiles().toList(), is(not(empty()))); + } - assertThat(x, greaterThan(150)); + /** + * Test commit short info. + * + * @throws Exception + * the exception + */ + @Test + public void testCommitShortInfo() throws Exception { + GHRepository r = gitHub.getRepository("hub4j/github-api"); + GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f23"); + assertThat("Kohsuke Kawaguchi", equalTo(commit.getCommitShortInfo().getAuthor().getName())); + assertThat("doc", equalTo(commit.getCommitShortInfo().getMessage())); + assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); + assertThat(GHVerification.Reason.UNSIGNED, equalTo(commit.getCommitShortInfo().getVerification().getReason())); + assertThat(commit.getCommitShortInfo().getAuthor().getDate().getEpochSecond(), equalTo(1271650361L)); + assertThat(commit.getCommitShortInfo().getCommitter().getDate().getEpochSecond(), equalTo(1271650361L)); } /** - * Test rate limit. + * Test commit status. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void testRateLimit() throws IOException { - assertThat(gitHub.getRateLimit(), notNullValue()); + public void testCommitStatus() throws Exception { + GHRepository r = gitHub.getRepository("hub4j/github-api"); + + GHCommitStatus state; + + // state = r.createCommitStatus("ecbfdd7315ef2cf04b2be7f11a072ce0bd00c396", GHCommitState.FAILURE, + // "http://kohsuke.org/", "testing!"); + + List lst = r.listCommitStatuses("ecbfdd7315ef2cf04b2be7f11a072ce0bd00c396").toList(); + state = lst.get(0); + // System.out.println(state); + assertThat(state.getDescription(), equalTo("testing!")); + assertThat(state.getTargetUrl(), equalTo("http://kohsuke.org/")); + assertThat(state.getCreator().getLogin(), equalTo("kohsuke")); } /** - * Test my organizations. + * Test commit status context. * * @throws IOException * Signals that an I/O exception has occurred. */ + @Ignore("Needs mocking check") @Test - public void testMyOrganizations() throws IOException { - Map org = gitHub.getMyOrganizations(); - assertThat(org.containsKey(null), is(false)); - // System.out.println(org); + public void testCommitStatusContext() throws IOException { + GHRepository myRepository = getTestRepository(); + GHRef mainRef = myRepository.getRef("heads/main"); + GHCommitStatus commitStatus = myRepository.createCommitStatus(mainRef.getObject() + .getSha(), GHCommitState.SUCCESS, "http://www.example.com", "test", "test/context"); + assertThat(commitStatus.getContext(), equalTo("test/context")); + } /** - * Test my organizations contain my teams. + * Test create and list deployments. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testMyOrganizationsContainMyTeams() throws IOException { - Map> teams = gitHub.getMyTeams(); - Map myOrganizations = gitHub.getMyOrganizations(); - // GitHub no longer has default 'owners' team, so there may be organization memberships without a team - // https://help.github.com/articles/about-improved-organization-permissions/ - assertThat(myOrganizations.keySet().containsAll(teams.keySet()), is(true)); + public void testCreateAndListDeployments() throws IOException { + GHRepository repository = getTestRepository(); + GHDeployment deployment = repository.createDeployment("main") + .payload("{\"user\":\"atmos\",\"room_id\":123456}") + .description("question") + .environment("unittest") + .create(); + try { + assertThat(deployment.getCreator(), notNullValue()); + assertThat(deployment.getId(), notNullValue()); + List deployments = repository.listDeployments(null, "main", null, "unittest").toList(); + assertThat(deployments, notNullValue()); + assertThat(deployments, is(not(emptyIterable()))); + GHDeployment unitTestDeployment = deployments.get(0); + assertThat(unitTestDeployment.getEnvironment(), equalTo("unittest")); + assertThat(unitTestDeployment.getOriginalEnvironment(), equalTo("unittest")); + assertThat(unitTestDeployment.isProductionEnvironment(), equalTo(false)); + assertThat(unitTestDeployment.isTransientEnvironment(), equalTo(false)); + assertThat(unitTestDeployment.getRef(), equalTo("main")); + } finally { + // deployment.delete(); + assert true; + } } /** - * Test my teams should include myself. + * Test create commit comment. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void testMyTeamsShouldIncludeMyself() throws IOException { - Map> teams = gitHub.getMyTeams(); - for (Entry> teamsPerOrg : teams.entrySet()) { - String organizationName = teamsPerOrg.getKey(); - for (GHTeam team : teamsPerOrg.getValue()) { - String teamName = team.getName(); - assertThat("Team " + teamName + " in organization " + organizationName + " does not contain myself", - shouldBelongToTeam(organizationName, teamName)); - } + public void testCreateCommitComment() throws Exception { + GHCommit commit = gitHub.getUser("kohsuke") + .getRepository("sandbox-ant") + .getCommit("8ae38db0ea5837313ab5f39d43a6f73de3bd9000"); + + assertThat(commit.getCommitShortInfo().getCommentCount(), equalTo(30)); + GHCommitComment c = commit.createComment("[testing](http://kohsuse.org/)"); + try { + assertThat(c.getPath(), nullValue()); + assertThat(c.getLine(), equalTo(-1)); + assertThat(c.getHtmlUrl().toString(), + containsString( + "kohsuke/sandbox-ant/commit/8ae38db0ea5837313ab5f39d43a6f73de3bd9000#commitcomment-")); + assertThat(c.listReactions().toList(), is(empty())); + + c.update("updated text"); + assertThat(c.getBody(), equalTo("updated text")); + + commit = gitHub.getUser("kohsuke") + .getRepository("sandbox-ant") + .getCommit("8ae38db0ea5837313ab5f39d43a6f73de3bd9000"); + + assertThat(commit.getCommitShortInfo().getCommentCount(), equalTo(31)); + + // testing reactions + List reactions = c.listReactions().toList(); + assertThat(reactions, is(empty())); + + GHReaction reaction = c.createReaction(ReactionContent.CONFUSED); + assertThat(reaction.getContent(), equalTo(ReactionContent.CONFUSED)); + + reactions = c.listReactions().toList(); + assertThat(reactions.size(), equalTo(1)); + + c.deleteReaction(reaction); + + reactions = c.listReactions().toList(); + assertThat(reactions.size(), equalTo(0)); + } finally { + c.delete(); } } /** - * Test user public organizations when there are some. + * Test create issue. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testUserPublicOrganizationsWhenThereAreSome() throws IOException { - // kohsuke had some public org memberships at the time Wiremock recorded the GitHub API responses - GHUser user = new GHUser(); - user.login = "kohsuke"; + public void testCreateIssue() throws IOException { + GHUser u = getUser(); + GHRepository repository = getTestRepository(); + GHMilestone milestone = repository.createMilestone("Test Milestone Title3", "Test Milestone"); + GHIssue o = repository.createIssue("testing") + .body("this is body") + .assignee(u) + .label("bug") + .label("question") + .milestone(milestone) + .create(); + assertThat(o, notNullValue()); + assertThat(o.getBody(), equalTo("this is body")); - Map orgs = gitHub.getUserPublicOrganizations(user); - assertThat(orgs.size(), greaterThan(0)); + // test locking + assertThat(o.isLocked(), is(false)); + o.lock(); + o = repository.getIssue(o.getNumber()); + assertThat(o.isLocked(), is(true)); + o.unlock(); + o = repository.getIssue(o.getNumber()); + assertThat(o.isLocked(), is(false)); + + o.close(); } /** - * Test user public organizations when there are none. + * Test credential valid. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testUserPublicOrganizationsWhenThereAreNone() throws IOException { - // bitwiseman had no public org memberships at the time Wiremock recorded the GitHub API responses - GHUser user = new GHUser(); - user.login = "bitwiseman"; + public void testCredentialValid() throws IOException { + assertThat(gitHub.isCredentialValid(), is(true)); + assertThat(gitHub.lastRateLimit().getCore(), not(instanceOf(GHRateLimit.UnknownLimitRecord.class))); + assertThat(gitHub.lastRateLimit().getCore().getLimit(), equalTo(5000)); - Map orgs = gitHub.getUserPublicOrganizations(user); - assertThat(orgs.size(), equalTo(0)); + gitHub = getGitHubBuilder().withOAuthToken("bogus", "user") + .withEndpoint(mockGitHub.apiServer().baseUrl()) + .build(); + assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT)); + assertThat(gitHub.isCredentialValid(), is(false)); + // For invalid credentials, we get a 401 but it includes anonymous rate limit headers + assertThat(gitHub.lastRateLimit().getCore(), not(instanceOf(GHRateLimit.UnknownLimitRecord.class))); + assertThat(gitHub.lastRateLimit().getCore().getLimit(), equalTo(60)); } - private boolean shouldBelongToTeam(String organizationName, String teamName) throws IOException { - GHOrganization org = gitHub.getOrganization(organizationName); - assertThat(org, notNullValue()); - GHTeam team = org.getTeamByName(teamName); - assertThat(team, notNullValue()); - return team.hasMember(gitHub.getMyself()); + /** + * Test credential valid enterprise. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testCredentialValidEnterprise() throws IOException { + // Simulated GHE: getRateLimit returns 404 + assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT)); + assertThat(gitHub.lastRateLimit().getCore().isExpired(), is(true)); + assertThat(gitHub.isCredentialValid(), is(true)); + + // lastRateLimitUpdates because 404 still includes header rate limit info + assertThat(gitHub.lastRateLimit(), notNullValue()); + assertThat(gitHub.lastRateLimit(), not(equalTo(GHRateLimit.DEFAULT))); + assertThat(gitHub.lastRateLimit().getCore().isExpired(), is(false)); + + gitHub = getGitHubBuilder().withOAuthToken("bogus", "user") + .withEndpoint(mockGitHub.apiServer().baseUrl()) + .build(); + assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT)); + assertThat(gitHub.isCredentialValid(), is(false)); + // Simulated GHE: For invalid credentials, we get a 401 that does not include ratelimit info + assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT)); } /** - * Test should fetch team from organization. + * Test event api. * * @throws Exception * the exception */ @Test - public void testShouldFetchTeamFromOrganization() throws Exception { - GHOrganization organization = gitHub.getOrganization(GITHUB_API_TEST_ORG); - GHTeam teamByName = organization.getTeams().get("Core Developers"); - - GHTeam teamById = organization.getTeam(teamByName.getId()); - assertThat(teamById, notNullValue()); - - assertThat(teamById.getId(), equalTo(teamByName.getId())); - assertThat(teamById.getDescription(), equalTo(teamByName.getDescription())); - - GHTeam teamById2 = organization.getTeam(teamByName.getId()); - assertThat(teamById2, notNullValue()); + public void testEventApi() throws Exception { + for (GHEventInfo ev : gitHub.getEvents()) { + if (ev.getType() == GHEvent.PULL_REQUEST) { + GHEventPayload.PullRequest pr = ev.getPayload(GHEventPayload.PullRequest.class); + assertThat(pr.getNumber(), is(pr.getPullRequest().getNumber())); - assertThat(teamById2.getId(), equalTo(teamByName.getId())); - assertThat(teamById2.getDescription(), equalTo(teamByName.getDescription())); + assertThat(pr.getPullRequest().getClosedBy(), nullValue()); + assertThat(pr.getPullRequest().getPullRequest(), nullValue()); + if (ev.getId() == 10680625394L) { + assertThat(ev.getActorLogin(), equalTo("pull[bot]")); + assertThat(ev.getOrganization(), nullValue()); + assertThat(ev.getRepository().getFullName(), equalTo("daddyfatstacksBIG/lerna")); + assertThat(ev.getCreatedAt(), equalTo(GitHubClient.parseInstant("2019-10-21T21:54:52Z"))); + assertThat(ev.getType(), equalTo(GHEvent.PULL_REQUEST)); + assertThat(pr.getPullRequest().getMergedAt(), + equalTo(GitHubClient.parseInstant("2019-10-21T21:54:52Z"))); + assertThat(pr.getPullRequest().getPatchUrl().toString(), endsWith("lerna/pull/20.patch")); + assertThat(pr.getPullRequest().getDiffUrl().toString(), endsWith("lerna/pull/20.diff")); + } + } + } } /** @@ -638,776 +740,486 @@ public void testGetAppInstallations() throws Exception { } /** - * Test repo permissions. - * - * @throws Exception - * the exception - */ - @Ignore("Needs mocking check") - @Test - public void testRepoPermissions() throws Exception { - kohsuke(); - - GHRepository r = gitHub.getOrganization(GITHUB_API_TEST_ORG).getRepository("github-api"); - assertThat(r.hasPullAccess(), is(true)); - - r = gitHub.getOrganization("github").getRepository("hub"); - assertThat(r.hasAdminAccess(), is(false)); - } - - /** - * Test get myself. - * - * @throws Exception - * the exception - */ - @Test - public void testGetMyself() throws Exception { - GHMyself me = gitHub.getMyself(); - assertThat(me, notNullValue()); - assertThat(me.root(), sameInstance(gitHub)); - assertThat(gitHub.getUser("bitwiseman"), notNullValue()); - PagedIterable ghRepositories = me.listRepositories(); - assertThat(ghRepositories, is(not(emptyIterable()))); - } - - /** - * Test public keys. - * - * @throws Exception - * the exception - */ - @Ignore("Needs mocking check") - @Test - public void testPublicKeys() throws Exception { - List keys = gitHub.getMyself().getPublicKeys(); - assertThat(keys, is(not(empty()))); - } - - /** - * Test org fork. - * - * @throws Exception - * the exception - */ - @Test - public void testOrgFork() throws Exception { - cleanupRepository(GITHUB_API_TEST_ORG + "/rubywm"); - gitHub.getRepository("kohsuke/rubywm").forkTo(gitHub.getOrganization(GITHUB_API_TEST_ORG)); - } - - /** - * Test get teams for repo. - * - * @throws Exception - * the exception - */ - @Test - public void testGetTeamsForRepo() throws Exception { - kohsuke(); - // 'Core Developers' and 'Owners' - assertThat(gitHub.getOrganization(GITHUB_API_TEST_ORG).getRepository("testGetTeamsForRepo").getTeams().size(), - equalTo(2)); - } - - /** - * Test membership. - * - * @throws Exception - * the exception - */ - @Test - public void testMembership() throws Exception { - Set members = gitHub.getOrganization(GITHUB_API_TEST_ORG) - .getRepository("jenkins") - .getCollaboratorNames(); - // System.out.println(members.contains("kohsuke")); - } - - /** - * Test member orgs. - * - * @throws Exception - * the exception - */ - @Test - public void testMemberOrgs() throws Exception { - HashSet o = gitHub.getUser("kohsuke").getOrganizations(); - assertThat(o, hasItem(hasProperty("name", equalTo("CloudBees")))); - } - - /** - * Test org teams. - * - * @throws Exception - * the exception - */ - @Test - public void testOrgTeams() throws Exception { - kohsuke(); - int sz = 0; - for (GHTeam t : gitHub.getOrganization(GITHUB_API_TEST_ORG).listTeams()) { - assertThat(t.getName(), notNullValue()); - sz++; - } - assertThat(sz, lessThan(100)); - } - - /** - * Test org team by name. - * - * @throws Exception - * the exception - */ - @Test - public void testOrgTeamByName() throws Exception { - kohsuke(); - GHTeam e = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamByName("Core Developers"); - assertThat(e, notNullValue()); - } - - /** - * Test org team by slug. - * - * @throws Exception - * the exception - */ - @Test - public void testOrgTeamBySlug() throws Exception { - kohsuke(); - GHTeam e = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug("core-developers"); - assertThat(e, notNullValue()); - } - - /** - * Test commit. + * Test get deployment statuses. * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ @Test - public void testCommit() throws Exception { - GHCommit commit = gitHub.getUser("jenkinsci") - .getRepository("jenkins") - .getCommit("08c1c9970af4d609ae754fbe803e06186e3206f7"); - assertThat(commit.getParents().size(), equalTo(1)); - assertThat(commit.listFiles().toList().size(), equalTo(1)); - assertThat(commit.getHtmlUrl().toString(), - equalTo("https://github.com/jenkinsci/jenkins/commit/08c1c9970af4d609ae754fbe803e06186e3206f7")); - assertThat(commit.getLinesAdded(), equalTo(40)); - assertThat(commit.getLinesChanged(), equalTo(48)); - assertThat(commit.getLinesDeleted(), equalTo(8)); - assertThat(commit.getParentSHA1s().size(), equalTo(1)); - assertThat(commit.getAuthoredDate(), equalTo(GitHubClient.parseDate("2012-04-24T00:16:52Z"))); - assertThat(commit.getCommitDate(), equalTo(GitHubClient.parseDate("2012-04-24T00:16:52Z"))); - assertThat(commit.getCommitShortInfo().getCommentCount(), equalTo(0)); - assertThat(commit.getCommitShortInfo().getAuthoredDate(), equalTo(commit.getAuthoredDate())); - assertThat(commit.getCommitShortInfo().getCommitDate(), equalTo(commit.getCommitDate())); - assertThat(commit.getCommitShortInfo().getMessage(), equalTo("creating an RC branch")); - - File f = commit.listFiles().toList().get(0); - assertThat(f.getLinesChanged(), equalTo(48)); - assertThat(f.getLinesAdded(), equalTo(40)); - assertThat(f.getLinesDeleted(), equalTo(8)); - assertThat(f.getPreviousFilename(), nullValue()); - assertThat(f.getPatch(), startsWith("@@ -54,6 +54,14 @@\n")); - assertThat(f.getSha(), equalTo("04d3e54017542ad0ff46355eababacd4850ccba5")); - assertThat(f.getBlobUrl().toString(), - equalTo("https://github.com/jenkinsci/jenkins/blob/08c1c9970af4d609ae754fbe803e06186e3206f7/changelog.html")); - assertThat(f.getRawUrl().toString(), - equalTo("https://github.com/jenkinsci/jenkins/raw/08c1c9970af4d609ae754fbe803e06186e3206f7/changelog.html")); - - assertThat(f.getStatus(), equalTo("modified")); - assertThat(f.getFileName(), equalTo("changelog.html")); + public void testGetDeploymentStatuses() throws IOException { + GHRepository repository = getTestRepository(); + GHDeployment deployment = repository.createDeployment("main") + .description("question") + .payload("{\"user\":\"atmos\",\"room_id\":123456}") + .create(); + try { + GHDeploymentStatus ghDeploymentStatus = deployment.createStatus(GHDeploymentState.QUEUED) + .description("success") + .logUrl("http://www.github.com/logurl") + .environmentUrl("http://www.github.com/envurl") + .environment("new-ci-env") + .create(); + Iterable deploymentStatuses = deployment.listStatuses(); + assertThat(deploymentStatuses, notNullValue()); + assertThat(Iterables.size(deploymentStatuses), equalTo(1)); + GHDeploymentStatus actualStatus = Iterables.get(deploymentStatuses, 0); + assertThat(actualStatus.getId(), equalTo(ghDeploymentStatus.getId())); + assertThat(actualStatus.getState(), equalTo(ghDeploymentStatus.getState())); + assertThat(actualStatus.getLogUrl(), equalTo(ghDeploymentStatus.getLogUrl())); + assertThat(ghDeploymentStatus.getDeploymentUrl(), equalTo(deployment.getUrl())); + assertThat(ghDeploymentStatus.getRepositoryUrl(), equalTo(repository.getUrl())); + assertThat(ghDeploymentStatus.getEnvironmentUrl().toString(), equalTo("http://www.github.com/envurl")); - // walk the tree - GHTree t = commit.getTree(); - assertThat(IOUtils.toString(t.getEntry("todo.txt").readAsBlob()), containsString("executor rendering")); - assertThat(t.getEntry("war").asTree(), notNullValue()); + } finally { + // deployment.delete(); + assert true; + } } /** - * Test list commits. + * Test getEmails. * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ @Test - public void testListCommits() throws Exception { - List sha1 = new ArrayList(); - for (GHCommit c : gitHub.getUser("kohsuke").getRepository("empty-commit").listCommits()) { - sha1.add(c.getSHA1()); - } - assertThat(sha1.get(0), equalTo("fdfad6be4db6f96faea1f153fb447b479a7a9cb7")); - assertThat(sha1.size(), equalTo(1)); + public void testGetEmails() throws IOException { + List emails = gitHub.getMyself().getEmails(); + assertThat(emails.size(), equalTo(2)); + assertThat(emails, contains("bitwiseman@gmail.com", "bitwiseman@users.noreply.github.com")); } /** - * Test branches. + * Test get issues. * * @throws Exception * the exception */ - @Ignore("Needs mocking check") @Test - public void testBranches() throws Exception { - Map b = gitHub.getUser("jenkinsci").getRepository("jenkins").getBranches(); - // System.out.println(b); + public void testGetIssues() throws Exception { + List closedIssues = gitHub.getOrganization("hub4j") + .getRepository("github-api") + .getIssues(GHIssueState.CLOSED); + // prior to using PagedIterable GHRepository.getIssues(GHIssueState) would only retrieve 30 issues + assertThat(closedIssues.size(), greaterThan(150)); + String readRepoString = GitHub.getMappingObjectWriter().writeValueAsString(closedIssues.get(0)); } /** - * Test commit comment. + * Test get myself. * * @throws Exception * the exception */ @Test - public void testCommitComment() throws Exception { - GHRepository r = gitHub.getUser("jenkinsci").getRepository("jenkins"); - PagedIterable comments = r.listCommitComments(); - List batch = comments.iterator().nextPage(); - for (GHCommitComment comment : batch) { - // System.out.println(comment.getBody()); - assertThat(r, sameInstance(comment.getOwner())); - } + public void testGetMyself() throws Exception { + GHMyself me = gitHub.getMyself(); + assertThat(me, notNullValue()); + assertThat(me.root(), sameInstance(gitHub)); + assertThat(gitHub.getUser("bitwiseman"), notNullValue()); + PagedIterable ghRepositories = me.listRepositories(); + assertThat(ghRepositories, is(not(emptyIterable()))); } /** - * Test create commit comment. + * Test get teams for repo. * * @throws Exception * the exception */ @Test - public void testCreateCommitComment() throws Exception { - GHCommit commit = gitHub.getUser("kohsuke") - .getRepository("sandbox-ant") - .getCommit("8ae38db0ea5837313ab5f39d43a6f73de3bd9000"); - - assertThat(commit.getCommitShortInfo().getCommentCount(), equalTo(30)); - GHCommitComment c = commit.createComment("[testing](http://kohsuse.org/)"); - try { - assertThat(c.getPath(), nullValue()); - assertThat(c.getLine(), equalTo(-1)); - assertThat(c.getHtmlUrl().toString(), - containsString( - "kohsuke/sandbox-ant/commit/8ae38db0ea5837313ab5f39d43a6f73de3bd9000#commitcomment-")); - assertThat(c.listReactions().toList(), is(empty())); - - c.update("updated text"); - assertThat(c.getBody(), equalTo("updated text")); - - commit = gitHub.getUser("kohsuke") - .getRepository("sandbox-ant") - .getCommit("8ae38db0ea5837313ab5f39d43a6f73de3bd9000"); - - assertThat(commit.getCommitShortInfo().getCommentCount(), equalTo(31)); - - // testing reactions - List reactions = c.listReactions().toList(); - assertThat(reactions, is(empty())); - - GHReaction reaction = c.createReaction(ReactionContent.CONFUSED); - assertThat(reaction.getContent(), equalTo(ReactionContent.CONFUSED)); - - reactions = c.listReactions().toList(); - assertThat(reactions.size(), equalTo(1)); - - c.deleteReaction(reaction); + public void testGetTeamsForRepo() throws Exception { + kohsuke(); + // 'Core Developers' and 'Owners' + assertThat(gitHub.getOrganization(GITHUB_API_TEST_ORG).getRepository("testGetTeamsForRepo").getTeams().size(), + equalTo(2)); + } - reactions = c.listReactions().toList(); - assertThat(reactions.size(), equalTo(0)); - } finally { - c.delete(); + /** + * Test issue search. + */ + @Test + public void testIssueSearch() { + PagedSearchIterable r = gitHub.searchIssues() + .mentions("kohsuke") + .isOpen() + .sort(GHIssueSearchBuilder.Sort.UPDATED) + .list(); + assertThat(r.getTotalCount(), greaterThan(0)); + for (GHIssue issue : r) { + assertThat(issue.getTitle(), notNullValue()); + PagedIterable comments = issue.listComments(); + for (GHIssueComment comment : comments) { + assertThat(comment, notNullValue()); + } } } /** - * Try hook. - * - * @throws Exception - * the exception + * Test issue search with isIssue filter to exclude pull requests. */ @Test - public void tryHook() throws Exception { - final GHOrganization o = gitHub.getOrganization(GITHUB_API_TEST_ORG); - final GHRepository r = o.getRepository("github-api"); - try { - GHHook hook = r.createWebHook(new URL("http://www.google.com/")); - assertThat(hook.getName(), equalTo("web")); - assertThat(hook.getEvents().size(), equalTo(1)); - assertThat(hook.getEvents(), contains(GHEvent.PUSH)); - assertThat(hook.getConfig().size(), equalTo(3)); - assertThat(hook.isActive(), equalTo(true)); - - GHHook hook2 = r.getHook((int) hook.getId()); - assertThat(hook2.getName(), equalTo("web")); - assertThat(hook2.getEvents().size(), equalTo(1)); - assertThat(hook2.getEvents(), contains(GHEvent.PUSH)); - assertThat(hook2.getConfig().size(), equalTo(3)); - assertThat(hook2.isActive(), equalTo(true)); - hook2.ping(); - hook2.delete(); - final GHHook finalRepoHook = hook; - GHFileNotFoundException e = Assert.assertThrows(GHFileNotFoundException.class, - () -> r.getHook((int) finalRepoHook.getId())); - assertThat(e.getMessage(), - containsString("repos/hub4j-test-org/github-api/hooks/" + finalRepoHook.getId())); - assertThat(e.getMessage(), containsString("rest/reference/repos#get-a-repository-webhook")); - - hook = r.createWebHook(new URL("http://www.google.com/")); - r.deleteHook((int) hook.getId()); - - hook = o.createWebHook(new URL("http://www.google.com/")); - assertThat(hook.getName(), equalTo("web")); - assertThat(hook.getEvents().size(), equalTo(1)); - assertThat(hook.getEvents(), contains(GHEvent.PUSH)); - assertThat(hook.getConfig().size(), equalTo(3)); - assertThat(hook.isActive(), equalTo(true)); - - hook2 = o.getHook((int) hook.getId()); - assertThat(hook2.getName(), equalTo("web")); - assertThat(hook2.getEvents().size(), equalTo(1)); - assertThat(hook2.getEvents(), contains(GHEvent.PUSH)); - assertThat(hook2.getConfig().size(), equalTo(3)); - assertThat(hook2.isActive(), equalTo(true)); - hook2.ping(); - hook2.delete(); - - final GHHook finalOrgHook = hook; - GHFileNotFoundException e2 = Assert.assertThrows(GHFileNotFoundException.class, - () -> o.getHook((int) finalOrgHook.getId())); - assertThat(e2.getMessage(), containsString("orgs/hub4j-test-org/hooks/" + finalOrgHook.getId())); - assertThat(e2.getMessage(), containsString("rest/reference/orgs#get-an-organization-webhook")); - - hook = o.createWebHook(new URL("http://www.google.com/")); - o.deleteHook((int) hook.getId()); + public void testIssueSearchIssuesOnly() { + PagedSearchIterable r = gitHub.searchIssues() + .repo("hub4j", "github-api") + .isPullRequest() + .isIssue() + .isClosed() + .sort(GHIssueSearchBuilder.Sort.CREATED) + .list(); + assertThat(r.getTotalCount(), greaterThan(0)); + int count = 0; + for (GHIssue issue : r) { + assertThat(issue.getTitle(), notNullValue()); + assertThat(issue.getPullRequest(), nullValue()); + if (++count >= 3) { + break; + } + } + } - // System.out.println(hook); - } finally { - if (mockGitHub.isUseProxy()) { - GHRepository cleanupRepo = getNonRecordingGitHub().getOrganization(GITHUB_API_TEST_ORG) - .getRepository("github-api"); - for (GHHook h : cleanupRepo.getHooks()) { - h.delete(); - } + /** + * Test issue search with isPullRequest filter to only return pull requests. + */ + @Test + public void testIssueSearchPullRequestsOnly() { + PagedSearchIterable r = gitHub.searchIssues() + .repo("hub4j", "github-api") + .isIssue() + .isPullRequest() + .isClosed() + .sort(GHIssueSearchBuilder.Sort.CREATED) + .list(); + assertThat(r.getTotalCount(), greaterThan(0)); + int count = 0; + for (GHIssue issue : r) { + assertThat(issue.getTitle(), notNullValue()); + assertThat(issue.getPullRequest(), notNullValue()); + if (++count >= 3) { + break; } } } /** - * Test event api. + * Test issue with comment. * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ @Test - public void testEventApi() throws Exception { - for (GHEventInfo ev : gitHub.getEvents()) { - if (ev.getType() == GHEvent.PULL_REQUEST) { - if (ev.getId() == 10680625394L) { - assertThat(ev.getActorLogin(), equalTo("pull[bot]")); - assertThat(ev.getOrganization(), nullValue()); - assertThat(ev.getRepository().getFullName(), equalTo("daddyfatstacksBIG/lerna")); - assertThat(ev.getCreatedAt(), equalTo(GitHubClient.parseDate("2019-10-21T21:54:52Z"))); - assertThat(ev.getType(), equalTo(GHEvent.PULL_REQUEST)); - } + public void testIssueWithComment() throws IOException { + GHRepository repository = gitHub.getRepository("kohsuke/test"); + GHIssue i = repository.getIssue(3); + List v = i.getComments(); + // System.out.println(v); + assertThat(v.size(), equalTo(3)); + assertThat(v.get(0).getHtmlUrl().toString(), + equalTo("https://github.com/kohsuke/test/issues/3#issuecomment-8547249")); + assertThat(v.get(0).getUrl().toString(), endsWith("/repos/kohsuke/test/issues/comments/8547249")); + assertThat(v.get(0).getNodeId(), equalTo("MDEyOklzc3VlQ29tbWVudDg1NDcyNDk=")); + assertThat(v.get(0).getParent().getNumber(), equalTo(3)); + assertThat(v.get(0).getParent().getId(), equalTo(6863845L)); + assertThat(v.get(0).getUser().getLogin(), equalTo("kohsuke")); + assertThat(v.get(0).listReactions().toList(), is(empty())); - GHEventPayload.PullRequest pr = ev.getPayload(GHEventPayload.PullRequest.class); - assertThat(pr.getNumber(), is(pr.getPullRequest().getNumber())); + assertThat(v.get(1).getHtmlUrl().toString(), + equalTo("https://github.com/kohsuke/test/issues/3#issuecomment-8547251")); + assertThat(v.get(1).getUrl().toString(), endsWith("/repos/kohsuke/test/issues/comments/8547251")); + assertThat(v.get(1).getNodeId(), equalTo("MDEyOklzc3VlQ29tbWVudDg1NDcyNTE=")); + assertThat(v.get(1).getParent().getNumber(), equalTo(3)); + assertThat(v.get(1).getUser().getLogin(), equalTo("kohsuke")); + List reactions = v.get(1).listReactions().toList(); + assertThat(reactions.size(), equalTo(3)); + assertThat(reactions.stream().map(item -> item.getContent()).collect(Collectors.toList()), + containsInAnyOrder(ReactionContent.EYES, ReactionContent.HOORAY, ReactionContent.ROCKET)); + + // TODO: Add comment CRUD test + + GHReaction reaction = null; + try { + reaction = v.get(1).createReaction(ReactionContent.CONFUSED); + v = i.getComments(); + reactions = v.get(1).listReactions().toList(); + assertThat(reactions.stream().map(item -> item.getContent()).collect(Collectors.toList()), + containsInAnyOrder(ReactionContent.CONFUSED, + ReactionContent.EYES, + ReactionContent.HOORAY, + ReactionContent.ROCKET)); + + // test new delete reaction API + v.get(1).deleteReaction(reaction); + reaction = null; + v = i.getComments(); + reactions = v.get(1).listReactions().toList(); + assertThat(reactions.stream().map(item -> item.getContent()).collect(Collectors.toList()), + containsInAnyOrder(ReactionContent.EYES, ReactionContent.HOORAY, ReactionContent.ROCKET)); + } finally { + if (reaction != null) { + v.get(1).deleteReaction(reaction); + reaction = null; } } } /** - * Test user public event api. + * Test issue with no comment. * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ @Test - public void testUserPublicEventApi() throws Exception { - for (GHEventInfo ev : gitHub.getUserPublicEvents("PierreBtz")) { - if (ev.getType() == GHEvent.PULL_REQUEST) { - if (ev.getId() == 27449881624L) { - assertThat(ev.getActorLogin(), equalTo("PierreBtz")); - assertThat(ev.getOrganization().getLogin(), equalTo("hub4j")); - assertThat(ev.getRepository().getFullName(), equalTo("hub4j/github-api")); - assertThat(ev.getCreatedAt(), equalTo(GitHubClient.parseDate("2023-03-02T16:37:49Z"))); - assertThat(ev.getType(), equalTo(GHEvent.PULL_REQUEST)); - } - - GHEventPayload.PullRequest pr = ev.getPayload(GHEventPayload.PullRequest.class); - assertThat(pr.getNumber(), is(pr.getPullRequest().getNumber())); - } - } + public void testIssueWithNoComment() throws IOException { + GHRepository repository = gitHub.getRepository("kohsuke/test"); + GHIssue i = repository.getIssue(4); + List v = i.getComments(); + // System.out.println(v); + assertThat(v, is(empty())); } /** - * Test getEmails. + * Test list commits. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void testGetEmails() throws IOException { - List emails = gitHub.getMyself().getEmails(); - assertThat(emails.size(), equalTo(2)); - assertThat(emails, contains("bitwiseman@gmail.com", "bitwiseman@users.noreply.github.com")); + public void testListCommits() throws Exception { + List sha1 = new ArrayList(); + for (GHCommit c : gitHub.getUser("kohsuke").getRepository("empty-commit").listCommits()) { + sha1.add(c.getSHA1()); + } + assertThat(sha1.get(0), equalTo("fdfad6be4db6f96faea1f153fb447b479a7a9cb7")); + assertThat(sha1.size(), equalTo(1)); } /** - * Test app. + * Test list issues. + * + * @throws IOException + * Signals that an I/O exception has occurred. */ - @Ignore("Needs mocking check") @Test - public void testApp() { - // System.out.println(gitHub.getMyself().getEmails()); - - // GHRepository r = gitHub.getOrganization("jenkinsci").createRepository("kktest4", "Kohsuke's test", - // "http://kohsuke.org/", "Everyone", true); - // r.fork(); - - // tryDisablingIssueTrackers(gitHub); - - // tryDisablingWiki(gitHub); - - // GHPullRequest i = gitHub.getOrganization("jenkinsci").getRepository("sandbox").getPullRequest(1); - // for (GHIssueComment c : i.getComments()) - // // System.out.println(c); - // // System.out.println(i); - - // gitHub.getMyself().getRepository("perforce-plugin").setEmailServiceHook("kk@kohsuke.org"); - - // tryRenaming(gitHub); - // tryOrgFork(gitHub); - - // testOrganization(gitHub); - // testPostCommitHook(gitHub); - - // tryTeamCreation(gitHub); - - // t.add(gitHub.getMyself()); - // // System.out.println(t.getMembers()); - // t.remove(gitHub.getMyself()); - // // System.out.println(t.getMembers()); - - // GHRepository r = gitHub.getOrganization("HudsonLabs").createRepository("auto-test", "some description", - // "http://kohsuke.org/", "Plugin Developers", true); - - // r. - // GitHub hub = GitHub.connectAnonymously(); - //// hub.createRepository("test","test repository",null,true); - //// hub.getUserTest("kohsuke").getRepository("test").delete(); - // - // // System.out.println(hub.getUserTest("kohsuke").getRepository("hudson").getCollaborators()); - } - - private void tryDisablingIssueTrackers(GitHub gitHub) throws IOException { - for (GHRepository r : gitHub.getOrganization("jenkinsci").getRepositories().values()) { - if (r.hasIssues()) { - if (r.getOpenIssueCount() == 0) { - // System.out.println("DISABLED " + r.getName()); - r.enableIssueTracker(false); - } else { - // System.out.println("UNTOUCHED " + r.getName()); - } - } - } - } + public void testListIssues() throws IOException { + Iterable closedIssues = gitHub.getOrganization("hub4j") + .getRepository("github-api") + .queryIssues() + .state(GHIssueState.CLOSED) + .list(); - private void tryDisablingWiki(GitHub gitHub) throws IOException { - for (GHRepository r : gitHub.getOrganization("jenkinsci").getRepositories().values()) { - if (r.hasWiki()) { - // System.out.println("DISABLED " + r.getName()); - r.enableWiki(false); - } + int x = 0; + for (GHIssue issue : closedIssues) { + assertThat(issue, notNullValue()); + x++; } - } - - private void tryUpdatingIssueTracker(GitHub gitHub) throws IOException { - GHRepository r = gitHub.getOrganization("jenkinsci").getRepository("lib-task-reactor"); - // System.out.println(r.hasIssues()); - // System.out.println(r.getOpenIssueCount()); - r.enableIssueTracker(false); - } - - private void tryRenaming(GitHub gitHub) throws IOException { - gitHub.getUser("kohsuke").getRepository("test").renameTo("test2"); - } - private void tryTeamCreation(GitHub gitHub) throws IOException { - GHOrganization o = gitHub.getOrganization("HudsonLabs"); - GHTeam t = o.createTeam("auto team").permission(Permission.PUSH).create(); - t.add(o.getRepository("auto-test")); + assertThat(x, greaterThan(150)); } /** - * Test org repositories. + * Test member orgs. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void testOrgRepositories() throws IOException { - kohsuke(); - GHOrganization j = gitHub.getOrganization("jenkinsci"); - long start = System.currentTimeMillis(); - Map repos = j.getRepositories(); - long end = System.currentTimeMillis(); - // System.out.printf("%d repositories in %dms\n", repos.size(), end - start); + public void testMemberOrgs() throws Exception { + HashSet o = gitHub.getUser("kohsuke").getOrganizations(); + assertThat(o, hasItem(hasProperty("name", equalTo("CloudBees")))); } /** - * Test organization. + * Test member pagenation. * * @throws IOException * Signals that an I/O exception has occurred. */ + @Ignore("Needs mocking check") @Test - public void testOrganization() throws IOException { - kohsuke(); - GHOrganization j = gitHub.getOrganization(GITHUB_API_TEST_ORG); - GHTeam t = j.getTeams().get("Core Developers"); - - assertThat(j.getRepository("jenkins"), notNullValue()); - - // t.add(labs.getRepository("xyz")); + public void testMemberPagenation() throws IOException { + Set all = new HashSet(); + for (GHUser u : gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamByName("Core Developers").listMembers()) { + // System.out.println(u.getLogin()); + all.add(u); + } + assertThat(all, is(not(empty()))); } /** - * Test commit status. + * Test membership. * * @throws Exception * the exception */ @Test - public void testCommitStatus() throws Exception { - GHRepository r = gitHub.getRepository("hub4j/github-api"); - - GHCommitStatus state; - - // state = r.createCommitStatus("ecbfdd7315ef2cf04b2be7f11a072ce0bd00c396", GHCommitState.FAILURE, - // "http://kohsuke.org/", "testing!"); - - List lst = r.listCommitStatuses("ecbfdd7315ef2cf04b2be7f11a072ce0bd00c396").toList(); - state = lst.get(0); - // System.out.println(state); - assertThat(state.getDescription(), equalTo("testing!")); - assertThat(state.getTargetUrl(), equalTo("http://kohsuke.org/")); - assertThat(state.getCreator().getLogin(), equalTo("kohsuke")); + public void testMembership() throws Exception { + Set members = gitHub.getOrganization(GITHUB_API_TEST_ORG) + .getRepository("jenkins") + .getCollaboratorNames(); + // System.out.println(members.contains("kohsuke")); } /** - * Test commit short info. + * Test my organizations. * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ @Test - public void testCommitShortInfo() throws Exception { - GHRepository r = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f23"); - assertThat("Kohsuke Kawaguchi", equalTo(commit.getCommitShortInfo().getAuthor().getName())); - assertThat("doc", equalTo(commit.getCommitShortInfo().getMessage())); - assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); - assertThat(GHVerification.Reason.UNSIGNED, equalTo(commit.getCommitShortInfo().getVerification().getReason())); - assertThat(commit.getCommitShortInfo().getAuthor().getDate().toInstant().getEpochSecond(), - equalTo(1271650361L)); - assertThat(commit.getCommitShortInfo().getCommitter().getDate().toInstant().getEpochSecond(), - equalTo(1271650361L)); + public void testMyOrganizations() throws IOException { + Map org = gitHub.getMyOrganizations(); + assertThat(org.containsKey(null), is(false)); + // System.out.println(org); } /** - * Test pull request populate. + * Test my organizations contain my teams. * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ - @Ignore("Needs mocking check") @Test - public void testPullRequestPopulate() throws Exception { - GHRepository r = gitHub.getUser("kohsuke").getRepository("github-api"); - GHPullRequest p = r.getPullRequest(17); - GHUser u = p.getUser(); - assertThat(u.getName(), notNullValue()); + public void testMyOrganizationsContainMyTeams() throws IOException { + Map> teams = gitHub.getMyTeams(); + Map myOrganizations = gitHub.getMyOrganizations(); + // GitHub no longer has default 'owners' team, so there may be organization memberships without a team + // https://help.github.com/articles/about-improved-organization-permissions/ + assertThat(myOrganizations.keySet().containsAll(teams.keySet()), is(true)); } /** - * Test check membership. + * Test my teams should include myself. * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ @Test - public void testCheckMembership() throws Exception { - kohsuke(); - GHOrganization j = gitHub.getOrganization("jenkinsci"); - GHUser kohsuke = gitHub.getUser("kohsuke"); - GHUser b = gitHub.getUser("b"); - - assertThat(j.hasMember(kohsuke), is(true)); - assertThat(j.hasMember(b), is(false)); - - assertThat(j.hasPublicMember(kohsuke), is(true)); - assertThat(j.hasPublicMember(b), is(false)); + public void testMyTeamsShouldIncludeMyself() throws IOException { + Map> teams = gitHub.getMyTeams(); + for (Entry> teamsPerOrg : teams.entrySet()) { + String organizationName = teamsPerOrg.getKey(); + for (GHTeam team : teamsPerOrg.getValue()) { + String teamName = team.getName(); + assertThat("Team " + teamName + " in organization " + organizationName + " does not contain myself", + shouldBelongToTeam(organizationName, teamName)); + } + } } /** - * Test ref. + * Test org fork. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void testRef() throws IOException { - GHRef mainRef = gitHub.getRepository("jenkinsci/jenkins").getRef("heads/master"); - assertThat(mainRef.getUrl().toString(), - equalTo(mockGitHub.apiServer().baseUrl() + "/repos/jenkinsci/jenkins/git/refs/heads/master")); + public void testOrgFork() throws Exception { + cleanupRepository(GITHUB_API_TEST_ORG + "/rubywm"); + gitHub.getRepository("kohsuke/rubywm").forkTo(gitHub.getOrganization(GITHUB_API_TEST_ORG)); } /** - * Directory listing. + * Test org repositories. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void directoryListing() throws IOException { - List children = gitHub.getRepository("jenkinsci/jenkins").getDirectoryContent("core"); - for (GHContent c : children) { - // System.out.println(c.getName()); - if (c.isDirectory()) { - for (GHContent d : c.listDirectoryContent()) { - // System.out.println(" " + d.getName()); - } - } - } + public void testOrgRepositories() throws IOException { + kohsuke(); + GHOrganization j = gitHub.getOrganization("jenkinsci"); + long start = System.currentTimeMillis(); + Map repos = j.getRepositories(); + long end = System.currentTimeMillis(); + // System.out.printf("%d repositories in %dms\n", repos.size(), end - start); } /** - * Test add deploy key. + * Test org team by name. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void testAddDeployKey() throws IOException { - GHRepository myRepository = getTestRepository(); - final GHDeployKey newDeployKey = myRepository.addDeployKey("test", - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIATWwMLytklB44O66isWRKOB3Qd7Ysc7q7EyWTmT0bG9 test@example.com"); - try { - assertThat(newDeployKey.getId(), notNullValue()); - - GHDeployKey k = Iterables.find(myRepository.getDeployKeys(), new Predicate() { - public boolean apply(GHDeployKey deployKey) { - return newDeployKey.getId() == deployKey.getId() && !deployKey.isRead_only(); - } - }); - assertThat(k, notNullValue()); - } finally { - newDeployKey.delete(); - } + public void testOrgTeamByName() throws Exception { + kohsuke(); + GHTeam e = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamByName("Core Developers"); + assertThat(e, notNullValue()); } /** - * Test add deploy key read-only. + * Test org team by slug. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void testAddDeployKeyAsReadOnly() throws IOException { - GHRepository myRepository = getTestRepository(); - final GHDeployKey newDeployKey = myRepository.addDeployKey("test", - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIATWwMLytklB44O66isWRKOB3Qd7Ysc7q7EyWTmT0bG9 test@example.com", - true); - try { - assertThat(newDeployKey.getId(), notNullValue()); - - GHDeployKey k = Iterables.find(myRepository.getDeployKeys(), new Predicate() { - public boolean apply(GHDeployKey deployKey) { - return newDeployKey.getId() == deployKey.getId() && deployKey.isRead_only(); - } - }); - assertThat(k, notNullValue()); - } finally { - newDeployKey.delete(); - } + public void testOrgTeamBySlug() throws Exception { + kohsuke(); + GHTeam e = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug("core-developers"); + assertThat(e, notNullValue()); } /** - * Test commit status context. + * Test org teams. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ - @Ignore("Needs mocking check") @Test - public void testCommitStatusContext() throws IOException { - GHRepository myRepository = getTestRepository(); - GHRef mainRef = myRepository.getRef("heads/main"); - GHCommitStatus commitStatus = myRepository.createCommitStatus(mainRef.getObject() - .getSha(), GHCommitState.SUCCESS, "http://www.example.com", "test", "test/context"); - assertThat(commitStatus.getContext(), equalTo("test/context")); - + public void testOrgTeams() throws Exception { + kohsuke(); + int sz = 0; + for (GHTeam t : gitHub.getOrganization(GITHUB_API_TEST_ORG).listTeams()) { + assertThat(t.getName(), notNullValue()); + sz++; + } + assertThat(sz, lessThan(100)); } /** - * Test member pagenation. + * Test organization. * * @throws IOException * Signals that an I/O exception has occurred. */ - @Ignore("Needs mocking check") @Test - public void testMemberPagenation() throws IOException { - Set all = new HashSet(); - for (GHUser u : gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamByName("Core Developers").listMembers()) { - // System.out.println(u.getLogin()); - all.add(u); - } - assertThat(all, is(not(empty()))); + public void testOrganization() throws IOException { + kohsuke(); + GHOrganization j = gitHub.getOrganization(GITHUB_API_TEST_ORG); + GHTeam t = j.getTeams().get("Core Developers"); + + assertThat(j.getRepository("jenkins"), notNullValue()); + + // t.add(labs.getRepository("xyz")); } /** - * Test commit search. + * Test public keys. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ + @Ignore("Needs mocking check") @Test - public void testCommitSearch() throws IOException { - PagedSearchIterable r = gitHub.searchCommits() - .org("github-api") - .repo("github-api") - .author("kohsuke") - .sort(GHCommitSearchBuilder.Sort.COMMITTER_DATE) - .list(); - assertThat(r.getTotalCount(), greaterThan(0)); - - GHCommit firstCommit = r.iterator().next(); - assertThat(firstCommit.listFiles().toList(), is(not(empty()))); + public void testPublicKeys() throws Exception { + List keys = gitHub.getMyself().getPublicKeys(); + assertThat(keys, is(not(empty()))); } /** - * Test issue search. + * Test pull request populate. + * + * @throws Exception + * the exception */ + @Ignore("Needs mocking check") @Test - public void testIssueSearch() { - PagedSearchIterable r = gitHub.searchIssues() - .mentions("kohsuke") - .isOpen() - .sort(GHIssueSearchBuilder.Sort.UPDATED) - .list(); - assertThat(r.getTotalCount(), greaterThan(0)); - for (GHIssue issue : r) { - assertThat(issue.getTitle(), notNullValue()); - PagedIterable comments = issue.listComments(); - for (GHIssueComment comment : comments) { - assertThat(comment, notNullValue()); - } - } + public void testPullRequestPopulate() throws Exception { + GHRepository r = gitHub.getUser("kohsuke").getRepository("github-api"); + GHPullRequest p = r.getPullRequest(17); + GHUser u = p.getUser(); + assertThat(u.getName(), notNullValue()); } /** @@ -1432,23 +1244,111 @@ public void testPullRequestSearch() throws Exception { newPR.setLabels("test"); Thread.sleep(1000); - List pullRequests = gitHub.searchPullRequests() - .repo(repository) - .createdByMe() - .isOpen() - .label("test") - .list() - .toList(); - assertThat(pullRequests.size(), is(1)); - assertThat(pullRequests.get(0).getNumber(), is(newPR.getNumber())); + List pullRequests = gitHub.searchPullRequests() + .repo(repository) + .createdByMe() + .isOpen() + .label("test") + .list() + .toList(); + assertThat(pullRequests.size(), is(1)); + assertThat(pullRequests.get(0).getNumber(), is(newPR.getNumber())); + + int totalCount = gitHub.searchPullRequests() + .repo(repository) + .author(repository.getOwner()) + .isMerged() + .list() + .getTotalCount(); + assertThat(totalCount, is(0)); + } + + /** + * Test query issues. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testQueryIssues() throws IOException { + final GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("testQueryIssues"); + List openBugIssues = repo.queryIssues() + .milestone("1") + .creator(gitHub.getMyself().getLogin()) + .state(GHIssueState.OPEN) + .label("bug") + .pageSize(10) + .list() + .toList(); + GHIssue issueWithMilestone = openBugIssues.get(0); + assertThat(openBugIssues, is(not(empty()))); + assertThat(openBugIssues, hasSize(1)); + assertThat(issueWithMilestone.getTitle(), is("Issue with milestone")); + assertThat(issueWithMilestone.getAssignee().getLogin(), is("bloslo")); + assertThat(issueWithMilestone.getBody(), containsString("@bloslo")); + + List openIssuesWithAssignee = repo.queryIssues() + .assignee(gitHub.getMyself().getLogin()) + .state(GHIssueState.OPEN) + .list() + .toList(); + GHIssue issueWithAssignee = openIssuesWithAssignee.get(0); + assertThat(openIssuesWithAssignee, is(not(empty()))); + assertThat(openIssuesWithAssignee, hasSize(1)); + assertThat(issueWithAssignee.getLabels(), hasSize(2)); + assertThat(issueWithAssignee.getMilestone(), is(notNullValue())); + + List allIssuesSince = repo.queryIssues() + .mentioned(gitHub.getMyself().getLogin()) + .state(GHIssueState.ALL) + .since(1632411646L) + .sort(GHIssueQueryBuilder.Sort.COMMENTS) + .direction(GHDirection.ASC) + .list() + .toList(); + GHIssue issueSince = allIssuesSince.get(3); + assertThat(allIssuesSince, is(not(empty()))); + assertThat(allIssuesSince, hasSize(4)); + assertThat(issueSince.getBody(), is("Test closed issue @bloslo")); + assertThat(issueSince.getState(), is(GHIssueState.CLOSED)); + + List allIssuesWithLabels = repo.queryIssues() + .label("bug") + .label("test-label") + .state(GHIssueState.ALL) + .list() + .toList(); + GHIssue issueWithLabel = allIssuesWithLabels.get(0); + assertThat(allIssuesWithLabels, is(not(empty()))); + assertThat(allIssuesWithLabels, hasSize(5)); + assertThat(issueWithLabel.getComments(), hasSize(2)); + assertThat(issueWithLabel.getTitle(), is("Issue with comments")); + + List issuesWithLabelNull = repo.queryIssues().label(null).list().toList(); + GHIssue issueWithLabelNull = issuesWithLabelNull.get(2); + assertThat(issuesWithLabelNull, is(not(empty()))); + assertThat(issuesWithLabelNull, hasSize(6)); + assertThat(issueWithLabelNull.getTitle(), is("Closed issue")); + assertThat(issueWithLabelNull.getBody(), is("Test closed issue @bloslo")); + assertThat(issueWithLabelNull.getState(), is(GHIssueState.OPEN)); + + List issuesWithLabelEmptyString = repo.queryIssues().label("").state(GHIssueState.ALL).list().toList(); + GHIssue issueWithLabelEmptyString = issuesWithLabelEmptyString.get(0); + assertThat(issuesWithLabelEmptyString, is(not(empty()))); + assertThat(issuesWithLabelEmptyString, hasSize(8)); + assertThat(issueWithLabelEmptyString.getTitle(), is("Closed issue")); + assertThat(issueWithLabelEmptyString.getBody(), is("Test closed issue @bloslo")); + } - int totalCount = gitHub.searchPullRequests() - .repo(repository) - .author(repository.getOwner()) - .isMerged() - .list() - .getTotalCount(); - assertThat(totalCount, is(0)); + /** + * Test rate limit. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testRateLimit() throws IOException { + assertThat(gitHub.getRateLimit(), notNullValue()); } /** @@ -1465,52 +1365,67 @@ public void testReadme() throws IOException { } /** - * Test trees. + * Test ref. * * @throws IOException * Signals that an I/O exception has occurred. */ - @Ignore("Needs mocking check") @Test - public void testTrees() throws IOException { - GHTree mainTree = gitHub.getRepository("hub4j/github-api").getTree("main"); - boolean foundReadme = false; - for (GHTreeEntry e : mainTree.getTree()) { - if ("readme".equalsIgnoreCase(e.getPath().replaceAll("\\.md", ""))) { - foundReadme = true; - break; - } - } - assertThat(foundReadme, is(true)); + public void testRef() throws IOException { + GHRef mainRef = gitHub.getRepository("jenkinsci/jenkins").getRef("heads/master"); + assertThat(mainRef.getUrl().toString(), + equalTo(mockGitHub.apiServer().baseUrl() + "/repos/jenkinsci/jenkins/git/refs/heads/master")); } /** - * Test trees recursive. + * Test repo CRUD. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void testTreesRecursive() throws IOException { - GHTree mainTree = gitHub.getRepository("hub4j/github-api").getTreeRecursive("main", 1); - boolean foundThisFile = false; - for (GHTreeEntry e : mainTree.getTree()) { - if (e.getPath().endsWith(AppTest.class.getSimpleName() + ".java")) { - foundThisFile = true; - assertThat(e.getPath(), equalTo("src/test/java/org/kohsuke/github/AppTest.java")); - assertThat(e.getSha(), equalTo("baad7a7c4cf409f610a0e8c7eba17664eb655c44")); - assertThat(e.getMode(), equalTo("100755")); - assertThat(e.getSize(), greaterThan(30000L)); - assertThat(e.getUrl().toString(), - containsString("/repos/hub4j/github-api/git/blobs/baad7a7c4cf409f610a0e8c7eba17664eb655c44")); - GHBlob blob = e.asBlob(); - assertThat(e.asBlob().getUrl().toString(), - containsString("/repos/hub4j/github-api/git/blobs/baad7a7c4cf409f610a0e8c7eba17664eb655c44")); - break; - } + public void testRepoCRUD() throws Exception { + String targetName = "github-api-test-rename2"; - } - assertThat(foundThisFile, is(true)); + cleanupUserRepository("github-api-test-rename"); + cleanupUserRepository(targetName); + + GHRepository r = gitHub.createRepository("github-api-test-rename") + .description("a test repository") + .homepage("http://github-api.kohsuke.org/") + .private_(false) + .create(); + + assertThat(r.hasIssues(), is(true)); + assertThat(r.hasWiki(), is(true)); + assertThat(r.hasDownloads(), is(true)); + assertThat(r.hasProjects(), is(true)); + + r.enableIssueTracker(false); + r.enableDownloads(false); + r.enableWiki(false); + r.enableProjects(false); + + r.renameTo(targetName); + + // local instance remains unchanged + assertThat(r.getName(), equalTo("github-api-test-rename")); + assertThat(r.hasIssues(), is(true)); + assertThat(r.hasWiki(), is(true)); + assertThat(r.hasDownloads(), is(true)); + assertThat(r.hasProjects(), is(true)); + + r = gitHub.getMyself().getRepository(targetName); + + // values are updated + assertThat(r.hasIssues(), is(false)); + assertThat(r.hasWiki(), is(false)); + assertThat(r.hasDownloads(), is(false)); + assertThat(r.getName(), equalTo(targetName)); + + assertThat(r.hasProjects(), is(false)); + + r.delete(); } /** @@ -1629,20 +1544,69 @@ public void testRepoLabel() throws IOException { } /** - * Cleanup label. + * Test repo permissions. * - * @param name - * the name + * @throws Exception + * the exception */ - void cleanupLabel(String name) { - if (mockGitHub.isUseProxy()) { - try { - GHLabel t = getNonRecordingGitHub().getRepository("hub4j-test-org/test-labels").getLabel(name); - t.delete(); - } catch (IOException e) { + @Ignore("Needs mocking check") + @Test + public void testRepoPermissions() throws Exception { + kohsuke(); - } + GHRepository r = gitHub.getOrganization(GITHUB_API_TEST_ORG).getRepository("github-api"); + assertThat(r.hasPullAccess(), is(true)); + + r = gitHub.getOrganization("github").getRepository("hub"); + assertThat(r.hasAdminAccess(), is(false)); + } + + /** + * Test repository with auto initialization CRUD. + * + * @throws Exception + * the exception + */ + @Test + public void testRepositoryWithAutoInitializationCRUD() throws Exception { + String name = "github-api-test-autoinit"; + cleanupUserRepository(name); + GHRepository r = gitHub.createRepository(name) + .description("a test repository for auto init") + .homepage("http://github-api.kohsuke.org/") + .autoInit(true) + .create(); + if (mockGitHub.isUseProxy()) { + Thread.sleep(3000); } + assertThat(r.getReadme(), notNullValue()); + + r.delete(); + } + + /** + * Test should fetch team from organization. + * + * @throws Exception + * the exception + */ + @Test + public void testShouldFetchTeamFromOrganization() throws Exception { + GHOrganization organization = gitHub.getOrganization(GITHUB_API_TEST_ORG); + GHTeam teamByName = organization.getTeams().get("Core Developers"); + + GHTeam teamById = organization.getTeam(teamByName.getId()); + assertThat(teamById, notNullValue()); + + assertThat(teamById.getId(), equalTo(teamByName.getId())); + assertThat(teamById.getDescription(), equalTo(teamByName.getDescription())); + + GHTeam teamById2 = organization.getTeam(teamByName.getId()); + assertThat(teamById2, notNullValue()); + + assertThat(teamById2.getId(), equalTo(teamByName.getId())); + assertThat(teamById2.getDescription(), equalTo(teamByName.getDescription())); + } /** @@ -1668,162 +1632,246 @@ public void testSubscribers() throws IOException { } /** - * Notifications. + * Test trees. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Ignore("Needs mocking check") + @Test + public void testTrees() throws IOException { + GHTree mainTree = gitHub.getRepository("hub4j/github-api").getTree("main"); + boolean foundReadme = false; + for (GHTreeEntry e : mainTree.getTree()) { + if ("readme".equalsIgnoreCase(e.getPath().replaceAll("\\.md", ""))) { + foundReadme = true; + break; + } + } + assertThat(foundReadme, is(true)); + } + + /** + * Test trees recursive. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testTreesRecursive() throws IOException { + GHTree mainTree = gitHub.getRepository("hub4j/github-api").getTreeRecursive("main", 1); + boolean foundThisFile = false; + for (GHTreeEntry e : mainTree.getTree()) { + if (e.getPath().endsWith(AppTest.class.getSimpleName() + ".java")) { + foundThisFile = true; + assertThat(e.getPath(), equalTo("src/test/java/org/kohsuke/github/AppTest.java")); + assertThat(e.getSha(), equalTo("baad7a7c4cf409f610a0e8c7eba17664eb655c44")); + assertThat(e.getMode(), equalTo("100755")); + assertThat(e.getSize(), greaterThan(30000L)); + assertThat(e.getUrl().toString(), + containsString("/repos/hub4j/github-api/git/blobs/baad7a7c4cf409f610a0e8c7eba17664eb655c44")); + GHBlob blob = e.asBlob(); + assertThat(e.asBlob().getUrl().toString(), + containsString("/repos/hub4j/github-api/git/blobs/baad7a7c4cf409f610a0e8c7eba17664eb655c44")); + break; + } + + } + assertThat(foundThisFile, is(true)); + } + + /** + * Test user public event api. * * @throws Exception * the exception */ @Test - public void notifications() throws Exception { - boolean found = false; - for (GHThread t : gitHub.listNotifications().nonBlocking(true).read(true)) { - if (!found) { - found = true; - // both read and unread are included - assertThat(t.getTitle(), is("Create a Jenkinsfile for Librecores CI in mor1kx")); - assertThat(t.getLastReadAt(), notNullValue()); - assertThat(t.isRead(), equalTo(true)); + public void testUserPublicEventApi() throws Exception { + for (GHEventInfo ev : gitHub.getUserPublicEvents("PierreBtz")) { + if (ev.getType() == GHEvent.PULL_REQUEST) { + if (ev.getId() == 27449881624L) { + assertThat(ev.getActorLogin(), equalTo("PierreBtz")); + assertThat(ev.getOrganization().getLogin(), equalTo("hub4j")); + assertThat(ev.getRepository().getFullName(), equalTo("hub4j/github-api")); + assertThat(ev.getCreatedAt(), equalTo(GitHubClient.parseInstant("2023-03-02T16:37:49Z"))); + assertThat(ev.getType(), equalTo(GHEvent.PULL_REQUEST)); + } - t.markAsRead(); // test this by calling it once on old notfication + GHEventPayload.PullRequest pr = ev.getPayload(GHEventPayload.PullRequest.class); + assertThat(pr.getNumber(), is(pr.getPullRequest().getNumber())); } - assertThat(t.getReason(), oneOf("subscribed", "mention", "review_requested", "comment")); - assertThat(t.getTitle(), notNullValue()); - assertThat(t.getLastCommentUrl(), notNullValue()); - assertThat(t.getRepository(), notNullValue()); - assertThat(t.getUpdatedAt(), notNullValue()); - assertThat(t.getType(), oneOf("Issue", "PullRequest")); - - // both thread an unread are included - // assertThat(t.getLastReadAt(), notNullValue()); - // assertThat(t.isRead(), equalTo(true)); + if (ev.getType() == GHEvent.PULL_REQUEST_REVIEW) { + if (ev.getId() == 27468578706L) { + GHEventPayload.PullRequestReview prr = ev.getPayload(GHEventPayload.PullRequestReview.class); + assertThat(prr.getReview().getSubmittedAt(), + equalTo(GitHubClient.parseInstant("2023-03-03T10:51:48Z"))); + assertThat(prr.getReview().getCreatedAt(), equalTo(prr.getReview().getSubmittedAt())); + } + } + } + } - // Doesn't exist on threads but is part of GHObject. :( - assertThat(t.getCreatedAt(), nullValue()); + /** + * Test user public organizations when there are none. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testUserPublicOrganizationsWhenThereAreNone() throws IOException { + // bitwiseman had no public org memberships at the time Wiremock recorded the GitHub API responses + GHUser user = new GHUser(); + user.login = "bitwiseman"; - } - assertThat(found, is(true)); - gitHub.listNotifications().markAsRead(); - gitHub.listNotifications().iterator().next(); + Map orgs = gitHub.getUserPublicOrganizations(user); + assertThat(orgs.size(), equalTo(0)); } /** - * Check to string. + * Test user public organizations when there are some. * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ - @Ignore("Needs mocking check") @Test - public void checkToString() throws Exception { - // Just basic code coverage to make sure toString() doesn't blow up - GHUser u = gitHub.getUser("rails"); - // System.out.println(u); - GHRepository r = u.getRepository("rails"); - // System.out.println(r); - // System.out.println(r.getIssue(1)); + public void testUserPublicOrganizationsWhenThereAreSome() throws IOException { + // kohsuke had some public org memberships at the time Wiremock recorded the GitHub API responses + GHUser user = new GHUser(); + user.login = "kohsuke"; + + Map orgs = gitHub.getUserPublicOrganizations(user); + assertThat(orgs.size(), greaterThan(0)); } /** - * Reactions. + * Try hook. * * @throws Exception * the exception */ @Test - public void reactions() throws Exception { - GHIssue i = gitHub.getRepository("hub4j/github-api").getIssue(311); - - List l; - // retrieval - l = i.listReactions().toList(); - assertThat(l.size(), equalTo(1)); + public void tryHook() throws Exception { + final GHOrganization o = gitHub.getOrganization(GITHUB_API_TEST_ORG); + final GHRepository r = o.getRepository("github-api"); + try { + GHHook hook = r.createWebHook(new URL("http://www.google.com/")); + assertThat(hook.getName(), equalTo("web")); + assertThat(hook.getEvents().size(), equalTo(1)); + assertThat(hook.getEvents(), contains(GHEvent.PUSH)); + assertThat(hook.getConfig().size(), equalTo(3)); + assertThat(hook.isActive(), equalTo(true)); - assertThat(l.get(0).getUser().getLogin(), is("kohsuke")); - assertThat(l.get(0).getContent(), is(ReactionContent.HEART)); + GHHook hook2 = r.getHook((int) hook.getId()); + assertThat(hook2.getName(), equalTo("web")); + assertThat(hook2.getEvents().size(), equalTo(1)); + assertThat(hook2.getEvents(), contains(GHEvent.PUSH)); + assertThat(hook2.getConfig().size(), equalTo(3)); + assertThat(hook2.isActive(), equalTo(true)); + hook2.ping(); + hook2.delete(); + final GHHook finalRepoHook = hook; + GHFileNotFoundException e = Assert.assertThrows(GHFileNotFoundException.class, + () -> r.getHook((int) finalRepoHook.getId())); + assertThat(e.getMessage(), + containsString("repos/hub4j-test-org/github-api/hooks/" + finalRepoHook.getId())); + assertThat(e.getMessage(), containsString("rest/reference/repos#get-a-repository-webhook")); - // CRUD - GHReaction a; - a = i.createReaction(ReactionContent.HOORAY); - assertThat(a.getUser().getLogin(), is(gitHub.getMyself().getLogin())); - assertThat(a.getContent(), is(ReactionContent.HOORAY)); - i.deleteReaction(a); + hook = r.createWebHook(new URL("http://www.google.com/")); + r.deleteHook((int) hook.getId()); - l = i.listReactions().toList(); - assertThat(l.size(), equalTo(1)); + hook = o.createWebHook(new URL("http://www.google.com/")); + assertThat(hook.getName(), equalTo("web")); + assertThat(hook.getEvents().size(), equalTo(1)); + assertThat(hook.getEvents(), contains(GHEvent.PUSH)); + assertThat(hook.getConfig().size(), equalTo(3)); + assertThat(hook.isActive(), equalTo(true)); - a = i.createReaction(ReactionContent.PLUS_ONE); - assertThat(a.getUser().getLogin(), is(gitHub.getMyself().getLogin())); - assertThat(a.getContent(), is(ReactionContent.PLUS_ONE)); + hook2 = o.getHook((int) hook.getId()); + assertThat(hook2.getName(), equalTo("web")); + assertThat(hook2.getEvents().size(), equalTo(1)); + assertThat(hook2.getEvents(), contains(GHEvent.PUSH)); + assertThat(hook2.getConfig().size(), equalTo(3)); + assertThat(hook2.isActive(), equalTo(true)); + hook2.ping(); + hook2.delete(); - a = i.createReaction(ReactionContent.CONFUSED); - assertThat(a.getUser().getLogin(), is(gitHub.getMyself().getLogin())); - assertThat(a.getContent(), is(ReactionContent.CONFUSED)); + final GHHook finalOrgHook = hook; + GHFileNotFoundException e2 = Assert.assertThrows(GHFileNotFoundException.class, + () -> o.getHook((int) finalOrgHook.getId())); + assertThat(e2.getMessage(), containsString("orgs/hub4j-test-org/hooks/" + finalOrgHook.getId())); + assertThat(e2.getMessage(), containsString("rest/reference/orgs#get-an-organization-webhook")); - a = i.createReaction(ReactionContent.EYES); - assertThat(a.getUser().getLogin(), is(gitHub.getMyself().getLogin())); - assertThat(a.getContent(), is(ReactionContent.EYES)); + hook = o.createWebHook(new URL("http://www.google.com/")); + o.deleteHook((int) hook.getId()); - a = i.createReaction(ReactionContent.ROCKET); - assertThat(a.getUser().getLogin(), is(gitHub.getMyself().getLogin())); - assertThat(a.getContent(), is(ReactionContent.ROCKET)); + // System.out.println(hook); + } finally { + if (mockGitHub.isUseProxy()) { + GHRepository cleanupRepo = getNonRecordingGitHub().getOrganization(GITHUB_API_TEST_ORG) + .getRepository("github-api"); + for (GHHook h : cleanupRepo.getHooks()) { + h.delete(); + } + } + } + } - l = i.listReactions().toList(); - assertThat(l.size(), equalTo(5)); - assertThat(l.get(0).getUser().getLogin(), is("kohsuke")); - assertThat(l.get(0).getContent(), is(ReactionContent.HEART)); - assertThat(l.get(1).getUser().getLogin(), is(gitHub.getMyself().getLogin())); - assertThat(l.get(1).getContent(), is(ReactionContent.PLUS_ONE)); - assertThat(l.get(2).getUser().getLogin(), is(gitHub.getMyself().getLogin())); - assertThat(l.get(2).getContent(), is(ReactionContent.CONFUSED)); - assertThat(l.get(3).getUser().getLogin(), is(gitHub.getMyself().getLogin())); - assertThat(l.get(3).getContent(), is(ReactionContent.EYES)); - assertThat(l.get(4).getUser().getLogin(), is(gitHub.getMyself().getLogin())); - assertThat(l.get(4).getContent(), is(ReactionContent.ROCKET)); + private void cleanupUserRepository(final String name) throws IOException { + if (mockGitHub.isUseProxy()) { + cleanupRepository(getUser(getNonRecordingGitHub()).getLogin() + "/" + name); + } + } - i.deleteReaction(l.get(1)); - i.deleteReaction(l.get(2)); - i.deleteReaction(l.get(3)); - i.deleteReaction(l.get(4)); + private GHRepository getTestRepository() throws IOException { + return getTempRepository(GITHUB_API_TEST_REPO); + } - l = i.listReactions().toList(); - assertThat(l.size(), equalTo(1)); + private boolean shouldBelongToTeam(String organizationName, String teamName) throws IOException { + GHOrganization org = gitHub.getOrganization(organizationName); + assertThat(org, notNullValue()); + GHTeam team = org.getTeamByName(teamName); + assertThat(team, notNullValue()); + return team.hasMember(gitHub.getMyself()); } - /** - * List org memberships. - * - * @throws Exception - * the exception - */ - @Test - public void listOrgMemberships() throws Exception { - GHMyself me = gitHub.getMyself(); - for (GHMembership m : me.listOrgMemberships()) { - assertThat(m.getUser(), is((GHUser) me)); - assertThat(m.getState(), notNullValue()); - assertThat(m.getRole(), notNullValue()); + private void tryDisablingIssueTrackers(GitHub gitHub) throws IOException { + for (GHRepository r : gitHub.getOrganization("jenkinsci").getRepositories().values()) { + if (r.hasIssues()) { + if (r.getOpenIssueCount() == 0) { + // System.out.println("DISABLED " + r.getName()); + r.enableIssueTracker(false); + } else { + // System.out.println("UNTOUCHED " + r.getName()); + } + } } } - /** - * Blob. - * - * @throws Exception - * the exception - */ - @Test - public void blob() throws Exception { - Assume.assumeFalse(SystemUtils.IS_OS_WINDOWS); + private void tryDisablingWiki(GitHub gitHub) throws IOException { + for (GHRepository r : gitHub.getOrganization("jenkinsci").getRepositories().values()) { + if (r.hasWiki()) { + // System.out.println("DISABLED " + r.getName()); + r.enableWiki(false); + } + } + } - GHRepository r = gitHub.getRepository("hub4j/github-api"); - String sha1 = "a12243f2fc5b8c2ba47dd677d0b0c7583539584d"; + private void tryRenaming(GitHub gitHub) throws IOException { + gitHub.getUser("kohsuke").getRepository("test").renameTo("test2"); + } - verifyBlobContent(r.readBlob(sha1)); + private void tryTeamCreation(GitHub gitHub) throws IOException { + GHOrganization o = gitHub.getOrganization("HudsonLabs"); + GHTeam t = o.createTeam("auto team").permission(Permission.PUSH).create(); + t.add(o.getRepository("auto-test")); + } - GHBlob blob = r.getBlob(sha1); - verifyBlobContent(blob.read()); - assertThat(blob.getSha(), is("a12243f2fc5b8c2ba47dd677d0b0c7583539584d")); - assertThat(blob.getSize(), is(1104L)); + private void tryUpdatingIssueTracker(GitHub gitHub) throws IOException { + GHRepository r = gitHub.getOrganization("jenkinsci").getRepository("lib-task-reactor"); + // System.out.println(r.hasIssues()); + // System.out.println(r.getOpenIssueCount()); + r.enableIssueTracker(false); } private void verifyBlobContent(InputStream is) throws Exception { @@ -1832,4 +1880,21 @@ private void verifyBlobContent(InputStream is) throws Exception { assertThat(content, containsString("FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR")); assertThat(content.length(), is(1104)); } + + /** + * Cleanup label. + * + * @param name + * the name + */ + void cleanupLabel(String name) { + if (mockGitHub.isUseProxy()) { + try { + GHLabel t = getNonRecordingGitHub().getRepository("hub4j-test-org/test-labels").getLabel(name); + t.delete(); + } catch (IOException e) { + + } + } + } } diff --git a/src/test/java/org/kohsuke/github/ArchTests.java b/src/test/java/org/kohsuke/github/ArchTests.java index e21a7ab108..fceae8c316 100644 --- a/src/test/java/org/kohsuke/github/ArchTests.java +++ b/src/test/java/org/kohsuke/github/ArchTests.java @@ -1,13 +1,16 @@ package org.kohsuke.github; import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.base.HasDescription; import com.tngtech.archunit.core.domain.*; import com.tngtech.archunit.core.domain.properties.HasName; import com.tngtech.archunit.core.domain.properties.HasOwner; +import com.tngtech.archunit.core.domain.properties.HasSourceCodeLocation; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.ImportOption; import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.lang.conditions.ArchConditions; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; @@ -15,6 +18,9 @@ import org.apache.commons.lang3.builder.ToStringStyle; import org.junit.BeforeClass; import org.junit.Test; +import org.kohsuke.github.GHDiscussion.Creator; +import org.kohsuke.github.GHPullRequestCommitDetail.Commit; +import org.kohsuke.github.GHPullRequestCommitDetail.CommitPointer; import java.io.Closeable; import java.io.InputStream; @@ -26,15 +32,26 @@ import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkNotNull; +import static com.tngtech.archunit.base.DescribedPredicate.not; +import static com.tngtech.archunit.base.DescribedPredicate.or; import static com.tngtech.archunit.core.domain.JavaCall.Predicates.target; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableTo; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleNameContaining; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.type; +import static com.tngtech.archunit.core.domain.JavaMember.Predicates.declaredIn; +import static com.tngtech.archunit.core.domain.JavaModifier.STATIC; +import static com.tngtech.archunit.core.domain.properties.HasModifiers.Predicates.modifier; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameContaining; import static com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With.owner; import static com.tngtech.archunit.core.domain.properties.HasParameterTypes.Predicates.rawParameterTypes; import static com.tngtech.archunit.lang.conditions.ArchConditions.*; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noFields; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noMethods; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.greaterThan; @@ -42,25 +59,47 @@ /** * The Class ArchTests. */ +@SuppressWarnings({ "LocalVariableNamingConvention", "TestMethodWithoutAssertion", "UnqualifiedStaticUsage", + "unchecked", "MethodMayBeStatic", "FieldNamingConvention", "StaticCollection" }) public class ArchTests { + private static final class EnumConstantFieldPredicate extends DescribedPredicate { + private EnumConstantFieldPredicate() { + super("are not enum constants"); + } + + @Override + public boolean test(JavaField javaField) { + JavaClass owner = javaField.getOwner(); + return owner.isEnum() && javaField.getRawType().isAssignableTo(owner.reflect()); + } + } + + private static class UnlessPredicate extends DescribedPredicate { + private final DescribedPredicate current; + private final DescribedPredicate other; + + UnlessPredicate(DescribedPredicate current, DescribedPredicate other) { + super(current.getDescription() + " unless " + other.getDescription()); + this.current = checkNotNull(current); + this.other = checkNotNull(other); + } + + @Override + public boolean test(T input) { + return current.test(input) && !other.test(input); + } + } + private static final JavaClasses classFiles = new ClassFileImporter() .withImportOption(new ImportOption.DoNotIncludeTests()) .importPackages("org.kohsuke.github"); - private static final JavaClasses apacheCommons = new ClassFileImporter().importPackages("org.apache.commons.lang3"); - private static final JavaClasses testClassFiles = new ClassFileImporter() .withImportOption(new ImportOption.OnlyIncludeTests()) .withImportOption(new ImportOption.DoNotIncludeJars()) .importPackages("org.kohsuke.github"); - /** - * Default constructor. - */ - public ArchTests() { - } - /** * Before class. */ @@ -70,69 +109,26 @@ public static void beforeClass() { } /** - * Test require use of assert that. - */ - @Test - public void testRequireUseOfAssertThat() { - - final String reason = "This project uses `assertThat(...)` or `assertThrows(...)` instead of other `assert*()` methods."; - - final DescribedPredicate assertMethodOtherThanAssertThat = nameContaining("assert") - .and(DescribedPredicate.not(name("assertThat")).and(DescribedPredicate.not(name("assertThrows")))); - - final ArchRule onlyAssertThatRule = classes() - .should(not(callMethodWhere(target(assertMethodOtherThanAssertThat)))) - .because(reason); - - onlyAssertThatRule.check(testClassFiles); - } - - /** - * Test require use of only specific apache commons. + * Have names containing unless. + * + * @param + * the generic type + * @param infix + * the infix + * @param unlessPredicates + * the unless predicates + * @return the arch condition */ - @Test - public void testRequireUseOfOnlySpecificApacheCommons() { + public static ArchCondition haveNamesContainingUnless( + final String infix, + final DescribedPredicate... unlessPredicates) { + DescribedPredicate restrictedNameContaining = nameContaining(infix); - final ArchRule onlyApprovedApacheCommonsMethods = classes() - .should(notCallMethodsInPackageUnless("org.apache.commons..", - // unless it is one of these methods - targetMethodIs(StringUtils.class, "capitalize", String.class), - targetMethodIs(StringUtils.class, "defaultString", String.class, String.class), - targetMethodIs(StringUtils.class, "equals", CharSequence.class, CharSequence.class), - targetMethodIs(StringUtils.class, "isBlank", CharSequence.class), - targetMethodIs(StringUtils.class, "isEmpty", CharSequence.class), - targetMethodIs(StringUtils.class, "join", Iterable.class, String.class), - targetMethodIs(StringUtils.class, - "prependIfMissing", - String.class, - CharSequence.class, - CharSequence[].class), - targetMethodIs(ToStringBuilder.class, "toString"), - targetMethodIs(ToStringBuilder.class, "append", String.class, Object.class), - targetMethodIs(ToStringBuilder.class, "append", String.class, long.class), - targetMethodIs(ToStringBuilder.class, "append", String.class, int.class), - targetMethodIs(ToStringBuilder.class, "append", String.class, boolean.class), - targetMethodIs(ToStringBuilder.class, "isEmpty"), - targetMethodIs(ToStringBuilder.class, "equals"), - targetMethodIs(ToStringBuilder.class, "capitalize"), - targetMethodIs(ToStringStyle.class, - "append", - StringBuffer.class, - String.class, - Object.class, - Boolean.class), - targetMethodIs(ReflectionToStringBuilder.class, "accept", Field.class), - targetMethodIs(IOUtils.class, "closeQuietly", InputStream.class), - targetMethodIs(IOUtils.class, "closeQuietly", Closeable.class), - targetMethodIs(IOUtils.class, "copyLarge", InputStream.class, OutputStream.class), - targetMethodIs(IOUtils.class, "toString", InputStream.class, Charset.class), - targetMethodIs(IOUtils.class, "toString", Reader.class), - targetMethodIs(IOUtils.class, "toByteArray", InputStream.class), - targetMethodIs(IOUtils.class, "write", byte[].class, OutputStream.class))) - .because( - "Commons methods must be manually verified to be compatible with commons-io:2.4 or earlier and commons-lang3:3.9 or earlier."); - - onlyApprovedApacheCommonsMethods.check(classFiles); + if (unlessPredicates.length > 0) { + final DescribedPredicate allowed = or(unlessPredicates); + restrictedNameContaining = unless(nameContaining(infix), allowed); + } + return have(restrictedNameContaining); } /** @@ -156,7 +152,7 @@ public static ArchCondition notCallMethodsInPackageUnless(final Strin } restrictedPackageCalls = unless(restrictedPackageCalls, allowed); } - return not(callMethodWhere(restrictedPackageCalls)); + return ArchConditions.not(callMethodWhere(restrictedPackageCalls)); } /** @@ -200,19 +196,143 @@ public static DescribedPredicate unless(DescribedPredicate fir return new UnlessPredicate(first, second); } - private static class UnlessPredicate extends DescribedPredicate { - private final DescribedPredicate current; - private final DescribedPredicate other; + /** + * Default constructor. + */ + public ArchTests() { + } - UnlessPredicate(DescribedPredicate current, DescribedPredicate other) { - super(current.getDescription() + " unless " + other.getDescription()); - this.current = checkNotNull(current); - this.other = checkNotNull(other); - } + /** + * Test naming conventions + */ + @Test + public void testRequireFollowingNamingConvention() { + final String reason = "This project follows standard java naming conventions and does not allow the use of underscores in names."; - @Override - public boolean test(T input) { - return current.test(input) && !other.test(input); - } + final ArchRule constantFieldsShouldFollowConvention = fields().that() + .areStatic() + .and() + .areFinal() + .should(haveNameMatching("[a-zA-Z$][a-zA-Z0-9$_]*")) + .because(reason); + + final ArchRule enumsShouldFollowConvention = fields().that(enumConstants()) + .and(not(declaredIn(GHCompare.Status.class))) + .should(haveNameMatching("[A-Z][A-Z0-9_]*")) + .because("This project follows standard java naming conventions for enums."); + + var notStaticFinalFields = DescribedPredicate.not(modifier(STATIC).and(modifier(STATIC))); + var notEnumOrStaticFinalFields = DescribedPredicate.and(not(enumConstants()), notStaticFinalFields); + + final ArchRule instanceFieldsShouldNotBePublic = noFields().that(notEnumOrStaticFinalFields) + .should(haveModifier(JavaModifier.PUBLIC)) + .because("This project does not allow public instance fields."); + + final ArchRule instanceFieldsShouldFollowConvention = noFields().that(notEnumOrStaticFinalFields) + .should(have(nameContaining("_"))) + .because("This project follows standard java naming conventions for fields."); + + @SuppressWarnings("AccessStaticViaInstance") + final ArchRule methodsNotFollowingConvention = noMethods().that() + .arePublic() + .should(haveNamesContainingUnless("_", + // currently failing method names + // TODO: 2025-03-28 Fix & remove these + declaredIn(assignableTo(PagedIterable.class)).and(name("_iterator")), + declaredIn(GHCompare.class).and(name("getAdded_by")), + declaredIn(GHDeployKey.class).and(name("getAdded_by")), + declaredIn(GHDeployKey.class).and(name("isRead_only")), + declaredIn(assignableTo(GHRepositoryBuilder.class)).and(name("private_")), + declaredIn(Creator.class).and(name("private_")), + declaredIn(GHGistBuilder.class).and(name("public_")), + declaredIn(Commit.class).and(name("getComment_count")), + declaredIn(CommitPointer.class).and(name("getHtml_url")), + declaredIn(GHRelease.class).and(name("getPublished_at")))) + .because(reason); + + final ArchRule classesNotFollowingConvention = noClasses().should(have(simpleNameContaining("_"))) + .because(reason); + + enumsShouldFollowConvention.check(classFiles); + constantFieldsShouldFollowConvention.check(classFiles); + instanceFieldsShouldNotBePublic.check(classFiles); + instanceFieldsShouldFollowConvention.check(classFiles); + methodsNotFollowingConvention.check(classFiles); + classesNotFollowingConvention.check(classFiles); + } + + /** + * Test require use of assert that. + */ + @Test + public void testRequireUseOfAssertThat() { + + final String reason = "This project uses `assertThat(...)` or `assertThrows(...)` instead of other `assert*()` methods."; + + final DescribedPredicate assertMethodOtherThanAssertThat = nameContaining("assert") + .and(not(name("assertThat")).and(not(name("assertThrows")))); + + final ArchRule onlyAssertThatRule = classes() + .should(ArchConditions.not(callMethodWhere(target(assertMethodOtherThanAssertThat)))) + .because(reason); + + onlyAssertThatRule.check(testClassFiles); + } + + /** + * Test require use of only specific apache commons. + */ + @Test + public void testRequireUseOfOnlySpecificApacheCommons() { + + final ArchRule onlyApprovedApacheCommonsMethods = classes() + .should(notCallMethodsInPackageUnless("org.apache.commons..", + // unless it is one of these methods + targetMethodIs(StringUtils.class, "capitalize", String.class), + targetMethodIs(StringUtils.class, "defaultString", String.class, String.class), + targetMethodIs(StringUtils.class, "equals", CharSequence.class, CharSequence.class), + targetMethodIs(StringUtils.class, "isBlank", CharSequence.class), + targetMethodIs(StringUtils.class, "isEmpty", CharSequence.class), + targetMethodIs(StringUtils.class, "join", Iterable.class, String.class), + targetMethodIs(StringUtils.class, + "prependIfMissing", + String.class, + CharSequence.class, + CharSequence[].class), + targetMethodIs(ToStringBuilder.class, "toString"), + targetMethodIs(ToStringBuilder.class, "append", String.class, Object.class), + targetMethodIs(ToStringBuilder.class, "append", String.class, long.class), + targetMethodIs(ToStringBuilder.class, "append", String.class, int.class), + targetMethodIs(ToStringBuilder.class, "append", String.class, boolean.class), + targetMethodIs(ToStringBuilder.class, "isEmpty"), + targetMethodIs(ToStringBuilder.class, "equals"), + targetMethodIs(ToStringBuilder.class, "capitalize"), + targetMethodIs(ToStringStyle.class, + "append", + StringBuffer.class, + String.class, + Object.class, + Boolean.class), + targetMethodIs(ReflectionToStringBuilder.class, "accept", Field.class), + targetMethodIs(IOUtils.class, "closeQuietly", InputStream.class), + targetMethodIs(IOUtils.class, "closeQuietly", Closeable.class), + targetMethodIs(IOUtils.class, "copyLarge", InputStream.class, OutputStream.class), + targetMethodIs(IOUtils.class, "toString", InputStream.class, Charset.class), + targetMethodIs(IOUtils.class, "toString", Reader.class), + targetMethodIs(IOUtils.class, "toByteArray", InputStream.class), + targetMethodIs(IOUtils.class, "write", byte[].class, OutputStream.class))) + .because( + "Commons methods must be manually verified to be compatible with commons-io:2.4 or earlier and commons-lang3:3.9 or earlier."); + + onlyApprovedApacheCommonsMethods.check(classFiles); + } + + /** + * Enum constants. + * + * @return the described predicate + */ + private DescribedPredicate enumConstants() { + return new EnumConstantFieldPredicate(); } } diff --git a/src/test/java/org/kohsuke/github/BridgeMethodTest.java b/src/test/java/org/kohsuke/github/BridgeMethodTest.java index a3f25adea8..1960b3073c 100644 --- a/src/test/java/org/kohsuke/github/BridgeMethodTest.java +++ b/src/test/java/org/kohsuke/github/BridgeMethodTest.java @@ -4,7 +4,9 @@ import org.junit.Test; import java.lang.reflect.Method; +import java.time.Instant; import java.util.ArrayList; +import java.util.Date; import java.util.List; import javax.annotation.Nonnull; @@ -38,8 +40,63 @@ public void testBridgeMethods() { // verifyBridgeMethods(new GHCommit(), "getAuthor", GHCommit.GHAuthor.class, GitUser.class); // verifyBridgeMethods(new GHCommit(), "getCommitter", GHCommit.GHAuthor.class, GitUser.class); - // verifyBridgeMethods(GitHub.class, "getMyself", GHMyself.class, GHUser.class); + String artifactId = System.getProperty("test.projectArtifactId", "default"); + // Only run these tests when building the "bridged" artifact + org.junit.Assume.assumeThat(artifactId, equalTo("github-api-bridged")); + verifyBridgeMethods(GHAppInstallation.class, "getSuspendedAt", Date.class, Instant.class); + verifyBridgeMethods(GHAppInstallationToken.class, "getExpiresAt", Date.class, Instant.class); + verifyBridgeMethods(GHArtifact.class, "getExpiresAt", Date.class, Instant.class); + verifyBridgeMethods(GHCheckRun.class, "getStartedAt", Date.class, Instant.class); + verifyBridgeMethods(GHCheckRun.class, "getCompletedAt", Date.class, Instant.class); + verifyBridgeMethods(GHCheckSuite.HeadCommit.class, "getTimestamp", Date.class, Instant.class); + verifyBridgeMethods(GHCommit.class, "getAuthoredDate", Date.class, Instant.class); + verifyBridgeMethods(GHCommit.class, "getCommitDate", Date.class, Instant.class); + verifyBridgeMethods(GHDeployKey.class, "getCreatedAt", Date.class, Instant.class); + verifyBridgeMethods(GHDeployKey.class, "getLastUsedAt", Date.class, Instant.class); + verifyBridgeMethods(GHEventInfo.class, "getCreatedAt", Date.class, Instant.class); + verifyBridgeMethods(GHEventPayload.Push.PushCommit.class, "getTimestamp", Date.class, Instant.class); + verifyBridgeMethods(GHEventPayload.Star.class, "getStarredAt", Date.class, Instant.class); + verifyBridgeMethods(GHExternalGroup.class, "getUpdatedAt", Date.class, Instant.class); + verifyBridgeMethods(GHIssue.class, "getClosedAt", Date.class, Instant.class); + verifyBridgeMethods(GHIssueEvent.class, "getCreatedAt", Date.class, Instant.class); + verifyBridgeMethods(GHMarketplacePendingChange.class, "getEffectiveDate", Date.class, Instant.class); + verifyBridgeMethods(GHMarketplacePurchase.class, "getNextBillingDate", Date.class, Instant.class); + verifyBridgeMethods(GHMarketplacePurchase.class, "getFreeTrialEndsOn", Date.class, Instant.class); + verifyBridgeMethods(GHMarketplacePurchase.class, "getUpdatedAt", Date.class, Instant.class); + verifyBridgeMethods(GHMarketplaceUserPurchase.class, "getNextBillingDate", Date.class, Instant.class); + verifyBridgeMethods(GHMarketplaceUserPurchase.class, "getFreeTrialEndsOn", Date.class, Instant.class); + verifyBridgeMethods(GHMarketplaceUserPurchase.class, "getUpdatedAt", Date.class, Instant.class); + verifyBridgeMethods(GHMilestone.class, "getDueOn", Date.class, Instant.class); + verifyBridgeMethods(GHMilestone.class, "getClosedAt", Date.class, Instant.class); + verifyBridgeMethods(GHObject.class, "getCreatedAt", Date.class, Instant.class); + verifyBridgeMethods(GHObject.class, "getUpdatedAt", Date.class, Instant.class); + verifyBridgeMethods(GHPerson.class, "getCreatedAt", Date.class, Instant.class); + verifyBridgeMethods(GHPerson.class, "getUpdatedAt", Date.class, Instant.class); + verifyBridgeMethods(GHProjectsV2Item.class, "getArchivedAt", Date.class, Instant.class); + verifyBridgeMethods(GHProjectsV2ItemChanges.FromToDate.class, "getFrom", Date.class, Instant.class); + verifyBridgeMethods(GHProjectsV2ItemChanges.FromToDate.class, "getTo", Date.class, Instant.class); + verifyBridgeMethods(GHPullRequest.class, "getMergedAt", Date.class, Instant.class); + verifyBridgeMethods(GHPullRequestReview.class, "getSubmittedAt", Date.class, Instant.class); + verifyBridgeMethods(GHPullRequestReview.class, "getCreatedAt", Date.class, Instant.class); + verifyBridgeMethods(GHRepository.class, "getPushedAt", Date.class, Instant.class); + verifyBridgeMethods(GHRepositoryDiscussion.class, "getAnswerChosenAt", Date.class, Instant.class); + verifyBridgeMethods(GHRepositoryDiscussion.class, "getCreatedAt", Date.class, Instant.class); + verifyBridgeMethods(GHRepositoryDiscussion.class, "getUpdatedAt", Date.class, Instant.class); + verifyBridgeMethods(GHRepositoryTraffic.DailyInfo.class, "getTimestamp", Date.class, Instant.class); + verifyBridgeMethods(GHStargazer.class, "getStarredAt", Date.class, Instant.class); + verifyBridgeMethods(GHSubscription.class, "getCreatedAt", Date.class, Instant.class); + verifyBridgeMethods(GHThread.class, "getLastReadAt", Date.class, Instant.class); + verifyBridgeMethods(GHUser.class, "getSuspendedAt", Date.class, Instant.class); + verifyBridgeMethods(GHWorkflowJob.class, "getStartedAt", Date.class, Instant.class); + verifyBridgeMethods(GHWorkflowJob.class, "getCompletedAt", Date.class, Instant.class); + verifyBridgeMethods(GHWorkflowJob.class, "getStartedAt", Date.class, Instant.class); + verifyBridgeMethods(GHWorkflowJob.class, "getCompletedAt", Date.class, Instant.class); + verifyBridgeMethods(GHWorkflowRun.class, "getRunStartedAt", Date.class, Instant.class); + verifyBridgeMethods(GHWorkflowRun.HeadCommit.class, "getTimestamp", Date.class, Instant.class); + verifyBridgeMethods(GitCommit.class, "getAuthoredDate", Date.class, Instant.class); + verifyBridgeMethods(GitCommit.class, "getCommitDate", Date.class, Instant.class); + verifyBridgeMethods(GitUser.class, "getDate", Date.class, Instant.class); } /** diff --git a/src/test/java/org/kohsuke/github/CommitTest.java b/src/test/java/org/kohsuke/github/CommitTest.java index 746fec1e38..cd429fc96c 100644 --- a/src/test/java/org/kohsuke/github/CommitTest.java +++ b/src/test/java/org/kohsuke/github/CommitTest.java @@ -26,15 +26,45 @@ public CommitTest() { } /** - * Last status. + * Commit date not null. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ - @Test // issue 152 - public void lastStatus() throws IOException { - GHTag t = gitHub.getRepository("stapler/stapler").listTags().iterator().next(); - assertThat(t.getCommit().getLastStatus(), notNullValue()); + @Test // issue 883 + public void commitDateNotNull() throws Exception { + GHRepository repo = gitHub.getRepository("hub4j/github-api"); + GHCommit commit = repo.getCommit("865a49d2e86c24c5777985f0f103e975c4b765b9"); + + assertThat(commit.getCommitShortInfo().getAuthoredDate().getEpochSecond(), equalTo(1609207093L)); + assertThat(commit.getCommitShortInfo().getAuthoredDate(), + equalTo(commit.getCommitShortInfo().getAuthor().getDate())); + assertThat(commit.getCommitShortInfo().getCommitDate().getEpochSecond(), equalTo(1609207652L)); + assertThat(commit.getCommitShortInfo().getCommitDate(), + equalTo(commit.getCommitShortInfo().getCommitter().getDate())); + } + + /** + * Commit signature verification. + * + * @throws Exception + * the exception + */ + @Test // issue 737 + public void commitSignatureVerification() throws Exception { + GHRepository repo = gitHub.getRepository("stapler/stapler"); + PagedIterable commits = repo.queryCommits().path("pom.xml").list(); + for (GHCommit commit : Iterables.limit(commits, 10)) { + GHCommit expected = repo.getCommit(commit.getSHA1()); + assertThat(commit.getCommitShortInfo().getVerification().isVerified(), + equalTo(expected.getCommitShortInfo().getVerification().isVerified())); + assertThat(commit.getCommitShortInfo().getVerification().getReason(), + equalTo(expected.getCommitShortInfo().getVerification().getReason())); + assertThat(commit.getCommitShortInfo().getVerification().getSignature(), + equalTo(expected.getCommitShortInfo().getVerification().getSignature())); + assertThat(commit.getCommitShortInfo().getVerification().getPayload(), + equalTo(expected.getCommitShortInfo().getVerification().getPayload())); + } } /** @@ -54,17 +84,84 @@ public void getFiles() throws Exception { } /** - * Test list files where there are less than 300 files in a commit. + * Tests the commit message. * * @throws Exception * the exception */ - @Test // issue 1669 - public void listFilesWhereCommitHasSmallChange() throws Exception { + @Test + public void getMessage() throws Exception { GHRepository repo = getRepository(); GHCommit commit = repo.getCommit("dabf0e89fe7107d6e294a924561533ecf80f2384"); - assertThat(commit.listFiles().toList().size(), equalTo(28)); + assertThat(commit.getCommitShortInfo().getMessage(), notNullValue()); + assertThat(commit.getCommitShortInfo().getMessage(), equalTo("A commit with a few files")); + } + + /** + * Last status. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test // issue 152 + public void lastStatus() throws IOException { + GHTag t = gitHub.getRepository("stapler/stapler").listTags().iterator().next(); + assertThat(t.getCommit().getLastStatus(), notNullValue()); + } + + /** + * List branches where head. + * + * @throws Exception + * the exception + */ + @Test + public void listBranchesWhereHead() throws Exception { + GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("listPrsListHeads"); + + GHCommit commit = repo.getCommit("ab92e13c0fc844fd51a379a48a3ad0b18231215c"); + + assertThat("Commit which was supposed to be HEAD in the \"main\" branch was not found.", + commit.listBranchesWhereHead() + .toList() + .stream() + .findFirst() + .filter(it -> it.getName().equals("main")) + .isPresent()); + } + + /** + * List branches where head 2 heads. + * + * @throws Exception + * the exception + */ + @Test + public void listBranchesWhereHead2Heads() throws Exception { + GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("listPrsListHeads"); + + GHCommit commit = repo.getCommit("ab92e13c0fc844fd51a379a48a3ad0b18231215c"); + + assertThat("Commit which was supposed to be HEAD in 2 branches was not found as such.", + commit.listBranchesWhereHead().toList().size(), + equalTo(2)); + } + + /** + * List branches where head of commit with head nowhere. + * + * @throws Exception + * the exception + */ + @Test + public void listBranchesWhereHeadOfCommitWithHeadNowhere() throws Exception { + GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("listPrsListHeads"); + + GHCommit commit = repo.getCommit("7460916bfb8e9966d6b9d3e8ae378c82c6b8e43e"); + + assertThat("Commit which was not supposed to be HEAD in any branch was found as HEAD.", + commit.listBranchesWhereHead().toList().isEmpty()); } /** @@ -82,18 +179,76 @@ public void listFilesWhereCommitHasLargeChange() throws Exception { } /** - * Tests the commit message. + * Test list files where there are less than 300 files in a commit. * * @throws Exception * the exception */ - @Test - public void getMessage() throws Exception { + @Test // issue 1669 + public void listFilesWhereCommitHasSmallChange() throws Exception { GHRepository repo = getRepository(); GHCommit commit = repo.getCommit("dabf0e89fe7107d6e294a924561533ecf80f2384"); - assertThat(commit.getCommitShortInfo().getMessage(), notNullValue()); - assertThat(commit.getCommitShortInfo().getMessage(), equalTo("A commit with a few files")); + assertThat(commit.listFiles().toList().size(), equalTo(28)); + } + + /** + * List pull requests. + * + * @throws Exception + * the exception + */ + @Test + public void listPullRequests() throws Exception { + GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("listPrsListHeads"); + Integer prNumber = 2; + + GHCommit commit = repo.getCommit("6b9956fe8c3d030dbc49c9d4c4166b0ceb4198fc"); + + List listedPrs = commit.listPullRequests().toList(); + + assertThat(1, equalTo(listedPrs.size())); + + assertThat("Pull request " + prNumber + " not found by searching from commit.", + listedPrs.stream().findFirst().filter(it -> it.getNumber() == prNumber).isPresent()); + } + + /** + * List pull requests of commit with 2 pull requests. + * + * @throws Exception + * the exception + */ + @Test + public void listPullRequestsOfCommitWith2PullRequests() throws Exception { + GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("listPrsListHeads"); + Integer[] expectedPrs = new Integer[]{ 1, 2 }; + + GHCommit commit = repo.getCommit("442aa213f924a5984856f16e52a18153aaf41ad3"); + + List listedPrs = commit.listPullRequests().toList(); + + assertThat(2, equalTo(listedPrs.size())); + + listedPrs.stream() + .forEach(pr -> assertThat("PR#" + pr.getNumber() + " not expected to be matched.", + Arrays.stream(expectedPrs).anyMatch(prNumber -> prNumber.equals(pr.getNumber())))); + } + + /** + * List pull requests of not included commit. + * + * @throws Exception + * the exception + */ + @Test + public void listPullRequestsOfNotIncludedCommit() throws Exception { + GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("listPrsListHeads"); + + GHCommit commit = repo.getCommit("f66f7ca691ace6f4a9230292efb932b49214d72c"); + + assertThat("The commit is supposed to be not part of any pull request", + commit.listPullRequests().toList().isEmpty()); } /** @@ -183,159 +338,8 @@ public void testQueryCommits() throws Exception { } - /** - * List pull requests of not included commit. - * - * @throws Exception - * the exception - */ - @Test - public void listPullRequestsOfNotIncludedCommit() throws Exception { - GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("listPrsListHeads"); - - GHCommit commit = repo.getCommit("f66f7ca691ace6f4a9230292efb932b49214d72c"); - - assertThat("The commit is supposed to be not part of any pull request", - commit.listPullRequests().toList().isEmpty()); - } - - /** - * List pull requests. - * - * @throws Exception - * the exception - */ - @Test - public void listPullRequests() throws Exception { - GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("listPrsListHeads"); - Integer prNumber = 2; - - GHCommit commit = repo.getCommit("6b9956fe8c3d030dbc49c9d4c4166b0ceb4198fc"); - - List listedPrs = commit.listPullRequests().toList(); - - assertThat(1, equalTo(listedPrs.size())); - - assertThat("Pull request " + prNumber + " not found by searching from commit.", - listedPrs.stream().findFirst().filter(it -> it.getNumber() == prNumber).isPresent()); - } - - /** - * List pull requests of commit with 2 pull requests. - * - * @throws Exception - * the exception - */ - @Test - public void listPullRequestsOfCommitWith2PullRequests() throws Exception { - GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("listPrsListHeads"); - Integer[] expectedPrs = new Integer[]{ 1, 2 }; - - GHCommit commit = repo.getCommit("442aa213f924a5984856f16e52a18153aaf41ad3"); - - List listedPrs = commit.listPullRequests().toList(); - - assertThat(2, equalTo(listedPrs.size())); - - listedPrs.stream() - .forEach(pr -> assertThat("PR#" + pr.getNumber() + " not expected to be matched.", - Arrays.stream(expectedPrs).anyMatch(prNumber -> prNumber.equals(pr.getNumber())))); - } - - /** - * List branches where head. - * - * @throws Exception - * the exception - */ - @Test - public void listBranchesWhereHead() throws Exception { - GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("listPrsListHeads"); - - GHCommit commit = repo.getCommit("ab92e13c0fc844fd51a379a48a3ad0b18231215c"); - - assertThat("Commit which was supposed to be HEAD in the \"main\" branch was not found.", - commit.listBranchesWhereHead() - .toList() - .stream() - .findFirst() - .filter(it -> it.getName().equals("main")) - .isPresent()); - } - - /** - * List branches where head 2 heads. - * - * @throws Exception - * the exception - */ - @Test - public void listBranchesWhereHead2Heads() throws Exception { - GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("listPrsListHeads"); - - GHCommit commit = repo.getCommit("ab92e13c0fc844fd51a379a48a3ad0b18231215c"); - - assertThat("Commit which was supposed to be HEAD in 2 branches was not found as such.", - commit.listBranchesWhereHead().toList().size(), - equalTo(2)); - } - - /** - * List branches where head of commit with head nowhere. - * - * @throws Exception - * the exception - */ - @Test - public void listBranchesWhereHeadOfCommitWithHeadNowhere() throws Exception { - GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("listPrsListHeads"); - - GHCommit commit = repo.getCommit("7460916bfb8e9966d6b9d3e8ae378c82c6b8e43e"); - - assertThat("Commit which was not supposed to be HEAD in any branch was found as HEAD.", - commit.listBranchesWhereHead().toList().isEmpty()); - } - - /** - * Commit signature verification. - * - * @throws Exception - * the exception - */ - @Test // issue 737 - public void commitSignatureVerification() throws Exception { - GHRepository repo = gitHub.getRepository("stapler/stapler"); - PagedIterable commits = repo.queryCommits().path("pom.xml").list(); - for (GHCommit commit : Iterables.limit(commits, 10)) { - GHCommit expected = repo.getCommit(commit.getSHA1()); - assertThat(commit.getCommitShortInfo().getVerification().isVerified(), - equalTo(expected.getCommitShortInfo().getVerification().isVerified())); - assertThat(commit.getCommitShortInfo().getVerification().getReason(), - equalTo(expected.getCommitShortInfo().getVerification().getReason())); - assertThat(commit.getCommitShortInfo().getVerification().getSignature(), - equalTo(expected.getCommitShortInfo().getVerification().getSignature())); - assertThat(commit.getCommitShortInfo().getVerification().getPayload(), - equalTo(expected.getCommitShortInfo().getVerification().getPayload())); - } - } - - /** - * Commit date not null. - * - * @throws Exception - * the exception - */ - @Test // issue 883 - public void commitDateNotNull() throws Exception { - GHRepository repo = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = repo.getCommit("865a49d2e86c24c5777985f0f103e975c4b765b9"); - - assertThat(commit.getCommitShortInfo().getAuthoredDate().toInstant().getEpochSecond(), equalTo(1609207093L)); - assertThat(commit.getCommitShortInfo().getAuthoredDate(), - equalTo(commit.getCommitShortInfo().getAuthor().getDate())); - assertThat(commit.getCommitShortInfo().getCommitDate().toInstant().getEpochSecond(), equalTo(1609207652L)); - assertThat(commit.getCommitShortInfo().getCommitDate(), - equalTo(commit.getCommitShortInfo().getCommitter().getDate())); + private GHRepository getRepository(GitHub gitHub) throws IOException { + return gitHub.getOrganization("hub4j-test-org").getRepository("CommitTest"); } /** @@ -348,8 +352,4 @@ public void commitDateNotNull() throws Exception { protected GHRepository getRepository() throws IOException { return getRepository(gitHub); } - - private GHRepository getRepository(GitHub gitHub) throws IOException { - return gitHub.getOrganization("hub4j-test-org").getRepository("CommitTest"); - } } diff --git a/src/test/java/org/kohsuke/github/EnterpriseManagedSupportTest.java b/src/test/java/org/kohsuke/github/EnterpriseManagedSupportTest.java index ad2c4c63fe..c8d7779c5b 100644 --- a/src/test/java/org/kohsuke/github/EnterpriseManagedSupportTest.java +++ b/src/test/java/org/kohsuke/github/EnterpriseManagedSupportTest.java @@ -14,73 +14,112 @@ */ public class EnterpriseManagedSupportTest extends AbstractGitHubWireMockTest { - /** - * Create default EnterpriseManagedSupportTest instance - */ - public EnterpriseManagedSupportTest() { - } - private static final String NOT_PART_OF_EXTERNALLY_MANAGED_ENTERPRISE_ERROR = "{\"message\":\"This organization is not part of externally managed enterprise.\"," + "\"documentation_url\": \"https://docs.github.com/rest/teams/external-groups#list-external-groups-in-an-organization\"}"; + private static final String TEAM_CANNOT_BE_EXTERNALLY_MANAGED_ERROR = "{\"message\":\"This team cannot be externally managed since it has explicit members.\"," + + "\"documentation_url\": \"https://docs.github.com/rest/teams/external-groups#list-a-connection-between-an-external-group-and-a-team\"}"; + private static final String UNKNOWN_ERROR = "{\"message\":\"Unknown error\"," + "\"documentation_url\": \"https://docs.github.com/rest/unknown#unknown\"}"; - private static final String TEAM_CANNOT_BE_EXTERNALLY_MANAGED_ERROR = "{\"message\":\"This team cannot be externally managed since it has explicit members.\"," - + "\"documentation_url\": \"https://docs.github.com/rest/teams/external-groups#list-a-connection-between-an-external-group-and-a-team\"}"; + /** + * Create default EnterpriseManagedSupportTest instance + */ + public EnterpriseManagedSupportTest() { + } /** - * Test to ensure that only HttpExceptions are handled + * Test to validate compliant use case. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testIgnoreNonHttpException() throws IOException { + public void testHandleEmbeddedNotPartOfExternallyManagedEnterpriseHttpException() throws IOException { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - final GHException inputCause = new GHException("Cause"); + final HttpException inputCause = new HttpException(NOT_PART_OF_EXTERNALLY_MANAGED_ENTERPRISE_ERROR, + 400, + "Error", + org.getUrl().toString()); final GHException inputException = new GHException("Test", inputCause); final Optional maybeException = EnterpriseManagedSupport.forOrganization(org) .filterException(inputException); - assertThat(maybeException.isPresent(), is(false)); + assertThat(maybeException.isPresent(), is(true)); + + final GHException exception = maybeException.get(); + + assertThat(exception.getMessage(), + equalTo(EnterpriseManagedSupport.COULD_NOT_RETRIEVE_ORGANIZATION_EXTERNAL_GROUPS)); + + final Throwable cause = exception.getCause(); + + assertThat(cause, instanceOf(GHNotExternallyManagedEnterpriseException.class)); + + final GHNotExternallyManagedEnterpriseException failure = (GHNotExternallyManagedEnterpriseException) cause; + + assertThat(failure.getCause(), is(inputCause)); + assertThat(failure.getMessage(), + equalTo(EnterpriseManagedSupport.COULD_NOT_RETRIEVE_ORGANIZATION_EXTERNAL_GROUPS)); + + final GHError error = failure.getError(); + + assertThat(error, notNullValue()); + assertThat(error.getMessage(), + equalTo(EnterpriseManagedSupport.NOT_PART_OF_EXTERNALLY_MANAGED_ENTERPRISE_ERROR)); + assertThat(error.getDocumentationUrl(), notNullValue()); } /** - * Test to ensure that only BadRequests HttpExceptions are handled + * Test to validate another compliant use case. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testIgnoreNonBadRequestExceptions() throws IOException { + public void testHandleTeamCannotBeExternallyManagedHttpException() throws IOException { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - final HttpException inputCause = new HttpException(NOT_PART_OF_EXTERNALLY_MANAGED_ENTERPRISE_ERROR, - 404, + final HttpException inputException = new HttpException(TEAM_CANNOT_BE_EXTERNALLY_MANAGED_ERROR, + 400, "Error", org.getUrl().toString()); - final GHException inputException = new GHException("Test", inputCause); - final Optional maybeException = EnterpriseManagedSupport.forOrganization(org) - .filterException(inputException); + final Optional maybeException = EnterpriseManagedSupport.forOrganization(org) + .filterException(inputException, "Scenario"); - assertThat(maybeException.isPresent(), is(false)); + assertThat(maybeException.isPresent(), is(true)); + + final GHIOException exception = maybeException.get(); + + assertThat(exception.getMessage(), equalTo("Scenario")); + assertThat(exception.getCause(), is(inputException)); + + assertThat(exception, instanceOf(GHTeamCannotBeExternallyManagedException.class)); + + final GHTeamCannotBeExternallyManagedException failure = (GHTeamCannotBeExternallyManagedException) exception; + + final GHError error = failure.getError(); + + assertThat(error, notNullValue()); + assertThat(error.getMessage(), equalTo(EnterpriseManagedSupport.TEAM_CANNOT_BE_EXTERNALLY_MANAGED_ERROR)); + assertThat(error.getDocumentationUrl(), notNullValue()); } /** - * Test to ensure that only BadRequests HttpExceptions with parseable JSON payload are handled + * Test to ensure that only BadRequests HttpExceptions with known error message are handled * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testIgnoreBadRequestsWithUnparseableJson() throws IOException { + public void testIgnoreBadRequestsWithUnknownErrorMessage() throws IOException { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - final HttpException inputCause = new HttpException("Error", 400, "Error", org.getUrl().toString()); + final HttpException inputCause = new HttpException(UNKNOWN_ERROR, 400, "Error", org.getUrl().toString()); final GHException inputException = new GHException("Test", inputCause); final Optional maybeException = EnterpriseManagedSupport.forOrganization(org) @@ -90,16 +129,16 @@ public void testIgnoreBadRequestsWithUnparseableJson() throws IOException { } /** - * Test to ensure that only BadRequests HttpExceptions with known error message are handled + * Test to ensure that only BadRequests HttpExceptions with parseable JSON payload are handled * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testIgnoreBadRequestsWithUnknownErrorMessage() throws IOException { + public void testIgnoreBadRequestsWithUnparseableJson() throws IOException { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - final HttpException inputCause = new HttpException(UNKNOWN_ERROR, 400, "Error", org.getUrl().toString()); + final HttpException inputCause = new HttpException("Error", 400, "Error", org.getUrl().toString()); final GHException inputException = new GHException("Test", inputCause); final Optional maybeException = EnterpriseManagedSupport.forOrganization(org) @@ -109,17 +148,17 @@ public void testIgnoreBadRequestsWithUnknownErrorMessage() throws IOException { } /** - * Test to validate compliant use case. + * Test to ensure that only BadRequests HttpExceptions are handled * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testHandleEmbeddedNotPartOfExternallyManagedEnterpriseHttpException() throws IOException { + public void testIgnoreNonBadRequestExceptions() throws IOException { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); final HttpException inputCause = new HttpException(NOT_PART_OF_EXTERNALLY_MANAGED_ENTERPRISE_ERROR, - 400, + 404, "Error", org.getUrl().toString()); final GHException inputException = new GHException("Test", inputCause); @@ -127,64 +166,25 @@ public void testHandleEmbeddedNotPartOfExternallyManagedEnterpriseHttpException( final Optional maybeException = EnterpriseManagedSupport.forOrganization(org) .filterException(inputException); - assertThat(maybeException.isPresent(), is(true)); - - final GHException exception = maybeException.get(); - - assertThat(exception.getMessage(), - equalTo(EnterpriseManagedSupport.COULD_NOT_RETRIEVE_ORGANIZATION_EXTERNAL_GROUPS)); - - final Throwable cause = exception.getCause(); - - assertThat(cause, instanceOf(GHNotExternallyManagedEnterpriseException.class)); - - final GHNotExternallyManagedEnterpriseException failure = (GHNotExternallyManagedEnterpriseException) cause; - - assertThat(failure.getCause(), is(inputCause)); - assertThat(failure.getMessage(), - equalTo(EnterpriseManagedSupport.COULD_NOT_RETRIEVE_ORGANIZATION_EXTERNAL_GROUPS)); - - final GHError error = failure.getError(); - - assertThat(error, notNullValue()); - assertThat(error.getMessage(), - equalTo(EnterpriseManagedSupport.NOT_PART_OF_EXTERNALLY_MANAGED_ENTERPRISE_ERROR)); - assertThat(error.getDocumentationUrl(), notNullValue()); + assertThat(maybeException.isPresent(), is(false)); } /** - * Test to validate another compliant use case. + * Test to ensure that only HttpExceptions are handled * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testHandleTeamCannotBeExternallyManagedHttpException() throws IOException { + public void testIgnoreNonHttpException() throws IOException { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - final HttpException inputException = new HttpException(TEAM_CANNOT_BE_EXTERNALLY_MANAGED_ERROR, - 400, - "Error", - org.getUrl().toString()); - - final Optional maybeException = EnterpriseManagedSupport.forOrganization(org) - .filterException(inputException, "Scenario"); - - assertThat(maybeException.isPresent(), is(true)); - - final GHIOException exception = maybeException.get(); - - assertThat(exception.getMessage(), equalTo("Scenario")); - assertThat(exception.getCause(), is(inputException)); - - assertThat(exception, instanceOf(GHTeamCannotBeExternallyManagedException.class)); - - final GHTeamCannotBeExternallyManagedException failure = (GHTeamCannotBeExternallyManagedException) exception; + final GHException inputCause = new GHException("Cause"); + final GHException inputException = new GHException("Test", inputCause); - final GHError error = failure.getError(); + final Optional maybeException = EnterpriseManagedSupport.forOrganization(org) + .filterException(inputException); - assertThat(error, notNullValue()); - assertThat(error.getMessage(), equalTo(EnterpriseManagedSupport.TEAM_CANNOT_BE_EXTERNALLY_MANAGED_ERROR)); - assertThat(error.getDocumentationUrl(), notNullValue()); + assertThat(maybeException.isPresent(), is(false)); } } diff --git a/src/test/java/org/kohsuke/github/EnumTest.java b/src/test/java/org/kohsuke/github/EnumTest.java index 86c4066236..7805ad2575 100644 --- a/src/test/java/org/kohsuke/github/EnumTest.java +++ b/src/test/java/org/kohsuke/github/EnumTest.java @@ -27,7 +27,7 @@ public void touchEnums() { assertThat(GHCheckRun.Conclusion.values().length, equalTo(9)); assertThat(GHCheckRun.Status.values().length, equalTo(4)); - assertThat(GHCommentAuthorAssociation.values().length, equalTo(8)); + assertThat(GHCommentAuthorAssociation.values().length, equalTo(9)); assertThat(GHCommitSearchBuilder.Sort.values().length, equalTo(2)); @@ -41,7 +41,7 @@ public void touchEnums() { assertThat(GHDirection.values().length, equalTo(2)); - assertThat(GHEvent.values().length, equalTo(65)); + assertThat(GHEvent.values().length, equalTo(66)); assertThat(GHEvent.ALL.symbol(), equalTo("*")); assertThat(GHEvent.PULL_REQUEST.symbol(), equalTo(GHEvent.PULL_REQUEST.toString().toLowerCase())); @@ -109,7 +109,7 @@ public void touchEnums() { assertThat(GHRepositorySelection.values().length, equalTo(2)); - assertThat(GHTargetType.values().length, equalTo(2)); + assertThat(GHTargetType.values().length, equalTo(3)); assertThat(GHTeam.Role.values().length, equalTo(2)); assertThat(GHTeam.Privacy.values().length, equalTo(3)); diff --git a/src/test/java/org/kohsuke/github/ExternalGroupsTestingSupport.java b/src/test/java/org/kohsuke/github/ExternalGroupsTestingSupport.java index 73cb162ec5..4c0d34cfc3 100644 --- a/src/test/java/org/kohsuke/github/ExternalGroupsTestingSupport.java +++ b/src/test/java/org/kohsuke/github/ExternalGroupsTestingSupport.java @@ -14,51 +14,12 @@ */ class ExternalGroupsTestingSupport { - static GHExternalGroup findExternalGroup(List groups, Predicate predicate) { - return groups.stream().filter(predicate).findFirst().orElseThrow(AssertionError::new); - } - - static Predicate hasName(String anObject) { - return g -> g.getName().equals(anObject); - } - - static List groupSummary(List groups) { - return collect(groups, ExternalGroupsTestingSupport::describeGroup); - } - - static List teamSummary(GHExternalGroup sut) { - return collect(sut.getTeams(), ExternalGroupsTestingSupport::describeTeam); - } - - static List membersSummary(GHExternalGroup sut) { - return collect(sut.getMembers(), ExternalGroupsTestingSupport::describeMember); - } - - private static List collect(List collection, Function transformation) { - return collection.stream().map(transformation).collect(Collectors.toList()); - } - - private static String describeGroup(GHExternalGroup g) { - return String.format("%d:%s", g.getId(), g.getName()); - } - - private static String describeTeam(GHExternalGroup.GHLinkedTeam t) { - return String.format("%d:%s", t.getId(), t.getName()); - } - - private static String describeMember(GHExternalGroup.GHLinkedExternalMember m) { - return String.format("%d:%s:%s:%s", m.getId(), m.getLogin(), m.getName(), m.getEmail()); - } - - static class Matchers { - - static Matcher isExternalGroupSummary() { - return new IsExternalGroupSummary(); + private static class IsExternalGroupSummary extends TypeSafeDiagnosingMatcher { + @Override + public void describeTo(Description description) { + description.appendText("is a summary"); } - } - - private static class IsExternalGroupSummary extends TypeSafeDiagnosingMatcher { @Override protected boolean matchesSafely(GHExternalGroup group, Description mismatchDescription) { boolean result = true; @@ -90,10 +51,49 @@ protected boolean matchesSafely(GHExternalGroup group, Description mismatchDescr } return result; } + } - @Override - public void describeTo(Description description) { - description.appendText("is a summary"); + static class Matchers { + + static Matcher isExternalGroupSummary() { + return new IsExternalGroupSummary(); } + + } + + private static List collect(List collection, Function transformation) { + return collection.stream().map(transformation).collect(Collectors.toList()); + } + + private static String describeGroup(GHExternalGroup g) { + return String.format("%d:%s", g.getId(), g.getName()); + } + + private static String describeMember(GHExternalGroup.GHLinkedExternalMember m) { + return String.format("%d:%s:%s:%s", m.getId(), m.getLogin(), m.getName(), m.getEmail()); + } + + private static String describeTeam(GHExternalGroup.GHLinkedTeam t) { + return String.format("%d:%s", t.getId(), t.getName()); + } + + static GHExternalGroup findExternalGroup(List groups, Predicate predicate) { + return groups.stream().filter(predicate).findFirst().orElseThrow(AssertionError::new); + } + + static List groupSummary(List groups) { + return collect(groups, ExternalGroupsTestingSupport::describeGroup); + } + + static Predicate hasName(String anObject) { + return g -> g.getName().equals(anObject); + } + + static List membersSummary(GHExternalGroup sut) { + return collect(sut.getMembers(), ExternalGroupsTestingSupport::describeMember); + } + + static List teamSummary(GHExternalGroup sut) { + return collect(sut.getTeams(), ExternalGroupsTestingSupport::describeTeam); } } diff --git a/src/test/java/org/kohsuke/github/GHAppExtendedTest.java b/src/test/java/org/kohsuke/github/GHAppExtendedTest.java index 84e3566a55..3ef03419cc 100644 --- a/src/test/java/org/kohsuke/github/GHAppExtendedTest.java +++ b/src/test/java/org/kohsuke/github/GHAppExtendedTest.java @@ -14,30 +14,12 @@ */ public class GHAppExtendedTest extends AbstractGitHubWireMockTest { - /** - * Create default GHAppExtendedTest instance - */ - public GHAppExtendedTest() { - } - private static final String APP_SLUG = "ghapi-test-app-4"; /** - * Gets the GitHub App by its slug. - * - * @throws IOException - * An IOException has occurred. + * Create default GHAppExtendedTest instance */ - @Test - public void getAppBySlugTest() throws IOException { - GHApp app = gitHub.getApp(APP_SLUG); - - assertThat(app.getId(), is((long) 330762)); - assertThat(app.getSlug(), equalTo(APP_SLUG)); - assertThat(app.getName(), equalTo("GHApi Test app 4")); - assertThat(app.getExternalUrl(), equalTo("https://github.com/organizations/hub4j-test-org")); - assertThat(app.getHtmlUrl().toString(), equalTo("https://github.com/apps/ghapi-test-app-4")); - assertThat(app.getDescription(), equalTo("An app to test the GitHub getApp(slug) method.")); + public GHAppExtendedTest() { } /** @@ -62,4 +44,22 @@ public void createAppByManifestFlowTest() throws IOException { } + /** + * Gets the GitHub App by its slug. + * + * @throws IOException + * An IOException has occurred. + */ + @Test + public void getAppBySlugTest() throws IOException { + GHApp app = gitHub.getApp(APP_SLUG); + + assertThat(app.getId(), is((long) 330762)); + assertThat(app.getSlug(), equalTo(APP_SLUG)); + assertThat(app.getName(), equalTo("GHApi Test app 4")); + assertThat(app.getExternalUrl(), equalTo("https://github.com/organizations/hub4j-test-org")); + assertThat(app.getHtmlUrl().toString(), equalTo("https://github.com/apps/ghapi-test-app-4")); + assertThat(app.getDescription(), equalTo("An app to test the GitHub getApp(slug) method.")); + } + } diff --git a/src/test/java/org/kohsuke/github/GHAppInstallationTest.java b/src/test/java/org/kohsuke/github/GHAppInstallationTest.java index 46c51b7b64..04f4f2529f 100644 --- a/src/test/java/org/kohsuke/github/GHAppInstallationTest.java +++ b/src/test/java/org/kohsuke/github/GHAppInstallationTest.java @@ -3,10 +3,10 @@ import org.junit.Test; import java.io.IOException; +import java.time.Instant; import java.time.LocalDateTime; import java.time.Month; import java.time.ZoneOffset; -import java.util.Date; import java.util.List; import static org.hamcrest.Matchers.*; @@ -25,20 +25,20 @@ public GHAppInstallationTest() { } /** - * Test list repositories two repos. + * Test list repositories no permissions. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testListRepositoriesTwoRepos() throws IOException { - GHAppInstallation appInstallation = getAppInstallationWithToken(jwtProvider1.getEncodedAuthorization()); + public void testGetMarketplaceAccount() throws IOException { + GHAppInstallation appInstallation = getAppInstallationWithToken(jwtProvider3.getEncodedAuthorization()); - List repositories = appInstallation.listRepositories().toList(); + GHMarketplaceAccountPlan marketplaceAccount = appInstallation.getMarketplaceAccount(); + GHMarketplacePlanTest.testMarketplaceAccount(marketplaceAccount); - assertThat(repositories.size(), equalTo(2)); - assertThat(repositories.stream().map(GHRepository::getName).toArray(), - arrayContainingInAnyOrder("empty", "test-readme")); + GHMarketplaceAccountPlan plan = marketplaceAccount.getPlan(); + assertThat(plan.getType(), equalTo(GHMarketplaceAccountType.ORGANIZATION)); } /** @@ -56,20 +56,20 @@ public void testListRepositoriesNoPermissions() throws IOException { } /** - * Test list repositories no permissions. + * Test list repositories two repos. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testGetMarketplaceAccount() throws IOException { - GHAppInstallation appInstallation = getAppInstallationWithToken(jwtProvider3.getEncodedAuthorization()); + public void testListRepositoriesTwoRepos() throws IOException { + GHAppInstallation appInstallation = getAppInstallationWithToken(jwtProvider1.getEncodedAuthorization()); - GHMarketplaceAccountPlan marketplaceAccount = appInstallation.getMarketplaceAccount(); - GHMarketplacePlanTest.testMarketplaceAccount(marketplaceAccount); + List repositories = appInstallation.listRepositories().toList(); - GHMarketplaceAccountPlan plan = marketplaceAccount.getPlan(); - assertThat(plan.getType(), equalTo(GHMarketplaceAccountType.ORGANIZATION)); + assertThat(repositories.size(), equalTo(2)); + assertThat(repositories.stream().map(GHRepository::getName).toArray(), + arrayContainingInAnyOrder("empty", "test-readme")); } /** @@ -85,9 +85,9 @@ public void testListSuspendedInstallation() throws IOException { final GHUser suspendedBy = appInstallation.getSuspendedBy(); assertThat(suspendedBy.getLogin(), equalTo("gilday")); - final Date suspendedAt = appInstallation.getSuspendedAt(); - final Date expectedSuspendedAt = Date - .from(LocalDateTime.of(2024, Month.FEBRUARY, 26, 2, 43, 12).toInstant(ZoneOffset.UTC)); + final Instant suspendedAt = appInstallation.getSuspendedAt(); + final Instant expectedSuspendedAt = LocalDateTime.of(2024, Month.FEBRUARY, 26, 2, 43, 12) + .toInstant(ZoneOffset.UTC); assertThat(suspendedAt, equalTo(expectedSuspendedAt)); } diff --git a/src/test/java/org/kohsuke/github/GHAppTest.java b/src/test/java/org/kohsuke/github/GHAppTest.java index b0369c4b99..420bc16466 100644 --- a/src/test/java/org/kohsuke/github/GHAppTest.java +++ b/src/test/java/org/kohsuke/github/GHAppTest.java @@ -5,6 +5,10 @@ import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.Collections; import java.util.Date; @@ -30,106 +34,115 @@ public GHAppTest() { } /** - * Gets the git hub builder. - * - * @return the git hub builder - */ - protected GitHubBuilder getGitHubBuilder() { - return super.getGitHubBuilder() - // ensure that only JWT will be used against the tests below - .withOAuthToken(null, null) - // Note that we used to provide a bogus token here and to rely on (apparently) manually crafted/edited - // Wiremock recordings, so most of the tests cannot actually be executed against GitHub without - // relying on the Wiremock recordings. - // Some tests have been updated, though (getGitHubApp in particular). - .withAuthorizationProvider(jwtProvider1); - } - - /** - * Gets the git hub app. + * Creates the token. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void getGitHubApp() throws IOException { + public void createToken() throws IOException { GHApp app = gitHub.getApp(); - assertThat(app.getId(), is((long) 82994)); - assertThat(app.getOwner().getId(), is((long) 7544739)); - assertThat(app.getOwner().getLogin(), is("hub4j-test-org")); - assertThat(app.getOwner().getType(), is("Organization")); - assertThat(app.getName(), is("GHApi Test app 1")); - assertThat(app.getSlug(), is("ghapi-test-app-1")); - assertThat(app.getDescription(), is("")); - assertThat(app.getExternalUrl(), is("http://localhost")); - assertThat(app.getHtmlUrl().toString(), is("https://github.com/apps/ghapi-test-app-1")); - assertThat(app.getCreatedAt(), is(GitHubClient.parseDate("2020-09-30T13:40:56Z"))); - assertThat(app.getUpdatedAt(), is(GitHubClient.parseDate("2020-09-30T13:40:56Z"))); - assertThat(app.getPermissions().size(), is(2)); - assertThat(app.getEvents().size(), is(0)); - assertThat(app.getInstallationsCount(), is((long) 1)); + GHAppInstallation installation = app.getInstallationByUser("bogus"); + + Map permissions = new HashMap(); + permissions.put("checks", GHPermissionType.WRITE); + permissions.put("pull_requests", GHPermissionType.WRITE); + permissions.put("contents", GHPermissionType.READ); + permissions.put("metadata", GHPermissionType.READ); + + // Create token specifying both permissions and repository ids + GHAppInstallationToken installationToken = installation.createToken(permissions) + .repositoryIds(Collections.singletonList((long) 111111111)) + .create(); + + assertThat(installationToken.getToken(), is("bogus")); + assertThat(installation.getPermissions(), is(permissions)); + assertThat(installationToken.getRepositorySelection(), is(GHRepositorySelection.SELECTED)); + assertThat(installationToken.getExpiresAt(), is(GitHubClient.parseInstant("2019-08-10T05:54:58Z"))); + + GHRepository repository = installationToken.getRepositories().get(0); + assertThat(installationToken.getRepositories().size(), is(1)); + assertThat(repository.getId(), is((long) 111111111)); + assertThat(repository.getName(), is("bogus")); + + // Create token with no payload + GHAppInstallationToken installationToken2 = installation.createToken().create(); + + assertThat(installationToken2.getToken(), is("bogus")); + assertThat(installationToken2.getPermissions().size(), is(4)); + assertThat(installationToken2.getRepositorySelection(), is(GHRepositorySelection.ALL)); + assertThat(installationToken2.getExpiresAt(), is(GitHubClient.parseInstant("2019-12-19T12:27:59Z"))); + + assertThat(installationToken2.getRepositories(), nullValue());; } /** - * List installation requests. + * Creates the token with repositories. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void listInstallationRequests() throws IOException { + public void createTokenWithRepositories() throws IOException { GHApp app = gitHub.getApp(); - List installations = app.listInstallationRequests().toList(); - assertThat(installations.size(), is(1)); + GHAppInstallation installation = app.getInstallationByUser("bogus"); - GHAppInstallationRequest appInstallation = installations.get(0); - assertThat(appInstallation.getId(), is((long) 1037204)); - assertThat(appInstallation.getAccount().getId(), is((long) 195438329)); - assertThat(appInstallation.getAccount().getLogin(), is("approval-test")); - assertThat(appInstallation.getAccount().getType(), is("Organization")); - assertThat(appInstallation.getRequester().getId(), is((long) 195437694)); - assertThat(appInstallation.getRequester().getLogin(), is("kaladinstormblessed2")); - assertThat(appInstallation.getRequester().getType(), is("User")); - assertThat(appInstallation.getCreatedAt(), is(GitHubClient.parseDate("2025-01-17T15:50:51Z"))); - assertThat(appInstallation.getNodeId(), is("MDMwOkludGVncmF0aW9uSW5zdGFsbGF0aW9uUmVxdWVzdDEwMzcyMDQ=")); + // Create token specifying repositories (not repository_ids!) + GHAppInstallationToken installationToken = installation.createToken() + .repositories(Collections.singletonList("bogus")) + .create(); + + assertThat(installationToken.getToken(), is("bogus")); + assertThat(installationToken.getPermissions().entrySet(), hasSize(4)); + assertThat(installationToken.getRepositorySelection(), is(GHRepositorySelection.SELECTED)); + assertThat(installationToken.getExpiresAt(), is(GitHubClient.parseInstant("2022-07-27T21:38:33Z"))); + + GHRepository repository = installationToken.getRepositories().get(0); + assertThat(installationToken.getRepositories().size(), is(1)); + assertThat(repository.getId(), is((long) 11111111)); + assertThat(repository.getName(), is("bogus")); } /** - * List installations. + * Delete installation. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void listInstallations() throws IOException { + public void deleteInstallation() throws IOException { GHApp app = gitHub.getApp(); - List installations = app.listInstallations().toList(); - assertThat(installations.size(), is(1)); - - GHAppInstallation appInstallation = installations.get(0); - testAppInstallation(appInstallation); + GHAppInstallation installation = app.getInstallationByUser("bogus"); + try { + installation.deleteInstallation(); + } catch (IOException e) { + fail("deleteInstallation wasn't suppose to fail in this test"); + } } /** - * List installations that have been updated since a given date. + * Gets the git hub app. * * @throws IOException * Signals that an I/O exception has occurred. - * - * @throws ParseException - * Issue parsing date string. */ @Test - public void listInstallationsSince() throws IOException, ParseException { - SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); - simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - Date localDate = simpleDateFormat.parse("2023-11-01"); + public void getGitHubApp() throws IOException { GHApp app = gitHub.getApp(); - List installations = app.listInstallations(localDate).toList(); - assertThat(installations.size(), is(1)); - - GHAppInstallation appInstallation = installations.get(0); - testAppInstallation(appInstallation); + assertThat(app.getId(), is((long) 82994)); + assertThat(app.getOwner().getId(), is((long) 7544739)); + assertThat(app.getOwner().getLogin(), is("hub4j-test-org")); + assertThat(app.getOwner().getType(), is("Organization")); + assertThat(app.getName(), is("GHApi Test app 1")); + assertThat(app.getSlug(), is("ghapi-test-app-1")); + assertThat(app.getDescription(), is("")); + assertThat(app.getExternalUrl(), is("http://localhost")); + assertThat(app.getHtmlUrl().toString(), is("https://github.com/apps/ghapi-test-app-1")); + assertThat(app.getCreatedAt(), is(GitHubClient.parseInstant("2020-09-30T13:40:56Z"))); + assertThat(app.getUpdatedAt(), is(GitHubClient.parseInstant("2020-09-30T13:40:56Z"))); + assertThat(app.getPermissions().size(), is(2)); + assertThat(app.getEvents().size(), is(0)); + assertThat(app.getInstallationsCount(), is((long) 1)); } /** @@ -185,90 +198,68 @@ public void getInstallationByUser() throws IOException { } /** - * Delete installation. + * List installation requests. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void deleteInstallation() throws IOException { + public void listInstallationRequests() throws IOException { GHApp app = gitHub.getApp(); - GHAppInstallation installation = app.getInstallationByUser("bogus"); - try { - installation.deleteInstallation(); - } catch (IOException e) { - fail("deleteInstallation wasn't suppose to fail in this test"); - } + List installations = app.listInstallationRequests().toList(); + assertThat(installations.size(), is(1)); + + GHAppInstallationRequest appInstallation = installations.get(0); + assertThat(appInstallation.getId(), is((long) 1037204)); + assertThat(appInstallation.getAccount().getId(), is((long) 195438329)); + assertThat(appInstallation.getAccount().getLogin(), is("approval-test")); + assertThat(appInstallation.getAccount().getType(), is("Organization")); + assertThat(appInstallation.getRequester().getId(), is((long) 195437694)); + assertThat(appInstallation.getRequester().getLogin(), is("kaladinstormblessed2")); + assertThat(appInstallation.getRequester().getType(), is("User")); + assertThat(appInstallation.getCreatedAt(), is(GitHubClient.parseInstant("2025-01-17T15:50:51Z"))); + assertThat(appInstallation.getNodeId(), is("MDMwOkludGVncmF0aW9uSW5zdGFsbGF0aW9uUmVxdWVzdDEwMzcyMDQ=")); } /** - * Creates the token. + * List installations. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void createToken() throws IOException { + public void listInstallations() throws IOException { GHApp app = gitHub.getApp(); - GHAppInstallation installation = app.getInstallationByUser("bogus"); - - Map permissions = new HashMap(); - permissions.put("checks", GHPermissionType.WRITE); - permissions.put("pull_requests", GHPermissionType.WRITE); - permissions.put("contents", GHPermissionType.READ); - permissions.put("metadata", GHPermissionType.READ); - - // Create token specifying both permissions and repository ids - GHAppInstallationToken installationToken = installation.createToken(permissions) - .repositoryIds(Collections.singletonList((long) 111111111)) - .create(); - - assertThat(installationToken.getToken(), is("bogus")); - assertThat(installation.getPermissions(), is(permissions)); - assertThat(installationToken.getRepositorySelection(), is(GHRepositorySelection.SELECTED)); - assertThat(installationToken.getExpiresAt(), is(GitHubClient.parseDate("2019-08-10T05:54:58Z"))); - - GHRepository repository = installationToken.getRepositories().get(0); - assertThat(installationToken.getRepositories().size(), is(1)); - assertThat(repository.getId(), is((long) 111111111)); - assertThat(repository.getName(), is("bogus")); - - // Create token with no payload - GHAppInstallationToken installationToken2 = installation.createToken().create(); - - assertThat(installationToken2.getToken(), is("bogus")); - assertThat(installationToken2.getPermissions().size(), is(4)); - assertThat(installationToken2.getRepositorySelection(), is(GHRepositorySelection.ALL)); - assertThat(installationToken2.getExpiresAt(), is(GitHubClient.parseDate("2019-12-19T12:27:59Z"))); + List installations = app.listInstallations().toList(); + assertThat(installations.size(), is(1)); - assertThat(installationToken2.getRepositories(), nullValue());; + GHAppInstallation appInstallation = installations.get(0); + testAppInstallation(appInstallation); } /** - * Creates the token with repositories. + * List installations that have been updated since a given date. * * @throws IOException * Signals that an I/O exception has occurred. + * @throws ParseException + * Signals that a ParseException has occurred. + * */ @Test - public void createTokenWithRepositories() throws IOException { + public void listInstallationsSince() throws IOException, ParseException { + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); + simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + Date localDate = simpleDateFormat.parse("2023-11-01"); + Instant localInstant = LocalDate.parse("2023-11-01", DateTimeFormatter.ISO_LOCAL_DATE) + .atStartOfDay() + .toInstant(ZoneOffset.UTC); GHApp app = gitHub.getApp(); - GHAppInstallation installation = app.getInstallationByUser("bogus"); - - // Create token specifying repositories (not repository_ids!) - GHAppInstallationToken installationToken = installation.createToken() - .repositories(Collections.singletonList("bogus")) - .create(); - - assertThat(installationToken.getToken(), is("bogus")); - assertThat(installationToken.getPermissions().entrySet(), hasSize(4)); - assertThat(installationToken.getRepositorySelection(), is(GHRepositorySelection.SELECTED)); - assertThat(installationToken.getExpiresAt(), is(GitHubClient.parseDate("2022-07-27T21:38:33Z"))); + List installations = app.listInstallations(localDate).toList(); + assertThat(installations.size(), is(1)); - GHRepository repository = installationToken.getRepositories().get(0); - assertThat(installationToken.getRepositories().size(), is(1)); - assertThat(repository.getId(), is((long) 11111111)); - assertThat(repository.getName(), is("bogus")); + GHAppInstallation appInstallation = installations.get(0); + testAppInstallation(appInstallation); } private void testAppInstallation(GHAppInstallation appInstallation) throws IOException { @@ -295,9 +286,25 @@ private void testAppInstallation(GHAppInstallation appInstallation) throws IOExc List events = Arrays.asList(GHEvent.PULL_REQUEST, GHEvent.PUSH); assertThat(appInstallation.getEvents(), containsInAnyOrder(events.toArray(new GHEvent[0]))); - assertThat(appInstallation.getCreatedAt(), is(GitHubClient.parseDate("2019-07-04T01:19:36.000Z"))); - assertThat(appInstallation.getUpdatedAt(), is(GitHubClient.parseDate("2019-07-30T22:48:09.000Z"))); + assertThat(appInstallation.getCreatedAt(), is(GitHubClient.parseInstant("2019-07-04T01:19:36.000Z"))); + assertThat(appInstallation.getUpdatedAt(), is(GitHubClient.parseInstant("2019-07-30T22:48:09.000Z"))); assertThat(appInstallation.getSingleFileName(), nullValue()); } + /** + * Gets the git hub builder. + * + * @return the git hub builder + */ + protected GitHubBuilder getGitHubBuilder() { + return super.getGitHubBuilder() + // ensure that only JWT will be used against the tests below + .withOAuthToken(null, null) + // Note that we used to provide a bogus token here and to rely on (apparently) manually crafted/edited + // Wiremock recordings, so most of the tests cannot actually be executed against GitHub without + // relying on the Wiremock recordings. + // Some tests have been updated, though (getGitHubApp in particular). + .withAuthorizationProvider(jwtProvider1); + } + } diff --git a/src/test/java/org/kohsuke/github/GHAuthenticatedAppInstallationTest.java b/src/test/java/org/kohsuke/github/GHAuthenticatedAppInstallationTest.java index 3786f22e37..775af6712d 100644 --- a/src/test/java/org/kohsuke/github/GHAuthenticatedAppInstallationTest.java +++ b/src/test/java/org/kohsuke/github/GHAuthenticatedAppInstallationTest.java @@ -21,19 +21,6 @@ public class GHAuthenticatedAppInstallationTest extends AbstractGHAppInstallatio public GHAuthenticatedAppInstallationTest() { } - /** - * Gets the git hub builder. - * - * @return the git hub builder - */ - @Override - protected GitHubBuilder getGitHubBuilder() { - AppInstallationAuthorizationProvider provider = new AppInstallationAuthorizationProvider( - app -> app.getInstallationByOrganization("hub4j-test-org"), - jwtProvider1); - return super.getGitHubBuilder().withAuthorizationProvider(provider); - } - /** * Test list repositories two repos. * @@ -51,4 +38,17 @@ public void testListRepositoriesTwoRepos() throws IOException { arrayContainingInAnyOrder("empty", "test-readme")); } + /** + * Gets the git hub builder. + * + * @return the git hub builder + */ + @Override + protected GitHubBuilder getGitHubBuilder() { + AppInstallationAuthorizationProvider provider = new AppInstallationAuthorizationProvider( + app -> app.getInstallationByOrganization("hub4j-test-org"), + jwtProvider1); + return super.getGitHubBuilder().withAuthorizationProvider(provider); + } + } diff --git a/src/test/java/org/kohsuke/github/GHAutolinkTest.java b/src/test/java/org/kohsuke/github/GHAutolinkTest.java index 203bf7754a..b337542ee1 100644 --- a/src/test/java/org/kohsuke/github/GHAutolinkTest.java +++ b/src/test/java/org/kohsuke/github/GHAutolinkTest.java @@ -22,6 +22,27 @@ public class GHAutolinkTest extends AbstractGitHubWireMockTest { public GHAutolinkTest() { } + /** + * Cleanup. + */ + @After + public void cleanup() { + if (repo != null) { + try { + PagedIterable autolinks = repo.listAutolinks(); + for (GHAutolink autolink : autolinks) { + try { + autolink.delete(); + } catch (Exception e) { + System.err.println("Failed to delete autolink: " + e.getMessage()); + } + } + } catch (Exception e) { + System.err.println("Cleanup failed: " + e.getMessage()); + } + } + } + /** * Sets up. * @@ -65,29 +86,44 @@ public void testCreateAutolink() throws Exception { } /** - * Test get autolink. + * Test delete autolink. * * @throws Exception * the exception */ @Test - public void testReadAutolink() throws Exception { + public void testDeleteAutolink() throws Exception { + // Delete autolink using the instance method GHAutolink autolink = repo.createAutolink() - .withKeyPrefix("JIRA-") - .withUrlTemplate("https://example.com/test/") - .withIsAlphanumeric(false) + .withKeyPrefix("DELETE-") + .withUrlTemplate("https://example.com/delete/") + .withIsAlphanumeric(true) .create(); - GHAutolink fetched = repo.readAutolink(autolink.getId()); + autolink.delete(); - assertThat(fetched.getId(), equalTo(autolink.getId())); - assertThat(fetched.getKeyPrefix(), equalTo(autolink.getKeyPrefix())); - assertThat(fetched.getUrlTemplate(), equalTo(autolink.getUrlTemplate())); - assertThat(fetched.isAlphanumeric(), equalTo(autolink.isAlphanumeric())); - assertThat(fetched.getOwner(), equalTo(repo)); + try { + repo.readAutolink(autolink.getId()); + fail("Expected GHFileNotFoundException"); + } catch (GHFileNotFoundException e) { + // Expected + } - autolink.delete(); + // Delete autolink using repository delete method + autolink = repo.createAutolink() + .withKeyPrefix("DELETED-") + .withUrlTemplate("https://example.com/delete2/") + .withIsAlphanumeric(true) + .create(); + repo.deleteAutolink(autolink.getId()); + + try { + repo.readAutolink(autolink.getId()); + fail("Expected GHFileNotFoundException"); + } catch (GHFileNotFoundException e) { + // Expected + } } /** @@ -142,64 +178,28 @@ public void testListAllAutolinks() throws Exception { } /** - * Test delete autolink. + * Test get autolink. * * @throws Exception * the exception */ @Test - public void testDeleteAutolink() throws Exception { - // Delete autolink using the instance method + public void testReadAutolink() throws Exception { GHAutolink autolink = repo.createAutolink() - .withKeyPrefix("DELETE-") - .withUrlTemplate("https://example.com/delete/") - .withIsAlphanumeric(true) + .withKeyPrefix("JIRA-") + .withUrlTemplate("https://example.com/test/") + .withIsAlphanumeric(false) .create(); - autolink.delete(); - - try { - repo.readAutolink(autolink.getId()); - fail("Expected GHFileNotFoundException"); - } catch (GHFileNotFoundException e) { - // Expected - } - - // Delete autolink using repository delete method - autolink = repo.createAutolink() - .withKeyPrefix("DELETED-") - .withUrlTemplate("https://example.com/delete2/") - .withIsAlphanumeric(true) - .create(); + GHAutolink fetched = repo.readAutolink(autolink.getId()); - repo.deleteAutolink(autolink.getId()); + assertThat(fetched.getId(), equalTo(autolink.getId())); + assertThat(fetched.getKeyPrefix(), equalTo(autolink.getKeyPrefix())); + assertThat(fetched.getUrlTemplate(), equalTo(autolink.getUrlTemplate())); + assertThat(fetched.isAlphanumeric(), equalTo(autolink.isAlphanumeric())); + assertThat(fetched.getOwner(), equalTo(repo)); - try { - repo.readAutolink(autolink.getId()); - fail("Expected GHFileNotFoundException"); - } catch (GHFileNotFoundException e) { - // Expected - } - } + autolink.delete(); - /** - * Cleanup. - */ - @After - public void cleanup() { - if (repo != null) { - try { - PagedIterable autolinks = repo.listAutolinks(); - for (GHAutolink autolink : autolinks) { - try { - autolink.delete(); - } catch (Exception e) { - System.err.println("Failed to delete autolink: " + e.getMessage()); - } - } - } catch (Exception e) { - System.err.println("Cleanup failed: " + e.getMessage()); - } - } } } diff --git a/src/test/java/org/kohsuke/github/GHBranchProtectionTest.java b/src/test/java/org/kohsuke/github/GHBranchProtectionTest.java index d01ecabcf7..5418427bf5 100755 --- a/src/test/java/org/kohsuke/github/GHBranchProtectionTest.java +++ b/src/test/java/org/kohsuke/github/GHBranchProtectionTest.java @@ -23,19 +23,19 @@ */ public class GHBranchProtectionTest extends AbstractGitHubWireMockTest { - /** - * Create default GHBranchProtectionTest instance - */ - public GHBranchProtectionTest() { - } - private static final String BRANCH = "main"; - private static final String BRANCH_REF = "heads/" + BRANCH; + private static final String BRANCH_REF = "heads/" + BRANCH; private GHBranch branch; private GHRepository repo; + /** + * Create default GHBranchProtectionTest instance + */ + public GHBranchProtectionTest() { + } + /** * Sets the up. * @@ -48,6 +48,45 @@ public void setUp() throws Exception { branch = repo.getBranch(BRANCH); } + /** + * Checks with app ids are being populated + * + * @throws Exception + * the exception + */ + @Test + public void testChecksWithAppIds() throws Exception { + GHBranchProtection protection = branch.enableProtection() + .addRequiredChecks(new GHBranchProtection.Check("context", -1), + new GHBranchProtection.Check("context2", 123), + new GHBranchProtection.Check("context3", null)) + .enable(); + + ArrayList resultChecks = new ArrayList<>( + protection.getRequiredStatusChecks().getChecks()); + + assertThat(resultChecks.size(), is(3)); + assertThat(resultChecks.get(0).getContext(), is("context")); + assertThat(resultChecks.get(0).getAppId(), nullValue()); + assertThat(resultChecks.get(1).getContext(), is("context2")); + assertThat(resultChecks.get(1).getAppId(), is(123)); + assertThat(resultChecks.get(2).getContext(), is("context3")); + } + + /** + * Test disable protection only. + * + * @throws Exception + * the exception + */ + @Test + public void testDisableProtectionOnly() throws Exception { + GHBranchProtection protection = branch.enableProtection().enable(); + assertThat(repo.getBranch(BRANCH).isProtected(), is(true)); + branch.disableProtection(); + assertThat(repo.getBranch(BRANCH).isProtected(), is(false)); + } + /** * Test enable branch protections. * @@ -81,52 +120,6 @@ public void testEnableBranchProtections() throws Exception { verifyBranchProtection(protection); } - private void verifyBranchProtection(GHBranchProtection protection) { - RequiredStatusChecks statusChecks = protection.getRequiredStatusChecks(); - assertThat(statusChecks, notNullValue()); - assertThat(statusChecks.isRequiresBranchUpToDate(), is(true)); - assertThat(statusChecks.getContexts(), contains("test-status-check")); - - RequiredReviews requiredReviews = protection.getRequiredReviews(); - assertThat(requiredReviews, notNullValue()); - assertThat(requiredReviews.isDismissStaleReviews(), is(true)); - assertThat(requiredReviews.isRequireCodeOwnerReviews(), is(true)); - assertThat(requiredReviews.isRequireLastPushApproval(), is(true)); - assertThat(requiredReviews.getRequiredReviewers(), equalTo(2)); - - AllowDeletions allowDeletions = protection.getAllowDeletions(); - assertThat(allowDeletions, notNullValue()); - assertThat(allowDeletions.isEnabled(), is(true)); - - AllowForcePushes allowForcePushes = protection.getAllowForcePushes(); - assertThat(allowForcePushes, notNullValue()); - assertThat(allowForcePushes.isEnabled(), is(true)); - - AllowForkSyncing allowForkSyncing = protection.getAllowForkSyncing(); - assertThat(allowForkSyncing, notNullValue()); - assertThat(allowForkSyncing.isEnabled(), is(true)); - - BlockCreations blockCreations = protection.getBlockCreations(); - assertThat(blockCreations, notNullValue()); - assertThat(blockCreations.isEnabled(), is(true)); - - EnforceAdmins enforceAdmins = protection.getEnforceAdmins(); - assertThat(enforceAdmins, notNullValue()); - assertThat(enforceAdmins.isEnabled(), is(true)); - - LockBranch lockBranch = protection.getLockBranch(); - assertThat(lockBranch, notNullValue()); - assertThat(lockBranch.isEnabled(), is(true)); - - RequiredConversationResolution requiredConversationResolution = protection.getRequiredConversationResolution(); - assertThat(requiredConversationResolution, notNullValue()); - assertThat(requiredConversationResolution.isEnabled(), is(true)); - - RequiredLinearHistory requiredLinearHistory = protection.getRequiredLinearHistory(); - assertThat(requiredLinearHistory, notNullValue()); - assertThat(requiredLinearHistory.isEnabled(), is(true)); - } - /** * Test enable protection only. * @@ -139,20 +132,6 @@ public void testEnableProtectionOnly() throws Exception { assertThat(repo.getBranch(BRANCH).isProtected(), is(true)); } - /** - * Test disable protection only. - * - * @throws Exception - * the exception - */ - @Test - public void testDisableProtectionOnly() throws Exception { - GHBranchProtection protection = branch.enableProtection().enable(); - assertThat(repo.getBranch(BRANCH).isProtected(), is(true)); - branch.disableProtection(); - assertThat(repo.getBranch(BRANCH).isProtected(), is(false)); - } - /** * Test enable require reviews only. * @@ -179,6 +158,21 @@ public void testEnableRequireReviewsOnly() throws Exception { assertThat(protection.getRequiredReviews().getRequiredReviewers(), equalTo(1)); } + /** + * Test get protection. + * + * @throws Exception + * the exception + */ + @Test + public void testGetProtection() throws Exception { + GHBranchProtection protection = branch.enableProtection().enable(); + GHBranchProtection protectionTest = repo.getBranch(BRANCH).getProtection(); + Boolean condition = protectionTest instanceof GHBranchProtection; + assertThat(protectionTest, instanceOf(GHBranchProtection.class)); + assertThat(repo.getBranch(BRANCH).isProtected(), is(true)); + } + /** * Test signed commits. * @@ -198,43 +192,49 @@ public void testSignedCommits() throws Exception { assertThat(protection.getRequiredSignatures(), is(false)); } - /** - * Checks with app ids are being populated - * - * @throws Exception - * the exception - */ - @Test - public void testChecksWithAppIds() throws Exception { - GHBranchProtection protection = branch.enableProtection() - .addRequiredChecks(new GHBranchProtection.Check("context", -1), - new GHBranchProtection.Check("context2", 123), - new GHBranchProtection.Check("context3", null)) - .enable(); + private void verifyBranchProtection(GHBranchProtection protection) { + RequiredStatusChecks statusChecks = protection.getRequiredStatusChecks(); + assertThat(statusChecks, notNullValue()); + assertThat(statusChecks.isRequiresBranchUpToDate(), is(true)); + assertThat(statusChecks.getContexts(), contains("test-status-check")); - ArrayList resultChecks = new ArrayList<>( - protection.getRequiredStatusChecks().getChecks()); + RequiredReviews requiredReviews = protection.getRequiredReviews(); + assertThat(requiredReviews, notNullValue()); + assertThat(requiredReviews.isDismissStaleReviews(), is(true)); + assertThat(requiredReviews.isRequireCodeOwnerReviews(), is(true)); + assertThat(requiredReviews.isRequireLastPushApproval(), is(true)); + assertThat(requiredReviews.getRequiredReviewers(), equalTo(2)); - assertThat(resultChecks.size(), is(3)); - assertThat(resultChecks.get(0).getContext(), is("context")); - assertThat(resultChecks.get(0).getAppId(), nullValue()); - assertThat(resultChecks.get(1).getContext(), is("context2")); - assertThat(resultChecks.get(1).getAppId(), is(123)); - assertThat(resultChecks.get(2).getContext(), is("context3")); - } + AllowDeletions allowDeletions = protection.getAllowDeletions(); + assertThat(allowDeletions, notNullValue()); + assertThat(allowDeletions.isEnabled(), is(true)); - /** - * Test get protection. - * - * @throws Exception - * the exception - */ - @Test - public void testGetProtection() throws Exception { - GHBranchProtection protection = branch.enableProtection().enable(); - GHBranchProtection protectionTest = repo.getBranch(BRANCH).getProtection(); - Boolean condition = protectionTest instanceof GHBranchProtection; - assertThat(protectionTest, instanceOf(GHBranchProtection.class)); - assertThat(repo.getBranch(BRANCH).isProtected(), is(true)); + AllowForcePushes allowForcePushes = protection.getAllowForcePushes(); + assertThat(allowForcePushes, notNullValue()); + assertThat(allowForcePushes.isEnabled(), is(true)); + + AllowForkSyncing allowForkSyncing = protection.getAllowForkSyncing(); + assertThat(allowForkSyncing, notNullValue()); + assertThat(allowForkSyncing.isEnabled(), is(true)); + + BlockCreations blockCreations = protection.getBlockCreations(); + assertThat(blockCreations, notNullValue()); + assertThat(blockCreations.isEnabled(), is(true)); + + EnforceAdmins enforceAdmins = protection.getEnforceAdmins(); + assertThat(enforceAdmins, notNullValue()); + assertThat(enforceAdmins.isEnabled(), is(true)); + + LockBranch lockBranch = protection.getLockBranch(); + assertThat(lockBranch, notNullValue()); + assertThat(lockBranch.isEnabled(), is(true)); + + RequiredConversationResolution requiredConversationResolution = protection.getRequiredConversationResolution(); + assertThat(requiredConversationResolution, notNullValue()); + assertThat(requiredConversationResolution.isEnabled(), is(true)); + + RequiredLinearHistory requiredLinearHistory = protection.getRequiredLinearHistory(); + assertThat(requiredLinearHistory, notNullValue()); + assertThat(requiredLinearHistory.isEnabled(), is(true)); } } diff --git a/src/test/java/org/kohsuke/github/GHBranchTest.java b/src/test/java/org/kohsuke/github/GHBranchTest.java index 117da049f7..e74a402d94 100644 --- a/src/test/java/org/kohsuke/github/GHBranchTest.java +++ b/src/test/java/org/kohsuke/github/GHBranchTest.java @@ -10,17 +10,17 @@ */ public class GHBranchTest extends AbstractGitHubWireMockTest { + private static final String BRANCH_1 = "testBranch1"; + + private static final String BRANCH_2 = "testBranch2"; + private GHRepository repository; + /** * Create default GHBranchTest instance */ public GHBranchTest() { } - private static final String BRANCH_1 = "testBranch1"; - private static final String BRANCH_2 = "testBranch2"; - - private GHRepository repository; - /** * Test merge branch. * diff --git a/src/test/java/org/kohsuke/github/GHCheckRunBuilderTest.java b/src/test/java/org/kohsuke/github/GHCheckRunBuilderTest.java index a52e07e3a4..ef888faf5e 100644 --- a/src/test/java/org/kohsuke/github/GHCheckRunBuilderTest.java +++ b/src/test/java/org/kohsuke/github/GHCheckRunBuilderTest.java @@ -29,6 +29,7 @@ import org.kohsuke.github.GHCheckRun.Status; import java.io.IOException; +import java.time.Instant; import java.util.Date; import static org.hamcrest.Matchers.*; @@ -46,17 +47,6 @@ public class GHCheckRunBuilderTest extends AbstractGHAppInstallationTest { public GHCheckRunBuilderTest() { } - /** - * Gets the installation github. - * - * @return the installation github - * @throws IOException - * Signals that an I/O exception has occurred. - */ - protected GitHub getInstallationGithub() throws IOException { - return getAppInstallationWithToken(jwtProvider3.getEncodedAuthorization()).root(); - } - /** * Creates the check run. * @@ -89,6 +79,28 @@ public void createCheckRun() throws Exception { assertThat(checkRun.getOutput().getText(), equalTo("Hello Text!")); } + /** + * Creates the check run err missing conclusion. + * + * @throws Exception + * the exception + */ + @Test + public void createCheckRunErrMissingConclusion() throws Exception { + try { + getInstallationGithub().getRepository("hub4j-test-org/test-checks") + .createCheckRun("outstanding", "89a9ae301e35e667756034fdc933b1fc94f63fc1") + .withStatus(GHCheckRun.Status.COMPLETED) + .create(); + fail("should have been rejected"); + } catch (HttpException x) { + assertThat(x.getResponseCode(), equalTo(422)); + assertThat(x.getMessage(), containsString("\\\"conclusion\\\" wasn't supplied")); + assertThat(x.getUrl(), containsString("/repos/hub4j-test-org/test-checks/check-runs")); + assertThat(x.getResponseMessage(), containsString("Unprocessable Entity")); + } + } + /** * Creates the check run many annotations. * @@ -152,28 +164,6 @@ public void createPendingCheckRun() throws Exception { assertThat(checkRun.getId(), equalTo(1424883451L)); } - /** - * Creates the check run err missing conclusion. - * - * @throws Exception - * the exception - */ - @Test - public void createCheckRunErrMissingConclusion() throws Exception { - try { - getInstallationGithub().getRepository("hub4j-test-org/test-checks") - .createCheckRun("outstanding", "89a9ae301e35e667756034fdc933b1fc94f63fc1") - .withStatus(GHCheckRun.Status.COMPLETED) - .create(); - fail("should have been rejected"); - } catch (HttpException x) { - assertThat(x.getResponseCode(), equalTo(422)); - assertThat(x.getMessage(), containsString("\\\"conclusion\\\" wasn't supplied")); - assertThat(x.getUrl(), containsString("/repos/hub4j-test-org/test-checks/check-runs")); - assertThat(x.getResponseMessage(), containsString("Unprocessable Entity")); - } - } - /** * Update check run. * @@ -185,7 +175,7 @@ public void updateCheckRun() throws Exception { GHCheckRun checkRun = getInstallationGithub().getRepository("hub4j-test-org/test-checks") .createCheckRun("foo", "89a9ae301e35e667756034fdc933b1fc94f63fc1") .withStatus(GHCheckRun.Status.IN_PROGRESS) - .withStartedAt(new Date(999_999_000)) + .withStartedAt(Instant.ofEpochMilli(999_999_000)) .add(new GHCheckRunBuilder.Output("Some Title", "what happenedâ€Ļ") .add(new GHCheckRunBuilder.Annotation("stuff.txt", 1, @@ -197,7 +187,7 @@ public void updateCheckRun() throws Exception { .withConclusion(GHCheckRun.Conclusion.SUCCESS) .withCompletedAt(new Date(999_999_999)) .create(); - assertThat(new Date(999_999_000), equalTo(updated.getStartedAt())); + assertThat(Instant.ofEpochMilli(999_999_000), equalTo(updated.getStartedAt())); assertThat("foo", equalTo(updated.getName())); assertThat(checkRun.getOutput().getAnnotationsCount(), equalTo(1)); } @@ -213,7 +203,7 @@ public void updateCheckRunWithName() throws Exception { GHCheckRun checkRun = getInstallationGithub().getRepository("hub4j-test-org/test-checks") .createCheckRun("foo", "89a9ae301e35e667756034fdc933b1fc94f63fc1") .withStatus(GHCheckRun.Status.IN_PROGRESS) - .withStartedAt(new Date(999_999_000)) + .withStartedAt(Instant.ofEpochMilli(999_999_000)) .add(new GHCheckRunBuilder.Output("Some Title", "what happenedâ€Ļ") .add(new GHCheckRunBuilder.Annotation("stuff.txt", 1, @@ -223,10 +213,10 @@ public void updateCheckRunWithName() throws Exception { GHCheckRun updated = checkRun.update() .withStatus(GHCheckRun.Status.COMPLETED) .withConclusion(GHCheckRun.Conclusion.SUCCESS) - .withCompletedAt(new Date(999_999_999)) + .withCompletedAt(Instant.ofEpochMilli(999_999_999)) .withName("bar", checkRun.getName()) .create(); - assertThat(new Date(999_999_000), equalTo(updated.getStartedAt())); + assertThat(Instant.ofEpochMilli(999_999_000), equalTo(updated.getStartedAt())); assertThat("bar", equalTo(updated.getName())); assertThat(checkRun.getOutput().getAnnotationsCount(), equalTo(1)); } @@ -243,7 +233,7 @@ public void updateCheckRunWithNameException() throws Exception { GHCheckRun checkRun = getInstallationGithub().getRepository("hub4j-test-org/test-checks") .createCheckRun("foo", "89a9ae301e35e667756034fdc933b1fc94f63fc1") .withStatus(GHCheckRun.Status.IN_PROGRESS) - .withStartedAt(new Date(999_999_000)) + .withStartedAt(Instant.ofEpochMilli(999_999_000)) .add(new GHCheckRunBuilder.Output("Some Title", "what happenedâ€Ļ") .add(new GHCheckRunBuilder.Annotation("stuff.txt", 1, @@ -258,4 +248,15 @@ public void updateCheckRunWithNameException() throws Exception { .withName("bar", null) .create()); } + + /** + * Gets the installation github. + * + * @return the installation github + * @throws IOException + * Signals that an I/O exception has occurred. + */ + protected GitHub getInstallationGithub() throws IOException { + return getAppInstallationWithToken(jwtProvider3.getEncodedAuthorization()).root(); + } } diff --git a/src/test/java/org/kohsuke/github/GHCodeownersErrorTest.java b/src/test/java/org/kohsuke/github/GHCodeownersErrorTest.java index 958d4908be..6b964c59d3 100644 --- a/src/test/java/org/kohsuke/github/GHCodeownersErrorTest.java +++ b/src/test/java/org/kohsuke/github/GHCodeownersErrorTest.java @@ -44,6 +44,10 @@ public void testGetCodeownersErrors() throws IOException { assertThat(firstError.getPath(), is(".github/CODEOWNERS")); } + private GHRepository getRepository(GitHub gitHub) throws IOException { + return gitHub.getOrganization(GITHUB_API_TEST_ORG).getRepository("github-api"); + } + /** * Gets the repository. * @@ -54,8 +58,4 @@ public void testGetCodeownersErrors() throws IOException { protected GHRepository getRepository() throws IOException { return getRepository(gitHub); } - - private GHRepository getRepository(GitHub gitHub) throws IOException { - return gitHub.getOrganization(GITHUB_API_TEST_ORG).getRepository("github-api"); - } } diff --git a/src/test/java/org/kohsuke/github/GHContentIntegrationTest.java b/src/test/java/org/kohsuke/github/GHContentIntegrationTest.java index 719382faed..d310b8924b 100644 --- a/src/test/java/org/kohsuke/github/GHContentIntegrationTest.java +++ b/src/test/java/org/kohsuke/github/GHContentIntegrationTest.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.util.Date; import java.util.List; import static org.hamcrest.Matchers.*; @@ -20,18 +21,18 @@ */ public class GHContentIntegrationTest extends AbstractGitHubWireMockTest { + // file name with spaces and other chars + private final String createdDirectory = "test+directory #50"; + + private final String createdFilename = createdDirectory + "/test file-to+create-#1.txt"; + + private GHRepository repo; /** * Create default GHContentIntegrationTest instance */ public GHContentIntegrationTest() { } - private GHRepository repo; - - // file name with spaces and other chars - private final String createdDirectory = "test+directory #50"; - private final String createdFilename = createdDirectory + "/test file-to+create-#1.txt"; - /** * Cleanup. * @@ -64,88 +65,6 @@ public void setUp() throws Exception { repo = gitHub.getRepository("hub4j-test-org/GHContentIntegrationTest"); } - /** - * Test get repository. - * - * @throws Exception - * the exception - */ - @Test - public void testGetRepository() throws Exception { - GHRepository testRepo = gitHub.getRepositoryById(repo.getId()); - assertThat(testRepo.getName(), equalTo(repo.getName())); - } - - /** - * Test get repository created from a template repository - * - * @throws Exception - * the exception - */ - @Test - public void testGetRepositoryWithTemplateRepositoryInfo() throws Exception { - GHRepository testRepo = gitHub.getRepositoryById(repo.getId()); - assertThat(testRepo.getTemplateRepository(), notNullValue()); - assertThat(testRepo.getTemplateRepository().getOwnerName(), equalTo("octocat")); - assertThat(testRepo.getTemplateRepository().isTemplate(), equalTo(true)); - } - - /** - * Test get file content. - * - * @throws Exception - * the exception - */ - @Test - public void testGetFileContent() throws Exception { - repo = gitHub.getRepository("hub4j-test-org/GHContentIntegrationTest"); - GHContent content = repo.getFileContent("ghcontent-ro/a-file-with-content"); - - assertThat(content.isFile(), is(true)); - assertThat(content.getContent(), equalTo("thanks for reading me\n")); - } - - /** - * Test get empty file content. - * - * @throws Exception - * the exception - */ - @Test - public void testGetEmptyFileContent() throws Exception { - GHContent content = repo.getFileContent("ghcontent-ro/an-empty-file"); - - assertThat(content.isFile(), is(true)); - assertThat(content.getContent(), is(emptyString())); - } - - /** - * Test get directory content. - * - * @throws Exception - * the exception - */ - @Test - public void testGetDirectoryContent() throws Exception { - List entries = repo.getDirectoryContent("ghcontent-ro/a-dir-with-3-entries"); - - assertThat(entries.size(), equalTo(3)); - } - - /** - * Test get directory content trailing slash. - * - * @throws Exception - * the exception - */ - @Test - public void testGetDirectoryContentTrailingSlash() throws Exception { - // Used to truncate the ?ref=main, see gh-224 https://github.com/kohsuke/github-api/pull/224 - List entries = repo.getDirectoryContent("ghcontent-ro/a-dir-with-3-entries/", "main"); - - assertThat(entries.get(0).getUrl(), endsWith("?ref=main")); - } - /** * Test CRUD content. * @@ -228,220 +147,229 @@ public void testCRUDContent() throws Exception { } /** - * Check created commits. + * Test creating content with custom author and committer via GHContentBuilder. * - * @param gitCommit - * the git commit - * @param ghCommit - * the gh commit - * @param expectedRequestCount - * the expected request count - * @return the int * @throws Exception * the exception */ - int checkCreatedCommits(GitCommit gitCommit, GHCommit ghCommit, int expectedRequestCount) throws Exception { - expectedRequestCount = checkBasicCommitInfo(gitCommit, ghCommit, expectedRequestCount); - assertThat(mockGitHub.getRequestCount(), equalTo(expectedRequestCount)); - - assertThat(gitCommit.getMessage(), equalTo("Creating a file for integration tests.")); - assertThat(gitCommit.getAuthoredDate(), equalTo(GitHubClient.parseDate("2021-06-28T20:37:49Z"))); - assertThat(gitCommit.getCommitDate(), equalTo(GitHubClient.parseDate("2021-06-28T20:37:49Z"))); - - assertThat(ghCommit.getCommitShortInfo().getMessage(), equalTo("Creating a file for integration tests.")); - assertThat("Message already resolved", mockGitHub.getRequestCount(), equalTo(expectedRequestCount)); - assertThrows(GHException.class, () -> ghCommit.getCommitShortInfo().getCommentCount()); - - ghCommit.populate(); - assertThat("Populate GHCommit", mockGitHub.getRequestCount(), equalTo(expectedRequestCount += 1)); - - expectedRequestCount = checkCommitUserInfo(gitCommit, ghCommit, expectedRequestCount); - assertThat("Resolved GHUser for GHCommit", mockGitHub.getRequestCount(), equalTo(expectedRequestCount += 1)); - - expectedRequestCount = checkCommitTree(gitCommit, ghCommit, expectedRequestCount); - - expectedRequestCount = checkCommitParents(gitCommit, ghCommit, expectedRequestCount); + @Test + public void testCreateWithAuthorCommitter() throws Exception { + GHRepository ghRepository = getTempRepository(); + GHContentUpdateResponse response = ghRepository.createContent() + .message("Creating with custom author and committer") + .path("author-committer-test.md") + .content("test content\n") + .author("John Doe", "john@example.com") + .committer("Service Account", "service@example.com") + .commit(); - return expectedRequestCount; + assertThat(response.getContent(), notNullValue()); + assertThat(response.getCommit(), notNullValue()); + assertThat(response.getCommit().getAuthor().getName(), equalTo("John Doe")); + assertThat(response.getCommit().getAuthor().getEmail(), equalTo("john@example.com")); + assertThat(response.getCommit().getCommitter().getName(), equalTo("Service Account")); + assertThat(response.getCommit().getCommitter().getEmail(), equalTo("service@example.com")); } /** - * Gets the GH commit. + * Test creating content with custom author and committer with date via GHContentBuilder. * - * @param resp - * the resp - * @return the GH commit + * @throws Exception + * the exception */ - GHCommit getGHCommit(GHContentUpdateResponse resp) { - return resp.getCommit().toGHCommit(); + @SuppressWarnings("deprecation") + @Test + public void testCreateWithAuthorCommitterAndDate() throws Exception { + GHRepository ghRepository = getTempRepository(); + GHContentUpdateResponse response = ghRepository.createContent() + .message("Creating with custom author and committer") + .path("author-committer-test.md") + .content("test content\n") + .author("John Doe", "john@example.com", new Date(1234567890000L)) + .committer("Service Account", "service@example.com", new Date(1234567890000L)) + .commit(); + + assertThat(response.getContent(), notNullValue()); + assertThat(response.getCommit(), notNullValue()); + assertThat(response.getCommit().getAuthor().getName(), equalTo("John Doe")); + assertThat(response.getCommit().getAuthor().getEmail(), equalTo("john@example.com")); + assertThat(response.getCommit().getAuthoredDate(), equalTo(GitHubClient.parseInstant("2009-02-13T23:31:30Z"))); + assertThat(response.getCommit().getCommitter().getName(), equalTo("Service Account")); + assertThat(response.getCommit().getCommitter().getEmail(), equalTo("service@example.com")); + assertThat(response.getCommit().getCommitDate(), equalTo(GitHubClient.parseInstant("2009-02-13T23:31:30Z"))); } /** - * Check updated content response commits. + * Test deleting content with custom author and committer via GHContentDeleter. * - * @param gitCommit - * the git commit - * @param ghCommit - * the gh commit - * @param expectedRequestCount - * the expected request count - * @return the int * @throws Exception * the exception */ - int checkUpdatedContentResponseCommits(GitCommit gitCommit, GHCommit ghCommit, int expectedRequestCount) - throws Exception { - - expectedRequestCount = checkBasicCommitInfo(gitCommit, ghCommit, expectedRequestCount); - assertThat(mockGitHub.getRequestCount(), equalTo(expectedRequestCount)); - - assertThat(gitCommit.getMessage(), equalTo("Updated file for integration tests.")); - assertThat(gitCommit.getAuthoredDate(), equalTo(GitHubClient.parseDate("2021-06-28T20:37:51Z"))); - assertThat(gitCommit.getCommitDate(), equalTo(GitHubClient.parseDate("2021-06-28T20:37:51Z"))); + @Test + public void testDeleteWithAuthorCommitter() throws Exception { + GHContent content = repo.getFileContent("ghcontent-ro/a-file-with-content"); + GHContentUpdateResponse response = content.createDelete() + .message("Deleting with custom author and committer") + .branch("main") + .author("John Doe", "john@example.com") + .committer("Service Account", "service@example.com") + .commit(); - assertThat(ghCommit.getCommitShortInfo().getMessage(), equalTo("Updated file for integration tests.")); - assertThat("Message already resolved", mockGitHub.getRequestCount(), equalTo(expectedRequestCount)); + assertThat(response.getCommit(), notNullValue()); + assertThat(response.getCommit().getAuthor().getName(), equalTo("John Doe")); + assertThat(response.getCommit().getAuthor().getEmail(), equalTo("john@example.com")); + assertThat(response.getCommit().getCommitter().getName(), equalTo("Service Account")); + assertThat(response.getCommit().getCommitter().getEmail(), equalTo("service@example.com")); + } - ghCommit.populate(); - assertThat("Populate GHCommit", mockGitHub.getRequestCount(), equalTo(expectedRequestCount += 1)); + /** + * Test deleting content with custom author and committer with date via GHContentDeleter. + * + * @throws Exception + * the exception + */ + @SuppressWarnings("deprecation") + @Test + public void testDeleteWithAuthorCommitterAndDate() throws Exception { + GHContent content = repo.getFileContent("ghcontent-ro/a-file-with-content"); + GHContentUpdateResponse response = content.createDelete() + .message("Deleting with custom author and committer") + .branch("main") + .author("John Doe", "john@example.com", new Date(1234567890000L)) + .committer("Service Account", "service@example.com", new Date(1234567890000L)) + .commit(); - expectedRequestCount = checkCommitUserInfo(gitCommit, ghCommit, expectedRequestCount); - assertThat("GHUser already resolved", mockGitHub.getRequestCount(), equalTo(expectedRequestCount)); + assertThat(response.getCommit(), notNullValue()); + assertThat(response.getCommit().getAuthor().getName(), equalTo("John Doe")); + assertThat(response.getCommit().getAuthor().getEmail(), equalTo("john@example.com")); + assertThat(response.getCommit().getAuthoredDate(), equalTo(GitHubClient.parseInstant("2009-02-13T23:31:30Z"))); + assertThat(response.getCommit().getCommitter().getName(), equalTo("Service Account")); + assertThat(response.getCommit().getCommitter().getEmail(), equalTo("service@example.com")); + assertThat(response.getCommit().getCommitDate(), equalTo(GitHubClient.parseInstant("2009-02-13T23:31:30Z"))); + } - expectedRequestCount = checkCommitTree(gitCommit, ghCommit, expectedRequestCount); + /** + * Test get directory content. + * + * @throws Exception + * the exception + */ + @Test + public void testGetDirectoryContent() throws Exception { + List entries = repo.getDirectoryContent("ghcontent-ro/a-dir-with-3-entries"); - return expectedRequestCount; + assertThat(entries.size(), equalTo(3)); } /** - * Check basic commit info. + * Test get directory content trailing slash. * - * @param gitCommit - * the git commit - * @param ghCommit - * the gh commit - * @param expectedRequestCount - * the expected request count - * @return the int + * @throws Exception + * the exception */ - int checkBasicCommitInfo(GitCommit gitCommit, GHCommit ghCommit, int expectedRequestCount) { - assertThat(gitCommit, notNullValue()); - assertThat(gitCommit.getSHA1(), notNullValue()); - assertThat(gitCommit.getUrl().toString(), - endsWith("/repos/hub4j-test-org/GHContentIntegrationTest/git/commits/" + gitCommit.getSHA1())); - assertThat(gitCommit.getNodeId(), notNullValue()); - assertThat(gitCommit.getHtmlUrl().toString(), - equalTo("https://github.com/hub4j-test-org/GHContentIntegrationTest/commit/" + gitCommit.getSHA1())); - assertThat(gitCommit.getVerification(), notNullValue()); - - assertThat(ghCommit.getSHA1(), notNullValue()); - assertThat(ghCommit.getUrl().toString(), - endsWith("/repos/hub4j-test-org/GHContentIntegrationTest/git/commits/" + ghCommit.getSHA1())); + @Test + public void testGetDirectoryContentTrailingSlash() throws Exception { + // Used to truncate the ?ref=main, see gh-224 https://github.com/kohsuke/github-api/pull/224 + List entries = repo.getDirectoryContent("ghcontent-ro/a-dir-with-3-entries/", "main"); - return expectedRequestCount; + assertThat(entries.get(0).getUrl(), endsWith("?ref=main")); } /** - * Check commit user info. + * Test get empty file content. * - * @param gitCommit - * the git commit - * @param ghCommit - * the gh commit - * @param expectedRequestCount - * the expected request count - * @return the int * @throws Exception * the exception */ - int checkCommitUserInfo(GitCommit gitCommit, GHCommit ghCommit, int expectedRequestCount) throws Exception { - assertThat(gitCommit.getAuthor().getName(), equalTo("Liam Newman")); - assertThat(gitCommit.getAuthor().getEmail(), equalTo("bitwiseman@gmail.com")); - - assertThat(gitCommit.getAuthor().getName(), equalTo("Liam Newman")); - assertThat(gitCommit.getAuthor().getEmail(), equalTo("bitwiseman@gmail.com")); - assertThat(gitCommit.getCommitter().getName(), equalTo("Liam Newman")); - assertThat(gitCommit.getCommitter().getEmail(), equalTo("bitwiseman@gmail.com")); - assertThat("GHUser already resolved", mockGitHub.getRequestCount(), equalTo(expectedRequestCount)); + @Test + public void testGetEmptyFileContent() throws Exception { + GHContent content = repo.getFileContent("ghcontent-ro/an-empty-file"); - assertThat(ghCommit.getAuthor().getName(), equalTo("Liam Newman")); - assertThat(ghCommit.getAuthor().getEmail(), equalTo("bitwiseman@gmail.com")); + assertThat(content.isFile(), is(true)); + assertThat(content.getContent(), is(emptyString())); + } - assertThat(ghCommit.getCommitter().getName(), equalTo("Liam Newman")); - assertThat(ghCommit.getCommitter().getEmail(), equalTo("bitwiseman@gmail.com")); + /** + * Test get file content. + * + * @throws Exception + * the exception + */ + @Test + public void testGetFileContent() throws Exception { + repo = gitHub.getRepository("hub4j-test-org/GHContentIntegrationTest"); + GHContent content = repo.getFileContent("ghcontent-ro/a-file-with-content"); - return expectedRequestCount; + assertThat(content.isFile(), is(true)); + assertThat(content.getContent(), equalTo("thanks for reading me\n")); } /** - * Check commit tree. + * Test get file content with non ascii path. * - * @param gitCommit - * the git commit - * @param ghCommit - * the gh commit - * @param expectedRequestCount - * the expected request count - * @return the int - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ - int checkCommitTree(GitCommit gitCommit, GHCommit ghCommit, int expectedRequestCount) throws IOException { - assertThat(gitCommit.getTreeSHA1(), notNullValue()); - assertThat(gitCommit.getTreeUrl(), - endsWith("/repos/hub4j-test-org/GHContentIntegrationTest/git/trees/" + gitCommit.getTree().getSha())); - assertThat("GHTree already resolved", mockGitHub.getRequestCount(), equalTo(expectedRequestCount)); - - assertThat(ghCommit.getTree().getSha(), notNullValue()); - assertThat("GHCommit has to resolve GHTree", mockGitHub.getRequestCount(), equalTo(expectedRequestCount += 1)); - assertThat(ghCommit.getTree().getUrl().toString(), - endsWith("/repos/hub4j-test-org/GHContentIntegrationTest/git/trees/" + ghCommit.getTree().getSha())); - assertThat("GHCommit resolving GHTree is not cached", - mockGitHub.getRequestCount(), - equalTo(expectedRequestCount += 2)); + @Test + public void testGetFileContentWithNonAsciiPath() throws Exception { + final GHRepository repo = gitHub.getRepository("hub4j-test-org/GHContentIntegrationTest"); + final GHContent fileContent = repo.getFileContent("ghcontent-ro/a-file-with-\u00F6"); + assertThat(IOUtils.readLines(fileContent.read(), StandardCharsets.UTF_8), hasItems("test")); - return expectedRequestCount; + final GHContent fileContent2 = repo.getFileContent(fileContent.getPath()); + assertThat(IOUtils.readLines(fileContent2.read(), StandardCharsets.UTF_8), hasItems("test")); } /** - * Check commit parents. + * Test get file content with symlink. * - * @param gitCommit - * the git commit - * @param ghCommit - * the gh commit - * @param expectedRequestCount - * the expected request count - * @return the int + * @throws Exception + * the exception */ - int checkCommitParents(GitCommit gitCommit, GHCommit ghCommit, int expectedRequestCount) { - assertThat(gitCommit.getParentSHA1s().size(), is(greaterThan(0))); - assertThat(gitCommit.getParentSHA1s().get(0), notNullValue()); - assertThat(ghCommit.getParentSHA1s().size(), is(greaterThan(0))); - assertThat(ghCommit.getParentSHA1s().get(0), notNullValue()); + @Test + public void testGetFileContentWithSymlink() throws Exception { + final GHRepository repo = gitHub.getRepository("hub4j-test-org/GHContentIntegrationTest"); - return expectedRequestCount; - } + final GHContent fileContent = repo.getFileContent("ghcontent-ro/a-symlink-to-a-file"); + // for whatever reason GH says this is a file :-o + assertThat(IOUtils.toString(fileContent.read(), StandardCharsets.UTF_8), is("thanks for reading me\n")); - // @Test - // public void testGitCommit2GHCommitExceptions() { + final GHContent dirContent = repo.getFileContent("ghcontent-ro/a-symlink-to-a-dir"); + // but symlinks to directories are symlinks! + assertThat(dirContent, + allOf(hasProperty("target", is("a-dir-with-3-entries")), hasProperty("type", is("symlink")))); - // } + // future somehow... + + // final GHContent fileContent2 = repo.getFileContent("ghcontent-ro/a-symlink-to-a-dir/entry-one"); + // this needs special handling and will 404 from GitHub + // assertThat(IOUtils.toString(fileContent.read(), StandardCharsets.UTF_8), is("")); + } /** - * Test MIME small. + * Test get repository. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void testMIMESmall() throws IOException { - GHRepository ghRepository = getTempRepository(); - GHContentBuilder ghContentBuilder = ghRepository.createContent(); - ghContentBuilder.message("Some commit msg"); - ghContentBuilder.path("MIME-Small.md"); - ghContentBuilder.content("123456789012345678901234567890123456789012345678901234567"); - ghContentBuilder.commit(); + public void testGetRepository() throws Exception { + GHRepository testRepo = gitHub.getRepositoryById(repo.getId()); + assertThat(testRepo.getName(), equalTo(repo.getName())); + } + + /** + * Test get repository created from a template repository + * + * @throws Exception + * the exception + */ + @Test + public void testGetRepositoryWithTemplateRepositoryInfo() throws Exception { + GHRepository testRepo = gitHub.getRepositoryById(repo.getId()); + assertThat(testRepo.getTemplateRepository(), notNullValue()); + assertThat(testRepo.getTemplateRepository().getOwnerName(), equalTo("octocat")); + assertThat(testRepo.getTemplateRepository().isTemplate(), equalTo(true)); } /** @@ -480,45 +408,273 @@ public void testMIMELonger() throws IOException { } /** - * Test get file content with non ascii path. + * Test MIME small. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testMIMESmall() throws IOException { + GHRepository ghRepository = getTempRepository(); + GHContentBuilder ghContentBuilder = ghRepository.createContent(); + ghContentBuilder.message("Some commit msg"); + ghContentBuilder.path("MIME-Small.md"); + ghContentBuilder.content("123456789012345678901234567890123456789012345678901234567"); + ghContentBuilder.commit(); + } + + /** + * Test updating content with custom author and committer via GHContentUpdater. * * @throws Exception * the exception */ @Test - public void testGetFileContentWithNonAsciiPath() throws Exception { - final GHRepository repo = gitHub.getRepository("hub4j-test-org/GHContentIntegrationTest"); - final GHContent fileContent = repo.getFileContent("ghcontent-ro/a-file-with-\u00F6"); - assertThat(IOUtils.readLines(fileContent.read(), StandardCharsets.UTF_8), hasItems("test")); + public void testUpdateWithAuthorCommitter() throws Exception { + GHContent content = repo.getFileContent("ghcontent-ro/a-file-with-content"); + GHContentUpdateResponse response = content.createUpdate() + .content("updated content\n") + .message("Updating with custom author and committer") + .branch("main") + .author("John Doe", "john@example.com") + .committer("Service Account", "service@example.com") + .commit(); - final GHContent fileContent2 = repo.getFileContent(fileContent.getPath()); - assertThat(IOUtils.readLines(fileContent2.read(), StandardCharsets.UTF_8), hasItems("test")); + assertThat(response.getContent(), notNullValue()); + assertThat(response.getCommit(), notNullValue()); + assertThat(response.getCommit().getAuthor().getName(), equalTo("John Doe")); + assertThat(response.getCommit().getAuthor().getEmail(), equalTo("john@example.com")); + assertThat(response.getCommit().getCommitter().getName(), equalTo("Service Account")); + assertThat(response.getCommit().getCommitter().getEmail(), equalTo("service@example.com")); } /** - * Test get file content with symlink. + * Test updating content with custom author and committer with date via GHContentUpdater. * * @throws Exception * the exception */ + @SuppressWarnings("deprecation") @Test - public void testGetFileContentWithSymlink() throws Exception { - final GHRepository repo = gitHub.getRepository("hub4j-test-org/GHContentIntegrationTest"); + public void testUpdateWithAuthorCommitterAndDate() throws Exception { + GHContent content = repo.getFileContent("ghcontent-ro/a-file-with-content"); + GHContentUpdateResponse response = content.createUpdate() + .content("updated content\n") + .message("Updating with custom author and committer") + .branch("main") + .author("John Doe", "john@example.com", new Date(1234567890000L)) + .committer("Service Account", "service@example.com", new Date(1234567890000L)) + .commit(); - final GHContent fileContent = repo.getFileContent("ghcontent-ro/a-symlink-to-a-file"); - // for whatever reason GH says this is a file :-o - assertThat(IOUtils.toString(fileContent.read(), StandardCharsets.UTF_8), is("thanks for reading me\n")); + assertThat(response.getContent(), notNullValue()); + assertThat(response.getCommit(), notNullValue()); + assertThat(response.getCommit().getAuthor().getName(), equalTo("John Doe")); + assertThat(response.getCommit().getAuthor().getEmail(), equalTo("john@example.com")); + assertThat(response.getCommit().getAuthoredDate(), equalTo(GitHubClient.parseInstant("2009-02-13T23:31:30Z"))); + assertThat(response.getCommit().getCommitter().getName(), equalTo("Service Account")); + assertThat(response.getCommit().getCommitter().getEmail(), equalTo("service@example.com")); + assertThat(response.getCommit().getCommitDate(), equalTo(GitHubClient.parseInstant("2009-02-13T23:31:30Z"))); + } - final GHContent dirContent = repo.getFileContent("ghcontent-ro/a-symlink-to-a-dir"); - // but symlinks to directories are symlinks! - assertThat(dirContent, - allOf(hasProperty("target", is("a-dir-with-3-entries")), hasProperty("type", is("symlink")))); + /** + * Check basic commit info. + * + * @param gitCommit + * the git commit + * @param ghCommit + * the gh commit + * @param expectedRequestCount + * the expected request count + * @return the int + */ + int checkBasicCommitInfo(GitCommit gitCommit, GHCommit ghCommit, int expectedRequestCount) { + assertThat(gitCommit, notNullValue()); + assertThat(gitCommit.getSHA1(), notNullValue()); + assertThat(gitCommit.getUrl().toString(), + endsWith("/repos/hub4j-test-org/GHContentIntegrationTest/git/commits/" + gitCommit.getSHA1())); + assertThat(gitCommit.getNodeId(), notNullValue()); + assertThat(gitCommit.getHtmlUrl().toString(), + equalTo("https://github.com/hub4j-test-org/GHContentIntegrationTest/commit/" + gitCommit.getSHA1())); + assertThat(gitCommit.getVerification(), notNullValue()); - // future somehow... + assertThat(ghCommit.getSHA1(), notNullValue()); + assertThat(ghCommit.getUrl().toString(), + endsWith("/repos/hub4j-test-org/GHContentIntegrationTest/git/commits/" + ghCommit.getSHA1())); - // final GHContent fileContent2 = repo.getFileContent("ghcontent-ro/a-symlink-to-a-dir/entry-one"); - // this needs special handling and will 404 from GitHub - // assertThat(IOUtils.toString(fileContent.read(), StandardCharsets.UTF_8), is("")); + return expectedRequestCount; + } + + /** + * Check commit parents. + * + * @param gitCommit + * the git commit + * @param ghCommit + * the gh commit + * @param expectedRequestCount + * the expected request count + * @return the int + */ + int checkCommitParents(GitCommit gitCommit, GHCommit ghCommit, int expectedRequestCount) { + assertThat(gitCommit.getParentSHA1s().size(), is(greaterThan(0))); + assertThat(gitCommit.getParentSHA1s().get(0), notNullValue()); + assertThat(ghCommit.getParentSHA1s().size(), is(greaterThan(0))); + assertThat(ghCommit.getParentSHA1s().get(0), notNullValue()); + + return expectedRequestCount; + } + + // @Test + // public void testGitCommit2GHCommitExceptions() { + + // } + + /** + * Check commit tree. + * + * @param gitCommit + * the git commit + * @param ghCommit + * the gh commit + * @param expectedRequestCount + * the expected request count + * @return the int + * @throws IOException + * Signals that an I/O exception has occurred. + */ + int checkCommitTree(GitCommit gitCommit, GHCommit ghCommit, int expectedRequestCount) throws IOException { + assertThat(gitCommit.getTreeSHA1(), notNullValue()); + assertThat(gitCommit.getTreeUrl(), + endsWith("/repos/hub4j-test-org/GHContentIntegrationTest/git/trees/" + gitCommit.getTree().getSha())); + assertThat("GHTree already resolved", mockGitHub.getRequestCount(), equalTo(expectedRequestCount)); + + assertThat(ghCommit.getTree().getSha(), notNullValue()); + assertThat("GHCommit has to resolve GHTree", mockGitHub.getRequestCount(), equalTo(expectedRequestCount += 1)); + assertThat(ghCommit.getTree().getUrl().toString(), + endsWith("/repos/hub4j-test-org/GHContentIntegrationTest/git/trees/" + ghCommit.getTree().getSha())); + assertThat("GHCommit resolving GHTree is not cached", + mockGitHub.getRequestCount(), + equalTo(expectedRequestCount += 2)); + + return expectedRequestCount; + } + + /** + * Check commit user info. + * + * @param gitCommit + * the git commit + * @param ghCommit + * the gh commit + * @param expectedRequestCount + * the expected request count + * @return the int + * @throws Exception + * the exception + */ + int checkCommitUserInfo(GitCommit gitCommit, GHCommit ghCommit, int expectedRequestCount) throws Exception { + assertThat(gitCommit.getAuthor().getName(), equalTo("Liam Newman")); + assertThat(gitCommit.getAuthor().getEmail(), equalTo("bitwiseman@gmail.com")); + + assertThat(gitCommit.getAuthor().getName(), equalTo("Liam Newman")); + assertThat(gitCommit.getAuthor().getEmail(), equalTo("bitwiseman@gmail.com")); + assertThat(gitCommit.getCommitter().getName(), equalTo("Liam Newman")); + assertThat(gitCommit.getCommitter().getEmail(), equalTo("bitwiseman@gmail.com")); + assertThat("GHUser already resolved", mockGitHub.getRequestCount(), equalTo(expectedRequestCount)); + + assertThat(ghCommit.getAuthor().getName(), equalTo("Liam Newman")); + assertThat(ghCommit.getAuthor().getEmail(), equalTo("bitwiseman@gmail.com")); + + assertThat(ghCommit.getCommitter().getName(), equalTo("Liam Newman")); + assertThat(ghCommit.getCommitter().getEmail(), equalTo("bitwiseman@gmail.com")); + + return expectedRequestCount; + } + + /** + * Check created commits. + * + * @param gitCommit + * the git commit + * @param ghCommit + * the gh commit + * @param expectedRequestCount + * the expected request count + * @return the int + * @throws Exception + * the exception + */ + int checkCreatedCommits(GitCommit gitCommit, GHCommit ghCommit, int expectedRequestCount) throws Exception { + expectedRequestCount = checkBasicCommitInfo(gitCommit, ghCommit, expectedRequestCount); + assertThat(mockGitHub.getRequestCount(), equalTo(expectedRequestCount)); + + assertThat(gitCommit.getMessage(), equalTo("Creating a file for integration tests.")); + assertThat(gitCommit.getAuthoredDate(), equalTo(GitHubClient.parseInstant("2021-06-28T20:37:49Z"))); + assertThat(gitCommit.getCommitDate(), equalTo(GitHubClient.parseInstant("2021-06-28T20:37:49Z"))); + + assertThat(ghCommit.getCommitShortInfo().getMessage(), equalTo("Creating a file for integration tests.")); + assertThat("Message already resolved", mockGitHub.getRequestCount(), equalTo(expectedRequestCount)); + assertThrows(GHException.class, () -> ghCommit.getCommitShortInfo().getCommentCount()); + + ghCommit.populate(); + assertThat("Populate GHCommit", mockGitHub.getRequestCount(), equalTo(expectedRequestCount += 1)); + + expectedRequestCount = checkCommitUserInfo(gitCommit, ghCommit, expectedRequestCount); + assertThat("Resolved GHUser for GHCommit", mockGitHub.getRequestCount(), equalTo(expectedRequestCount += 1)); + + expectedRequestCount = checkCommitTree(gitCommit, ghCommit, expectedRequestCount); + + expectedRequestCount = checkCommitParents(gitCommit, ghCommit, expectedRequestCount); + + return expectedRequestCount; + } + + /** + * Check updated content response commits. + * + * @param gitCommit + * the git commit + * @param ghCommit + * the gh commit + * @param expectedRequestCount + * the expected request count + * @return the int + * @throws Exception + * the exception + */ + int checkUpdatedContentResponseCommits(GitCommit gitCommit, GHCommit ghCommit, int expectedRequestCount) + throws Exception { + + expectedRequestCount = checkBasicCommitInfo(gitCommit, ghCommit, expectedRequestCount); + assertThat(mockGitHub.getRequestCount(), equalTo(expectedRequestCount)); + + assertThat(gitCommit.getMessage(), equalTo("Updated file for integration tests.")); + assertThat(gitCommit.getAuthoredDate(), equalTo(GitHubClient.parseInstant("2021-06-28T20:37:51Z"))); + assertThat(gitCommit.getCommitDate(), equalTo(GitHubClient.parseInstant("2021-06-28T20:37:51Z"))); + + assertThat(ghCommit.getCommitShortInfo().getMessage(), equalTo("Updated file for integration tests.")); + assertThat("Message already resolved", mockGitHub.getRequestCount(), equalTo(expectedRequestCount)); + + ghCommit.populate(); + assertThat("Populate GHCommit", mockGitHub.getRequestCount(), equalTo(expectedRequestCount += 1)); + + expectedRequestCount = checkCommitUserInfo(gitCommit, ghCommit, expectedRequestCount); + assertThat("GHUser already resolved", mockGitHub.getRequestCount(), equalTo(expectedRequestCount)); + + expectedRequestCount = checkCommitTree(gitCommit, ghCommit, expectedRequestCount); + + return expectedRequestCount; + } + + /** + * Gets the GH commit. + * + * @param resp + * the resp + * @return the GH commit + */ + GHCommit getGHCommit(GHContentUpdateResponse resp) { + return resp.getCommit().toGHCommit(); } } diff --git a/src/test/java/org/kohsuke/github/GHDeployKeyTest.java b/src/test/java/org/kohsuke/github/GHDeployKeyTest.java index bc3bb229ab..19918e2130 100644 --- a/src/test/java/org/kohsuke/github/GHDeployKeyTest.java +++ b/src/test/java/org/kohsuke/github/GHDeployKeyTest.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.time.Instant; -import java.util.Date; import java.util.List; import java.util.Optional; @@ -18,17 +17,17 @@ */ public class GHDeployKeyTest extends AbstractGitHubWireMockTest { + private static final String DEPLOY_KEY_TEST_REPO_NAME = "hub4j-test-org/GHDeployKeyTest"; + + private static final String ED_25519_READONLY = "DeployKey - ed25519 - readonly"; + private static final String KEY_CREATOR_USERNAME = "van-vliet"; + private static final String RSA_4096_READWRITE = "Deploykey - rsa4096 - readwrite"; /** * Create default GHDeployKeyTest instance */ public GHDeployKeyTest() { } - private static final String DEPLOY_KEY_TEST_REPO_NAME = "hub4j-test-org/GHDeployKeyTest"; - private static final String ED_25519_READONLY = "DeployKey - ed25519 - readonly"; - private static final String RSA_4096_READWRITE = "Deploykey - rsa4096 - readwrite"; - private static final String KEY_CREATOR_USERNAME = "van-vliet"; - /** * Test get deploymentkeys. * @@ -47,13 +46,13 @@ public void testGetDeployKeys() throws IOException { assertThat("The key exists", ed25519Key, isPresent()); assertThat("The key was created at the specified date", ed25519Key.get().getCreatedAt(), - is(Date.from(Instant.parse("2023-02-08T10:00:15.00Z")))); + is(Instant.parse("2023-02-08T10:00:15.00Z"))); assertThat("The key is created by " + KEY_CREATOR_USERNAME, ed25519Key.get().getAdded_by(), is(KEY_CREATOR_USERNAME)); assertThat("The key has a last_used value", ed25519Key.get().getLastUsedAt(), - is(Date.from(Instant.parse("2023-02-08T10:02:11.00Z")))); + is(Instant.parse("2023-02-08T10:02:11.00Z"))); assertThat("The key only has read access", ed25519Key.get().isRead_only(), is(true)); assertThat("Object has a toString()", ed25519Key.get().toString(), is(notNullValue())); @@ -63,7 +62,7 @@ public void testGetDeployKeys() throws IOException { assertThat("The key exists", rsa_4096Key, isPresent()); assertThat("The key was created at the specified date", rsa_4096Key.get().getCreatedAt(), - is(Date.from(Instant.parse("2023-01-26T14:12:12.00Z")))); + is(Instant.parse("2023-01-26T14:12:12.00Z"))); assertThat("The key is created by " + KEY_CREATOR_USERNAME, rsa_4096Key.get().getAdded_by(), is(KEY_CREATOR_USERNAME)); @@ -71,6 +70,10 @@ public void testGetDeployKeys() throws IOException { assertThat("The key only has read/write access", rsa_4096Key.get().isRead_only(), is(false)); } + private GHRepository getRepository(final GitHub gitHub) throws IOException { + return gitHub.getRepository(DEPLOY_KEY_TEST_REPO_NAME); + } + /** * Gets the repository. * @@ -81,8 +84,4 @@ public void testGetDeployKeys() throws IOException { protected GHRepository getRepository() throws IOException { return getRepository(gitHub); } - - private GHRepository getRepository(final GitHub gitHub) throws IOException { - return gitHub.getRepository(DEPLOY_KEY_TEST_REPO_NAME); - } } diff --git a/src/test/java/org/kohsuke/github/GHDeploymentTest.java b/src/test/java/org/kohsuke/github/GHDeploymentTest.java index 56d8caa634..32e588bcc8 100644 --- a/src/test/java/org/kohsuke/github/GHDeploymentTest.java +++ b/src/test/java/org/kohsuke/github/GHDeploymentTest.java @@ -23,53 +23,60 @@ public GHDeploymentTest() { } /** - * Test get deployment by id string payload. + * Test get deployment by id object payload. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testGetDeploymentByIdStringPayload() throws IOException { + public void testGetDeploymentByIdObjectPayload() throws IOException { final GHRepository repo = getRepository(); final GHDeployment deployment = repo.getDeployment(178653229); assertThat(deployment, notNullValue()); assertThat(deployment.getId(), equalTo(178653229L)); assertThat(deployment.getEnvironment(), equalTo("production")); - assertThat(deployment.getPayload(), equalTo("custom")); - assertThat(deployment.getPayloadObject(), equalTo("custom")); assertThat(deployment.getRef(), equalTo("main")); assertThat(deployment.getSha(), equalTo("3a09d2de4a9a1322a0ba2c3e2f54a919ca8fe353")); assertThat(deployment.getTask(), equalTo("deploy")); + final Map payload = deployment.getPayloadMap(); + assertThat(payload.size(), equalTo(4)); + assertThat(payload.get("custom1"), equalTo(1)); + assertThat(payload.get("custom2"), equalTo("two")); + assertThat(payload.get("custom3"), equalTo(Arrays.asList("3", 3, "three"))); + assertThat(payload.get("custom4"), nullValue()); assertThat(deployment.getOriginalEnvironment(), equalTo("production")); assertThat(deployment.isProductionEnvironment(), equalTo(false)); assertThat(deployment.isTransientEnvironment(), equalTo(true)); } /** - * Test get deployment by id object payload. + * Test get deployment by id string payload. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testGetDeploymentByIdObjectPayload() throws IOException { + public void testGetDeploymentByIdStringPayload() throws IOException { final GHRepository repo = getRepository(); final GHDeployment deployment = repo.getDeployment(178653229); assertThat(deployment, notNullValue()); assertThat(deployment.getId(), equalTo(178653229L)); assertThat(deployment.getEnvironment(), equalTo("production")); + assertThat(deployment.getPayload(), equalTo("custom")); + assertThat(deployment.getPayloadObject(), equalTo("custom")); assertThat(deployment.getRef(), equalTo("main")); assertThat(deployment.getSha(), equalTo("3a09d2de4a9a1322a0ba2c3e2f54a919ca8fe353")); assertThat(deployment.getTask(), equalTo("deploy")); - final Map payload = deployment.getPayloadMap(); - assertThat(payload.size(), equalTo(4)); - assertThat(payload.get("custom1"), equalTo(1)); - assertThat(payload.get("custom2"), equalTo("two")); - assertThat(payload.get("custom3"), equalTo(Arrays.asList("3", 3, "three"))); - assertThat(payload.get("custom4"), nullValue()); assertThat(deployment.getOriginalEnvironment(), equalTo("production")); assertThat(deployment.isProductionEnvironment(), equalTo(false)); assertThat(deployment.isTransientEnvironment(), equalTo(true)); + assertThat(deployment.getStatusesUrl().toString(), + endsWith("/repos/hub4j-test-org/github-api/deployments/178653229/statuses")); + assertThat(deployment.getRepositoryUrl().toString(), endsWith("/repos/hub4j-test-org/github-api")); + } + + private GHRepository getRepository(final GitHub gitHub) throws IOException { + return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); } /** @@ -82,8 +89,4 @@ public void testGetDeploymentByIdObjectPayload() throws IOException { protected GHRepository getRepository() throws IOException { return getRepository(gitHub); } - - private GHRepository getRepository(final GitHub gitHub) throws IOException { - return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); - } } diff --git a/src/test/java/org/kohsuke/github/GHDiscussionTest.java b/src/test/java/org/kohsuke/github/GHDiscussionTest.java index a2b68db1b5..2a713647df 100644 --- a/src/test/java/org/kohsuke/github/GHDiscussionTest.java +++ b/src/test/java/org/kohsuke/github/GHDiscussionTest.java @@ -18,24 +18,13 @@ */ public class GHDiscussionTest extends AbstractGitHubWireMockTest { - /** - * Create default GHDiscussionTest instance - */ - public GHDiscussionTest() { - } - private final String TEAM_SLUG = "dummy-team"; - private GHTeam team; + private GHTeam team; /** - * Sets the up. - * - * @throws Exception - * the exception + * Create default GHDiscussionTest instance */ - @Before - public void setUp() throws Exception { - team = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(TEAM_SLUG); + public GHDiscussionTest() { } /** @@ -56,6 +45,17 @@ public void cleanupDiscussions() throws Exception { } } + /** + * Sets the up. + * + * @throws Exception + * the exception + */ + @Before + public void setUp() throws Exception { + team = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(TEAM_SLUG); + } + /** * Test created discussion. * diff --git a/src/test/java/org/kohsuke/github/GHEventPayloadTest.java b/src/test/java/org/kohsuke/github/GHEventPayloadTest.java index 638e60f18b..aa0186075e 100644 --- a/src/test/java/org/kohsuke/github/GHEventPayloadTest.java +++ b/src/test/java/org/kohsuke/github/GHEventPayloadTest.java @@ -9,10 +9,8 @@ import org.kohsuke.github.GHTeam.Privacy; import java.io.IOException; -import java.text.SimpleDateFormat; import java.util.Collections; import java.util.List; -import java.util.TimeZone; import static org.hamcrest.Matchers.aMapWithSize; import static org.hamcrest.Matchers.contains; @@ -46,119 +44,163 @@ public GHEventPayloadTest() { } /** - * Commit comment. + * Installation event. * * @throws Exception * the exception */ @Test - public void commit_comment() throws Exception { - final GHEventPayload.CommitComment event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.CommitComment.class); + @Payload("installation_created") + public void InstallationCreatedEvent() throws Exception { + final GHEventPayload.Installation event = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()) + .build() + .parseEventPayload(payload.asReader(), GHEventPayload.Installation.class); + assertThat(event.getAction(), is("created")); - assertThat(event.getComment().getSHA1(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); - assertThat(event.getComment().getUser().getLogin(), is("baxterthehacker")); - assertThat(event.getRepository().getName(), is("public-repo")); - assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); - assertThat(event.getSender().getLogin(), is("baxterthehacker")); + assertThat(event.getInstallation().getId(), is(43898337L)); + assertThat(event.getInstallation().getAccount().getLogin(), is("CronFire")); - assertThat(event.getComment().getOwner(), sameInstance(event.getRepository())); + assertThat(event.getRepositories().isEmpty(), is(false)); + assertThat(event.getRepositories().get(0).getId(), is(1296269L)); + assertThat(event.getRawRepositories().isEmpty(), is(false)); + assertThat(event.getRawRepositories().get(0).getId(), is(1296269L)); + + assertThat(event.getSender().getLogin(), is("Haarolean")); } /** - * Creates the. + * Installation event. * * @throws Exception * the exception */ @Test - public void create() throws Exception { - final GHEventPayload.Create event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Create.class); - assertThat(event.getRef(), is("0.0.1")); - assertThat(event.getRefType(), is("tag")); - assertThat(event.getMasterBranch(), is("main")); - assertThat(event.getDescription(), is("")); - assertThat(event.getRepository().getName(), is("public-repo")); - assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); - assertThat(event.getSender().getLogin(), is("baxterthehacker")); + @Payload("installation_deleted") + public void InstallationDeletedEvent() throws Exception { + final GHEventPayload.Installation event = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()) + .build() + .parseEventPayload(payload.asReader(), GHEventPayload.Installation.class); + + assertThat(event.getAction(), is("deleted")); + assertThat(event.getInstallation().getId(), is(2L)); + assertThat(event.getInstallation().getAccount().getLogin(), is("octocat")); + + assertThrows(IllegalStateException.class, () -> event.getRepositories().isEmpty()); + assertThat(event.getRawRepositories().isEmpty(), is(false)); + assertThat(event.getRawRepositories().get(0).getId(), is(1296269L)); + assertThat(event.getRawRepositories().get(0).getNodeId(), is("MDEwOlJlcG9zaXRvcnkxMjk2MjY5")); + assertThat(event.getRawRepositories().get(0).getName(), is("Hello-World")); + assertThat(event.getRawRepositories().get(0).getFullName(), is("octocat/Hello-World")); + assertThat(event.getRawRepositories().get(0).isPrivate(), is(false)); + + assertThat(event.getSender().getLogin(), is("octocat")); } /** - * Delete. + * Installation repositories event. * * @throws Exception * the exception */ @Test - public void delete() throws Exception { - final GHEventPayload.Delete event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Delete.class); - assertThat(event.getRef(), is("simple-tag")); - assertThat(event.getRefType(), is("tag")); - assertThat(event.getRepository().getName(), is("public-repo")); - assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); - assertThat(event.getSender().getLogin(), is("baxterthehacker")); + @Payload("installation_repositories") + public void InstallationRepositoriesEvent() throws Exception { + final GHEventPayload.InstallationRepositories event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.InstallationRepositories.class); + + assertThat(event.getAction(), is("added")); + assertThat(event.getInstallation().getId(), is(957387L)); + assertThat(event.getInstallation().getAccount().getLogin(), is("Codertocat")); + assertThat(event.getRepositorySelection(), is("selected")); + + assertThat(event.getRepositoriesAdded().get(0).getId(), is(186853007L)); + assertThat(event.getRepositoriesAdded().get(0).getNodeId(), is("MDEwOlJlcG9zaXRvcnkxODY4NTMwMDc=")); + assertThat(event.getRepositoriesAdded().get(0).getName(), is("Space")); + assertThat(event.getRepositoriesAdded().get(0).getFullName(), is("Codertocat/Space")); + assertThat(event.getRepositoriesAdded().get(0).isPrivate(), is(false)); + + assertThat(event.getRepositoriesRemoved(), is(Collections.emptyList())); + assertThat(event.getSender().getLogin(), is("Codertocat")); } /** - * Deployment. + * Check run event. * * @throws Exception * the exception */ @Test - public void deployment() throws Exception { - final GHEventPayload.Deployment event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Deployment.class); - assertThat(event.getDeployment().getSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); - assertThat(event.getDeployment().getEnvironment(), is("production")); - assertThat(event.getDeployment().getCreator().getLogin(), is("baxterthehacker")); - assertThat(event.getRepository().getName(), is("public-repo")); - assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); - assertThat(event.getSender().getLogin(), is("baxterthehacker")); + @Payload("check-run") + public void checkRunEvent() throws Exception { + final GHEventPayload.CheckRun event = GitHub.offline() + .parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), GHEventPayload.CheckRun.class); + final GHCheckRun checkRun = verifyBasicCheckRunEvent(event); + assertThat("pull body not populated offline", checkRun.getPullRequests().get(0).getBody(), nullValue()); + assertThat("using offline github", mockGitHub.getRequestCount(), equalTo(0)); + assertThat(checkRun.getPullRequests().get(0).getRepository(), sameInstance(event.getRepository())); - assertThat(event.getDeployment().getOwner(), sameInstance(event.getRepository())); + gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()).build(); + final GHEventPayload.CheckRun event2 = gitHub.parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), + GHEventPayload.CheckRun.class); + final GHCheckRun checkRun2 = verifyBasicCheckRunEvent(event2); + + int expectedRequestCount = 2; + assertThat("pull body should be populated", + checkRun2.getPullRequests().get(0).getBody(), + equalTo("This is a pretty simple change that we need to pull into main.")); + assertThat("multiple getPullRequests() calls are made, the pull is populated only once", + mockGitHub.getRequestCount(), + equalTo(expectedRequestCount)); } /** - * Deployment status. + * Check suite event. * * @throws Exception * the exception */ @Test - public void deployment_status() throws Exception { - final GHEventPayload.DeploymentStatus event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.DeploymentStatus.class); - assertThat(event.getDeploymentStatus().getState(), is(GHDeploymentState.SUCCESS)); - assertThat(event.getDeploymentStatus().getLogUrl(), nullValue()); - assertThat(event.getDeployment().getSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); - assertThat(event.getDeployment().getEnvironment(), is("production")); - assertThat(event.getDeployment().getCreator().getLogin(), is("baxterthehacker")); - assertThat(event.getRepository().getName(), is("public-repo")); - assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); - assertThat(event.getSender().getLogin(), is("baxterthehacker")); + @Payload("check-suite") + public void checkSuiteEvent() throws Exception { + final GHEventPayload.CheckSuite event = GitHub.offline() + .parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), GHEventPayload.CheckSuite.class); + final GHCheckSuite checkSuite = verifyBasicCheckSuiteEvent(event); + assertThat("pull body not populated offline", checkSuite.getPullRequests().get(0).getBody(), nullValue()); + assertThat("using offline github", mockGitHub.getRequestCount(), equalTo(0)); + assertThat(checkSuite.getPullRequests().get(0).getRepository(), sameInstance(event.getRepository())); - assertThat(event.getDeployment().getOwner(), sameInstance(event.getRepository())); - assertThat(event.getDeploymentStatus().getOwner(), sameInstance(event.getRepository())); + gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()).build(); + final GHEventPayload.CheckSuite event2 = gitHub.parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), + GHEventPayload.CheckSuite.class); + final GHCheckSuite checkSuite2 = verifyBasicCheckSuiteEvent(event2); + + int expectedRequestCount = mockGitHub.isUseProxy() ? 3 : 2; + assertThat("pull body should be populated", + checkSuite2.getPullRequests().get(0).getBody(), + equalTo("This is a pretty simple change that we need to pull into main.")); + assertThat("multiple getPullRequests() calls are made, the pull is populated only once", + mockGitHub.getRequestCount(), + lessThanOrEqualTo(expectedRequestCount)); } /** - * Fork. + * Commit comment. * * @throws Exception * the exception */ @Test - public void fork() throws Exception { - final GHEventPayload.Fork event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Fork.class); - assertThat(event.getForkee().getName(), is("public-repo")); - assertThat(event.getForkee().getOwner().getLogin(), is("baxterandthehackers")); + public void commit_comment() throws Exception { + final GHEventPayload.CommitComment event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.CommitComment.class); + assertThat(event.getAction(), is("created")); + assertThat(event.getComment().getSHA1(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); + assertThat(event.getComment().getUser().getLogin(), is("baxterthehacker")); assertThat(event.getRepository().getName(), is("public-repo")); assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); - assertThat(event.getSender().getLogin(), is("baxterandthehackers")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); + + assertThat(event.getComment().getOwner(), sameInstance(event.getRepository())); } // TODO uncomment when we have GHPage implemented @@ -180,134 +222,261 @@ public void fork() throws Exception { // } /** - * Issue comment. + * Creates the. * * @throws Exception * the exception */ @Test - public void issue_comment() throws Exception { - final GHEventPayload.IssueComment event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.IssueComment.class); - assertThat(event.getAction(), is("created")); - assertThat(event.getIssue().getNumber(), is(2)); - assertThat(event.getIssue().getTitle(), is("Spelling error in the README file")); - assertThat(event.getIssue().getState(), is(GHIssueState.OPEN)); - assertThat(event.getIssue().getLabels().size(), is(1)); - assertThat(event.getIssue().getLabels().iterator().next().getName(), is("bug")); - assertThat(event.getComment().getUser().getLogin(), is("baxterthehacker")); - assertThat(event.getComment().getBody(), is("You are totally right! I'll get this fixed right away.")); + public void create() throws Exception { + final GHEventPayload.Create event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Create.class); + assertThat(event.getRef(), is("0.0.1")); + assertThat(event.getRefType(), is("tag")); + assertThat(event.getMasterBranch(), is("main")); + assertThat(event.getDescription(), is("")); assertThat(event.getRepository().getName(), is("public-repo")); assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); assertThat(event.getSender().getLogin(), is("baxterthehacker")); - - assertThat(event.getIssue().getRepository(), sameInstance(event.getRepository())); - assertThat(event.getComment().getParent(), sameInstance(event.getIssue())); } /** - * Issue comment edited. + * Delete. * * @throws Exception * the exception */ @Test - public void issue_comment_edited() throws Exception { - final GHEventPayload.IssueComment event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.IssueComment.class); - assertThat(event.getAction(), is("edited")); - assertThat(event.getComment().getBody(), is("This is the issue comment AFTER edit.")); - assertThat(event.getChanges().getBody().getFrom(), is("This is the issue comment BEFORE edit.")); + public void delete() throws Exception { + final GHEventPayload.Delete event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Delete.class); + assertThat(event.getRef(), is("simple-tag")); + assertThat(event.getRefType(), is("tag")); + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); } /** - * Issues. + * Deployment. * * @throws Exception * the exception */ @Test - public void issues() throws Exception { - final GHEventPayload.Issue event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Issue.class); - assertThat(event.getAction(), is("opened")); - assertThat(event.getIssue().getNumber(), is(2)); - assertThat(event.getIssue().getTitle(), is("Spelling error in the README file")); - assertThat(event.getIssue().getState(), is(GHIssueState.OPEN)); - assertThat(event.getIssue().getLabels().size(), is(1)); - assertThat(event.getIssue().getLabels().iterator().next().getName(), is("bug")); + public void deployment() throws Exception { + final GHEventPayload.Deployment event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Deployment.class); + assertThat(event.getDeployment().getSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); + assertThat(event.getDeployment().getEnvironment(), is("production")); + assertThat(event.getDeployment().getCreator().getLogin(), is("baxterthehacker")); assertThat(event.getRepository().getName(), is("public-repo")); assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); assertThat(event.getSender().getLogin(), is("baxterthehacker")); - assertThat(event.getIssue().getRepository(), sameInstance(event.getRepository())); + assertThat(event.getDeployment().getOwner(), sameInstance(event.getRepository())); } /** - * Issue labeled. + * Deployment status. * * @throws Exception * the exception */ @Test - public void issue_labeled() throws Exception { - final GHEventPayload.Issue event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Issue.class); - assertThat(event.getAction(), is("labeled")); - assertThat(event.getIssue().getNumber(), is(42)); - assertThat(event.getIssue().getTitle(), is("Test GHEventPayload.Issue label/unlabel")); - assertThat(event.getIssue().getLabels().size(), is(1)); - assertThat(event.getIssue().getLabels().iterator().next().getName(), is("enhancement")); - assertThat(event.getLabel().getName(), is("enhancement")); + public void deployment_status() throws Exception { + final GHEventPayload.DeploymentStatus event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.DeploymentStatus.class); + assertThat(event.getDeploymentStatus().getState(), is(GHDeploymentState.SUCCESS)); + assertThat(event.getDeploymentStatus().getLogUrl(), nullValue()); + assertThat(event.getDeployment().getSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); + assertThat(event.getDeployment().getEnvironment(), is("production")); + assertThat(event.getDeployment().getCreator().getLogin(), is("baxterthehacker")); + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); + + assertThat(event.getDeployment().getOwner(), sameInstance(event.getRepository())); + assertThat(event.getDeploymentStatus().getOwner(), sameInstance(event.getRepository())); } /** - * Issue unlabeled. + * Discussion answered. * * @throws Exception * the exception */ @Test - public void issue_unlabeled() throws Exception { - final GHEventPayload.Issue event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Issue.class); - assertThat(event.getAction(), is("unlabeled")); - assertThat(event.getIssue().getNumber(), is(42)); - assertThat(event.getIssue().getTitle(), is("Test GHEventPayload.Issue label/unlabel")); - assertThat(event.getIssue().getLabels().size(), is(0)); - assertThat(event.getLabel().getName(), is("enhancement")); + public void discussion_answered() throws Exception { + final GHEventPayload.Discussion discussionPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Discussion.class); + + assertThat(discussionPayload.getAction(), is("answered")); + assertThat(discussionPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); + assertThat(discussionPayload.getSender().getLogin(), is("gsmet")); + + GHRepositoryDiscussion discussion = discussionPayload.getDiscussion(); + + GHRepositoryDiscussion.Category category = discussion.getCategory(); + + assertThat(category.getId(), is(33522033L)); + assertThat(category.getNodeId(), is("DIC_kwDOEq3cwc4B_4Fx")); + assertThat(category.getEmoji(), is(":pray:")); + assertThat(category.getName(), is("Q&A")); + assertThat(category.getDescription(), is("Ask the community for help")); + assertThat(category.getCreatedAt().toEpochMilli(), is(1636991431000L)); + assertThat(category.getUpdatedAt().toEpochMilli(), is(1636991431000L)); + assertThat(category.getSlug(), is("q-a")); + assertThat(category.isAnswerable(), is(true)); + + assertThat(discussion.getAnswerHtmlUrl().toString(), + is("https://github.com/gsmet/quarkus-bot-java-playground/discussions/78#discussioncomment-1681242")); + assertThat(discussion.getAnswerChosenAt().toEpochMilli(), is(1637585047000L)); + assertThat(discussion.getAnswerChosenBy().getLogin(), is("gsmet")); + + assertThat(discussion.getHtmlUrl().toString(), + is("https://github.com/gsmet/quarkus-bot-java-playground/discussions/78")); + assertThat(discussion.getId(), is(3698909L)); + assertThat(discussion.getNodeId(), is("D_kwDOEq3cwc4AOHDd")); + assertThat(discussion.getNumber(), is(78)); + assertThat(discussion.getTitle(), is("Title of discussion")); + + assertThat(discussion.getUser().getLogin(), is("gsmet")); + assertThat(discussion.getUser().getId(), is(1279749L)); + assertThat(discussion.getUser().getNodeId(), is("MDQ6VXNlcjEyNzk3NDk=")); + + assertThat(discussion.getState(), is(GHRepositoryDiscussion.State.OPEN)); + assertThat(discussion.isLocked(), is(false)); + assertThat(discussion.getComments(), is(1)); + assertThat(discussion.getCreatedAt().toEpochMilli(), is(1637584949000L)); + assertThat(discussion.getUpdatedAt().toEpochMilli(), is(1637585047000L)); + assertThat(discussion.getAuthorAssociation(), is(GHCommentAuthorAssociation.OWNER)); + assertThat(discussion.getActiveLockReason(), is(nullValue())); + assertThat(discussion.getBody(), is("Body of discussion.")); } /** - * Issue title edited. + * Discussion comment created. * * @throws Exception * the exception */ @Test - public void issue_title_edited() throws Exception { - final GHEventPayload.Issue event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Issue.class); - assertThat(event.getAction(), is("edited")); - assertThat(event.getIssue().getNumber(), is(43)); - assertThat(event.getIssue().getTitle(), is("Test GHEventPayload.Issue changes [updated]")); - assertThat(event.getChanges().getTitle().getFrom(), is("Test GHEventPayload.Issue changes")); + public void discussion_comment_created() throws Exception { + final GHEventPayload.DiscussionComment discussionCommentPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.DiscussionComment.class); + + assertThat(discussionCommentPayload.getAction(), is("created")); + assertThat(discussionCommentPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); + assertThat(discussionCommentPayload.getSender().getLogin(), is("gsmet")); + + GHRepositoryDiscussion discussion = discussionCommentPayload.getDiscussion(); + + GHRepositoryDiscussion.Category category = discussion.getCategory(); + + assertThat(category.getId(), is(33522033L)); + assertThat(category.getNodeId(), is("DIC_kwDOEq3cwc4B_4Fx")); + assertThat(category.getEmoji(), is(":pray:")); + assertThat(category.getName(), is("Q&A")); + assertThat(category.getDescription(), is("Ask the community for help")); + assertThat(category.getCreatedAt().toEpochMilli(), is(1636991431000L)); + assertThat(category.getUpdatedAt().toEpochMilli(), is(1636991431000L)); + assertThat(category.getSlug(), is("q-a")); + assertThat(category.isAnswerable(), is(true)); + + assertThat(discussion.getAnswerHtmlUrl(), is(nullValue())); + assertThat(discussion.getAnswerChosenAt(), is(nullValue())); + assertThat(discussion.getAnswerChosenBy(), is(nullValue())); + + assertThat(discussion.getHtmlUrl().toString(), + is("https://github.com/gsmet/quarkus-bot-java-playground/discussions/162")); + assertThat(discussion.getId(), is(6090566L)); + assertThat(discussion.getNodeId(), is("D_kwDOEq3cwc4AXO9G")); + assertThat(discussion.getNumber(), is(162)); + assertThat(discussion.getTitle(), is("New test question")); + + assertThat(discussion.getUser().getLogin(), is("gsmet")); + assertThat(discussion.getUser().getId(), is(1279749L)); + assertThat(discussion.getUser().getNodeId(), is("MDQ6VXNlcjEyNzk3NDk=")); + + assertThat(discussion.getState(), is(GHRepositoryDiscussion.State.OPEN)); + assertThat(discussion.isLocked(), is(false)); + assertThat(discussion.getComments(), is(1)); + assertThat(discussion.getCreatedAt().toEpochMilli(), is(1705586390000L)); + assertThat(discussion.getUpdatedAt().toEpochMilli(), is(1705586399000L)); + assertThat(discussion.getAuthorAssociation(), is(GHCommentAuthorAssociation.OWNER)); + assertThat(discussion.getActiveLockReason(), is(nullValue())); + assertThat(discussion.getBody(), is("Test question")); + + GHRepositoryDiscussionComment comment = discussionCommentPayload.getComment(); + + assertThat(comment.getHtmlUrl().toString(), + is("https://github.com/gsmet/quarkus-bot-java-playground/discussions/162#discussioncomment-8169669")); + assertThat(comment.getId(), is(8169669L)); + assertThat(comment.getNodeId(), is("DC_kwDOEq3cwc4AfKjF")); + assertThat(comment.getAuthorAssociation(), is(GHCommentAuthorAssociation.OWNER)); + assertThat(comment.getCreatedAt().toEpochMilli(), is(1705586398000L)); + assertThat(comment.getUpdatedAt().toEpochMilli(), is(1705586399000L)); + assertThat(comment.getBody(), is("Test comment.")); + assertThat(comment.getUser().getLogin(), is("gsmet")); + assertThat(comment.getUser().getId(), is(1279749L)); + assertThat(comment.getUser().getNodeId(), is("MDQ6VXNlcjEyNzk3NDk=")); + assertThat(comment.getParentId(), is(nullValue())); + assertThat(comment.getChildCommentCount(), is(0)); } /** - * Issue body edited. + * Discussion created. * * @throws Exception * the exception */ @Test - public void issue_body_edited() throws Exception { - final GHEventPayload.Issue event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Issue.class); - assertThat(event.getAction(), is("edited")); - assertThat(event.getIssue().getNumber(), is(43)); - assertThat(event.getIssue().getBody(), is("Description [updated].")); - assertThat(event.getChanges().getBody().getFrom(), is("Description.")); + public void discussion_created() throws Exception { + final GHEventPayload.Discussion discussionPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Discussion.class); + + assertThat(discussionPayload.getAction(), is("created")); + assertThat(discussionPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); + assertThat(discussionPayload.getSender().getLogin(), is("gsmet")); + + GHRepositoryDiscussion discussion = discussionPayload.getDiscussion(); + + GHRepositoryDiscussion.Category category = discussion.getCategory(); + + assertThat(category.getId(), is(33522033L)); + assertThat(category.getNodeId(), is("DIC_kwDOEq3cwc4B_4Fx")); + assertThat(category.getEmoji(), is(":pray:")); + assertThat(category.getName(), is("Q&A")); + assertThat(category.getDescription(), is("Ask the community for help")); + assertThat(category.getCreatedAt().toEpochMilli(), is(1636991431000L)); + assertThat(category.getUpdatedAt().toEpochMilli(), is(1636991431000L)); + assertThat(category.getSlug(), is("q-a")); + assertThat(category.isAnswerable(), is(true)); + + assertThat(discussion.getAnswerHtmlUrl(), is(nullValue())); + assertThat(discussion.getAnswerChosenAt(), is(nullValue())); + assertThat(discussion.getAnswerChosenBy(), is(nullValue())); + + assertThat(discussion.getHtmlUrl().toString(), + is("https://github.com/gsmet/quarkus-bot-java-playground/discussions/78")); + assertThat(discussion.getId(), is(3698909L)); + assertThat(discussion.getNodeId(), is("D_kwDOEq3cwc4AOHDd")); + assertThat(discussion.getNumber(), is(78)); + assertThat(discussion.getTitle(), is("Title of discussion")); + + assertThat(discussion.getUser().getLogin(), is("gsmet")); + assertThat(discussion.getUser().getId(), is(1279749L)); + assertThat(discussion.getUser().getNodeId(), is("MDQ6VXNlcjEyNzk3NDk=")); + + assertThat(discussion.getState(), is(GHRepositoryDiscussion.State.OPEN)); + assertThat(discussion.isLocked(), is(false)); + assertThat(discussion.getComments(), is(0)); + assertThat(discussion.getCreatedAt().toEpochMilli(), is(1637584949000L)); + assertThat(discussion.getUpdatedAt().toEpochMilli(), is(1637584949000L)); + assertThat(discussion.getAuthorAssociation(), is(GHCommentAuthorAssociation.OWNER)); + assertThat(discussion.getActiveLockReason(), is(nullValue())); + assertThat(discussion.getBody(), is("Body of discussion.")); } // TODO implement support classes and write test @@ -331,1466 +500,1062 @@ public void issue_body_edited() throws Exception { // public void page_build() throws Exception {} /** - * Ping. + * Discussion labeled. * * @throws Exception * the exception */ @Test - public void ping() throws Exception { - final GHEventPayload.Ping event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Ping.class); + public void discussion_labeled() throws Exception { + final GHEventPayload.Discussion discussionPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Discussion.class); - assertThat(event.getAction(), nullValue()); - assertThat(event.getSender().getLogin(), is("seregamorph")); - assertThat(event.getRepository().getName(), is("acme-project-project")); - assertThat(event.getOrganization(), nullValue()); + assertThat(discussionPayload.getAction(), is("labeled")); + assertThat(discussionPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); + assertThat(discussionPayload.getSender().getLogin(), is("gsmet")); + + GHRepositoryDiscussion discussion = discussionPayload.getDiscussion(); + + GHRepositoryDiscussion.Category category = discussion.getCategory(); + + assertThat(category.getId(), is(33522033L)); + assertThat(category.getNodeId(), is("DIC_kwDOEq3cwc4B_4Fx")); + assertThat(category.getEmoji(), is(":pray:")); + assertThat(category.getName(), is("Q&A")); + assertThat(category.getDescription(), is("Ask the community for help")); + assertThat(category.getCreatedAt().toEpochMilli(), is(1636991431000L)); + assertThat(category.getUpdatedAt().toEpochMilli(), is(1636991431000L)); + assertThat(category.getSlug(), is("q-a")); + assertThat(category.isAnswerable(), is(true)); + + assertThat(discussion.getAnswerHtmlUrl(), is(nullValue())); + assertThat(discussion.getAnswerChosenAt(), is(nullValue())); + assertThat(discussion.getAnswerChosenBy(), is(nullValue())); + + assertThat(discussion.getHtmlUrl().toString(), + is("https://github.com/gsmet/quarkus-bot-java-playground/discussions/78")); + assertThat(discussion.getId(), is(3698909L)); + assertThat(discussion.getNodeId(), is("D_kwDOEq3cwc4AOHDd")); + assertThat(discussion.getNumber(), is(78)); + assertThat(discussion.getTitle(), is("Title of discussion")); + + assertThat(discussion.getUser().getLogin(), is("gsmet")); + assertThat(discussion.getUser().getId(), is(1279749L)); + assertThat(discussion.getUser().getNodeId(), is("MDQ6VXNlcjEyNzk3NDk=")); + + assertThat(discussion.getState(), is(GHRepositoryDiscussion.State.OPEN)); + assertThat(discussion.isLocked(), is(false)); + assertThat(discussion.getComments(), is(0)); + assertThat(discussion.getCreatedAt().toEpochMilli(), is(1637584949000L)); + assertThat(discussion.getUpdatedAt().toEpochMilli(), is(1637584961000L)); + assertThat(discussion.getAuthorAssociation(), is(GHCommentAuthorAssociation.OWNER)); + assertThat(discussion.getActiveLockReason(), is(nullValue())); + assertThat(discussion.getBody(), is("Body of discussion.")); + + GHLabel label = discussionPayload.getLabel(); + assertThat(label.getId(), is(2543373314L)); + assertThat(label.getNodeId(), is("MDU6TGFiZWwyNTQzMzczMzE0")); + assertThat(label.getUrl().toString(), + is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/labels/area/hibernate-validator")); + assertThat(label.getName(), is("area/hibernate-validator")); + assertThat(label.getColor(), is("ededed")); + assertThat(label.isDefault(), is(false)); + assertThat(label.getDescription(), is(nullValue())); } /** - * Public. + * Fork. * * @throws Exception * the exception */ @Test - @Payload("public") - public void public_() throws Exception { - final GHEventPayload.Public event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Public.class); + public void fork() throws Exception { + final GHEventPayload.Fork event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Fork.class); + assertThat(event.getForkee().getName(), is("public-repo")); + assertThat(event.getForkee().getOwner().getLogin(), is("baxterandthehackers")); assertThat(event.getRepository().getName(), is("public-repo")); assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); - assertThat(event.getSender().getLogin(), is("baxterthehacker")); + assertThat(event.getSender().getLogin(), is("baxterandthehackers")); } /** - * Pull request. + * Issue body edited. * * @throws Exception * the exception */ @Test - public void pull_request() throws Exception { - final GHEventPayload.PullRequest event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.PullRequest.class); - assertThat(event.getAction(), is("opened")); - assertThat(event.getNumber(), is(1)); - assertThat(event.getPullRequest().getNumber(), is(1)); - assertThat(event.getPullRequest().getTitle(), is("Update the README with new information")); - assertThat(event.getPullRequest().getBody(), - is("This is a pretty simple change that we need to pull into " + "main.")); - assertThat(event.getPullRequest().getUser().getLogin(), is("baxterthehacker")); - assertThat(event.getPullRequest().getHead().getUser().getLogin(), is("baxterthehacker")); - assertThat(event.getPullRequest().getHead().getRef(), is("changes")); - assertThat(event.getPullRequest().getHead().getLabel(), is("baxterthehacker:changes")); - assertThat(event.getPullRequest().getHead().getSha(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c")); - assertThat(event.getPullRequest().getBase().getUser().getLogin(), is("baxterthehacker")); - assertThat(event.getPullRequest().getBase().getRef(), is("main")); - assertThat(event.getPullRequest().getBase().getLabel(), is("baxterthehacker:main")); - assertThat(event.getPullRequest().getBase().getSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); - assertThat(event.getPullRequest().isMerged(), is(false)); - assertThat(event.getPullRequest().getMergeable(), nullValue()); - assertThat(event.getPullRequest().getMergeableState(), is("unknown")); - assertThat(event.getPullRequest().getMergedBy(), nullValue()); - assertThat(event.getPullRequest().getCommentsCount(), is(0)); - assertThat(event.getPullRequest().getReviewComments(), is(0)); - assertThat(event.getPullRequest().getAdditions(), is(1)); - assertThat(event.getPullRequest().getDeletions(), is(1)); - assertThat(event.getPullRequest().getChangedFiles(), is(1)); - assertThat(event.getRepository().getName(), is("public-repo")); - assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); - assertThat(event.getSender().getLogin(), is("baxterthehacker")); - - assertThat(event.getPullRequest().getRepository(), sameInstance(event.getRepository())); + public void issue_body_edited() throws Exception { + final GHEventPayload.Issue event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Issue.class); + assertThat(event.getAction(), is("edited")); + assertThat(event.getIssue().getNumber(), is(43)); + assertThat(event.getIssue().getBody(), is("Description [updated].")); + assertThat(event.getChanges().getBody().getFrom(), is("Description.")); } /** - * Pull request edited base. + * Issue comment. * * @throws Exception * the exception */ @Test - public void pull_request_edited_base() throws Exception { - final GHEventPayload.PullRequest event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.PullRequest.class); + public void issue_comment() throws Exception { + final GHEventPayload.IssueComment event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.IssueComment.class); + assertThat(event.getAction(), is("created")); + assertThat(event.getIssue().getNumber(), is(2)); + assertThat(event.getIssue().getTitle(), is("Spelling error in the README file")); + assertThat(event.getIssue().getState(), is(GHIssueState.OPEN)); + assertThat(event.getIssue().getLabels().size(), is(1)); + assertThat(event.getIssue().getLabels().iterator().next().getName(), is("bug")); + assertThat(event.getComment().getUser().getLogin(), is("baxterthehacker")); + assertThat(event.getComment().getBody(), is("You are totally right! I'll get this fixed right away.")); + assertThat(event.getComment().getAuthorAssociation(), equalTo(GHCommentAuthorAssociation.UNKNOWN)); + assertThat(event.getComment().getBody(), is("You are totally right! I'll get this fixed right away.")); + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); - assertThat(event.getAction(), is("edited")); - assertThat(event.getChanges().getTitle(), nullValue()); - assertThat(event.getPullRequest().getTitle(), is("REST-276 - easy-random")); - assertThat(event.getChanges().getBase().getRef().getFrom(), is("develop")); - assertThat(event.getChanges().getBase().getSha().getFrom(), is("4b0f3b9fd582b071652ccfccd10bfc8c143cff96")); - assertThat(event.getPullRequest().getBase().getRef(), is("4.3")); - assertThat(event.getPullRequest().getBody(), startsWith("**JIRA Ticket URL:**")); - assertThat(event.getChanges().getBody(), nullValue()); + assertThat(event.getIssue().getRepository(), sameInstance(event.getRepository())); + assertThat(event.getComment().getParent(), sameInstance(event.getIssue())); } /** - * Pull request edited title. + * Issue comment edited. * * @throws Exception * the exception */ @Test - public void pull_request_edited_title() throws Exception { - final GHEventPayload.PullRequest event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.PullRequest.class); - + public void issue_comment_edited() throws Exception { + final GHEventPayload.IssueComment event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.IssueComment.class); assertThat(event.getAction(), is("edited")); - assertThat(event.getChanges().getTitle().getFrom(), is("REST-276 - easy-random")); - assertThat(event.getPullRequest().getTitle(), is("REST-276 - easy-random 4.3.0")); - assertThat(event.getChanges().getBase(), nullValue()); - assertThat(event.getPullRequest().getBase().getRef(), is("4.3")); - assertThat(event.getPullRequest().getBody(), startsWith("**JIRA Ticket URL:**")); - assertThat(event.getChanges().getBody(), nullValue()); + assertThat(event.getComment().getBody(), is("This is the issue comment AFTER edit.")); + assertThat(event.getChanges().getBody().getFrom(), is("This is the issue comment BEFORE edit.")); } /** - * Pull request labeled. + * Issue labeled. * * @throws Exception * the exception */ @Test - public void pull_request_labeled() throws Exception { - final GHEventPayload.PullRequest event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.PullRequest.class); + public void issue_labeled() throws Exception { + final GHEventPayload.Issue event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Issue.class); assertThat(event.getAction(), is("labeled")); - assertThat(event.getNumber(), is(79)); - assertThat(event.getPullRequest().getNumber(), is(79)); - assertThat(event.getPullRequest().getTitle(), is("Base POJO test enhancement")); - assertThat(event.getPullRequest().getBody(), - is("This is a pretty simple change that we need to pull into develop.")); - assertThat(event.getPullRequest().getUser().getLogin(), is("seregamorph")); - assertThat(event.getPullRequest().getHead().getUser().getLogin(), is("trilogy-group")); - assertThat(event.getPullRequest().getHead().getRef(), is("changes")); - assertThat(event.getPullRequest().getHead().getLabel(), is("trilogy-group:changes")); - assertThat(event.getPullRequest().getHead().getSha(), is("4b91e3a970fb967fb7be4d52e0969f8e3fb063d0")); - assertThat(event.getPullRequest().getBase().getUser().getLogin(), is("trilogy-group")); - assertThat(event.getPullRequest().getBase().getRef(), is("3.10")); - assertThat(event.getPullRequest().getBase().getLabel(), is("trilogy-group:3.10")); - assertThat(event.getPullRequest().getBase().getSha(), is("7a735f17d686c6a1fc7df5b9d395e5863868f364")); - assertThat(event.getPullRequest().isMerged(), is(false)); - assertThat(event.getPullRequest().getMergeable(), is(true)); - assertThat(event.getPullRequest().getMergeableState(), is("draft")); - assertThat(event.getPullRequest().getMergedBy(), nullValue()); - assertThat(event.getPullRequest().getCommentsCount(), is(1)); - assertThat(event.getPullRequest().getReviewComments(), is(14)); - assertThat(event.getPullRequest().getAdditions(), is(137)); - assertThat(event.getPullRequest().getDeletions(), is(81)); - assertThat(event.getPullRequest().getChangedFiles(), is(22)); - assertThat(event.getPullRequest().getLabels().iterator().next().getName(), is("Ready for Review")); - assertThat(event.getRepository().getName(), is("trilogy-rest-api-framework")); - assertThat(event.getRepository().getOwner().getLogin(), is("trilogy-group")); - assertThat(event.getSender().getLogin(), is("schernov-xo")); - assertThat(event.getLabel().getUrl(), - is("https://api.github.com/repos/trilogy-group/trilogy-rest-api-framework/labels/rest%20api")); - assertThat(event.getLabel().getName(), is("rest api")); - assertThat(event.getLabel().getColor(), is("fef2c0")); - assertThat(event.getLabel().getDescription(), is("REST API pull request")); - assertThat(event.getOrganization().getLogin(), is("trilogy-group")); + assertThat(event.getIssue().getNumber(), is(42)); + assertThat(event.getIssue().getTitle(), is("Test GHEventPayload.Issue label/unlabel")); + assertThat(event.getIssue().getLabels().size(), is(1)); + assertThat(event.getIssue().getLabels().iterator().next().getName(), is("enhancement")); + assertThat(event.getLabel().getName(), is("enhancement")); } /** - * Pull request review. + * Issue title edited. * * @throws Exception * the exception */ @Test - public void pull_request_review() throws Exception { - final GHEventPayload.PullRequestReview event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.PullRequestReview.class); - assertThat(event.getAction(), is("submitted")); - - assertThat(event.getReview().getId(), is(2626884L)); - assertThat(event.getReview().getBody(), is("Looks great!\n")); - assertThat(event.getReview().getState(), is(GHPullRequestReviewState.APPROVED)); - - assertThat(event.getPullRequest().getNumber(), is(8)); - assertThat(event.getPullRequest().getTitle(), is("Add a README description")); - assertThat(event.getPullRequest().getBody(), is("Just a few more details")); - assertThat(event.getReview().getHtmlUrl(), - hasToString("https://github.com/baxterthehacker/public-repo/pull/8#pullrequestreview-2626884")); - assertThat(event.getPullRequest().getUser().getLogin(), is("skalnik")); - assertThat(event.getPullRequest().getHead().getUser().getLogin(), is("skalnik")); - assertThat(event.getPullRequest().getHead().getRef(), is("patch-2")); - assertThat(event.getPullRequest().getHead().getLabel(), is("skalnik:patch-2")); - assertThat(event.getPullRequest().getHead().getSha(), is("b7a1f9c27caa4e03c14a88feb56e2d4f7500aa63")); - assertThat(event.getPullRequest().getBase().getUser().getLogin(), is("baxterthehacker")); - assertThat(event.getPullRequest().getBase().getRef(), is("main")); - assertThat(event.getPullRequest().getBase().getLabel(), is("baxterthehacker:main")); - assertThat(event.getPullRequest().getBase().getSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); - - assertThat(event.getRepository().getName(), is("public-repo")); - assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); - - assertThat(event.getSender().getLogin(), is("baxterthehacker")); - - assertThat(event.getPullRequest().getRepository(), sameInstance(event.getRepository())); - assertThat(event.getReview().getParent(), sameInstance(event.getPullRequest())); + public void issue_title_edited() throws Exception { + final GHEventPayload.Issue event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Issue.class); + assertThat(event.getAction(), is("edited")); + assertThat(event.getIssue().getNumber(), is(43)); + assertThat(event.getIssue().getTitle(), is("Test GHEventPayload.Issue changes [updated]")); + assertThat(event.getChanges().getTitle().getFrom(), is("Test GHEventPayload.Issue changes")); } /** - * Pull request review comment. + * Issue unlabeled. * * @throws Exception * the exception */ @Test - public void pull_request_review_comment() throws Exception { - final GHEventPayload.PullRequestReviewComment event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.PullRequestReviewComment.class); - assertThat(event.getAction(), is("created")); - - assertThat(event.getComment().getBody(), is("Maybe you should use more emojji on this line.")); - - assertThat(event.getPullRequest().getNumber(), is(1)); - assertThat(event.getPullRequest().getTitle(), is("Update the README with new information")); - assertThat(event.getPullRequest().getBody(), - is("This is a pretty simple change that we need to pull into main.")); - assertThat(event.getPullRequest().getUser().getLogin(), is("baxterthehacker")); - assertThat(event.getPullRequest().getHead().getUser().getLogin(), is("baxterthehacker")); - assertThat(event.getPullRequest().getHead().getRef(), is("changes")); - assertThat(event.getPullRequest().getHead().getLabel(), is("baxterthehacker:changes")); - assertThat(event.getPullRequest().getHead().getSha(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c")); - assertThat(event.getPullRequest().getBase().getUser().getLogin(), is("baxterthehacker")); - assertThat(event.getPullRequest().getBase().getRef(), is("main")); - assertThat(event.getPullRequest().getBase().getLabel(), is("baxterthehacker:main")); - assertThat(event.getPullRequest().getBase().getSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); - - assertThat(event.getRepository().getName(), is("public-repo")); - assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); - - assertThat(event.getSender().getLogin(), is("baxterthehacker")); - - assertThat(event.getPullRequest().getRepository(), sameInstance(event.getRepository())); - assertThat(event.getComment().getParent(), sameInstance(event.getPullRequest())); + public void issue_unlabeled() throws Exception { + final GHEventPayload.Issue event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Issue.class); + assertThat(event.getAction(), is("unlabeled")); + assertThat(event.getIssue().getNumber(), is(42)); + assertThat(event.getIssue().getTitle(), is("Test GHEventPayload.Issue label/unlabel")); + assertThat(event.getIssue().getLabels().size(), is(0)); + assertThat(event.getLabel().getName(), is("enhancement")); } /** - * Pull request review comment edited. + * Issue unlabeled when label was deleted from repository. * * @throws Exception * the exception */ @Test - public void pull_request_review_comment_edited() throws Exception { - final GHEventPayload.PullRequestReviewComment event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.PullRequestReviewComment.class); - assertThat(event.getAction(), is("edited")); - assertThat(event.getPullRequest().getNumber(), is(4)); - assertThat(event.getComment().getBody(), is("This is the pull request review comment AFTER edit.")); - assertThat(event.getChanges().getBody().getFrom(), is("This is the pull request review comment BEFORE edit.")); + public void issue_unlabeled_deleted_label() throws Exception { + final GHEventPayload.Issue event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Issue.class); + assertThat(event.getAction(), is("unlabeled")); + assertThat(event.getIssue().getNumber(), is(42)); + assertThat(event.getLabel(), is(nullValue())); } /** - * Push. + * Issues. * * @throws Exception * the exception */ @Test - public void push() throws Exception { - final GHEventPayload.Push event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Push.class); - assertThat(event.getRef(), is("refs/heads/changes")); - assertThat(event.getBefore(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); - assertThat(event.getHead(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c")); - assertThat(event.isCreated(), is(false)); - assertThat(event.isDeleted(), is(false)); - assertThat(event.isForced(), is(false)); - assertThat(event.getCommits().size(), is(1)); - assertThat(event.getCommits().get(0).getSha(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c")); - assertThat(event.getCommits().get(0).getAuthor().getEmail(), is("baxterthehacker@users.noreply.github.com")); - assertThat(event.getCommits().get(0).getAuthor().getUsername(), is("baxterthehacker")); - assertThat(event.getCommits().get(0).getCommitter().getEmail(), is("baxterthehacker@users.noreply.github.com")); - assertThat(event.getCommits().get(0).getCommitter().getUsername(), is("baxterthehacker")); - assertThat(event.getCommits().get(0).getAdded().size(), is(0)); - assertThat(event.getCommits().get(0).getRemoved().size(), is(0)); - assertThat(event.getCommits().get(0).getModified().size(), is(1)); - assertThat(event.getCommits().get(0).getModified().get(0), is("README.md")); - - assertThat(event.getHeadCommit().getSha(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c")); - assertThat(event.getHeadCommit().getAuthor().getEmail(), is("baxterthehacker@users.noreply.github.com")); - assertThat(event.getHeadCommit().getAuthor().getUsername(), is("baxterthehacker")); - assertThat(event.getHeadCommit().getCommitter().getEmail(), is("baxterthehacker@users.noreply.github.com")); - assertThat(event.getHeadCommit().getCommitter().getUsername(), is("baxterthehacker")); - assertThat(event.getHeadCommit().getAdded().size(), is(0)); - assertThat(event.getHeadCommit().getRemoved().size(), is(0)); - assertThat(event.getHeadCommit().getModified().size(), is(1)); - assertThat(event.getHeadCommit().getModified().get(0), is("README.md")); - assertThat(event.getHeadCommit().getMessage(), is("Update README.md")); - - SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - formatter.setTimeZone(TimeZone.getTimeZone("UTC")); - assertThat(formatter.format(event.getCommits().get(0).getTimestamp()), is("2015-05-05T23:40:15Z")); + public void issues() throws Exception { + final GHEventPayload.Issue event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Issue.class); + assertThat(event.getAction(), is("opened")); + assertThat(event.getIssue().getNumber(), is(2)); + assertThat(event.getIssue().getTitle(), is("Spelling error in the README file")); + assertThat(event.getIssue().getState(), is(GHIssueState.OPEN)); + assertThat(event.getIssue().getLabels().size(), is(1)); + assertThat(event.getIssue().getLabels().iterator().next().getName(), is("bug")); assertThat(event.getRepository().getName(), is("public-repo")); - assertThat(event.getRepository().getOwnerName(), is("baxterthehacker")); - assertThat(event.getRepository().getUrl().toExternalForm(), - is("https://github.com/baxterthehacker/public-repo")); - assertThat(event.getPusher().getName(), is("baxterthehacker")); - assertThat(event.getPusher().getEmail(), is("baxterthehacker@users.noreply.github.com")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); assertThat(event.getSender().getLogin(), is("baxterthehacker")); - assertThat(event.getCompare(), - is("https://github.com/baxterthehacker/public-repo/compare/9049f1265b7d...0d1a26e67d8f")); + + assertThat(event.getIssue().getRepository(), sameInstance(event.getRepository())); } /** - * Push to fork. + * Label created. * * @throws Exception * the exception */ @Test - @Payload("push.fork") - public void pushToFork() throws Exception { - gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()).build(); - - final GHEventPayload.Push event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Push.class); - assertThat(event.getRef(), is("refs/heads/changes")); - assertThat(event.getBefore(), is("85c44b352958bf6d81b74ab8b21920f1d313a287")); - assertThat(event.getHead(), is("1393706f1364742defbc28ba459082630ca979af")); - assertThat(event.isCreated(), is(false)); - assertThat(event.isDeleted(), is(false)); - assertThat(event.isForced(), is(false)); - assertThat(event.getCommits().size(), is(1)); - assertThat(event.getCommits().get(0).getSha(), is("1393706f1364742defbc28ba459082630ca979af")); - assertThat(event.getCommits().get(0).getAuthor().getEmail(), is("bitwiseman@gmail.com")); - assertThat(event.getCommits().get(0).getCommitter().getEmail(), is("bitwiseman@gmail.com")); - assertThat(event.getCommits().get(0).getAdded().size(), is(6)); - assertThat(event.getCommits().get(0).getRemoved().size(), is(0)); - assertThat(event.getCommits().get(0).getModified().size(), is(2)); - assertThat(event.getCommits().get(0).getModified().get(0), - is("src/main/java/org/kohsuke/github/GHLicense.java")); - assertThat(event.getRepository().getName(), is("github-api")); - assertThat(event.getRepository().getOwnerName(), is("hub4j-test-org")); - assertThat(event.getRepository().getUrl().toExternalForm(), is("https://github.com/hub4j-test-org/github-api")); - assertThat(event.getPusher().getName(), is("bitwiseman")); - assertThat(event.getPusher().getEmail(), is("bitwiseman@gmail.com")); - assertThat(event.getSender().getLogin(), is("bitwiseman")); - - assertThat(event.getRepository().isFork(), is(true)); - - // in offliine mode, we should not populate missing fields - assertThat(event.getRepository().getSource(), is(nullValue())); - assertThat(event.getRepository().getParent(), is(nullValue())); - - assertThat(event.getRepository().getUrl().toString(), is("https://github.com/hub4j-test-org/github-api")); - assertThat(event.getRepository().getHttpTransportUrl().toString(), - is("https://github.com/hub4j-test-org/github-api.git")); - - // Test repository populate - final GHEventPayload.Push event2 = gitHub.parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), - GHEventPayload.Push.class); - assertThat(event2.getRepository().getUrl().toString(), is("https://github.com/hub4j-test-org/github-api")); - assertThat(event2.getRepository().getHttpTransportUrl(), - is("https://github.com/hub4j-test-org/github-api.git")); - - event2.getRepository().populate(); - - // After populate the url is fixed to point to the correct API endpoint - assertThat(event2.getRepository().getUrl().toString(), - is(mockGitHub.apiServer().baseUrl() + "/repos/hub4j-test-org/github-api")); - assertThat(event2.getRepository().getHttpTransportUrl(), - is("https://github.com/hub4j-test-org/github-api.git")); - - // ensure that root has been bound after populate - event2.getRepository().getSource().getRef("heads/main"); - event2.getRepository().getParent().getRef("heads/main"); - - // Source - final GHEventPayload.Push event3 = gitHub.parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), - GHEventPayload.Push.class); - assertThat(event3.getRepository().getSource().getFullName(), is("hub4j/github-api")); - - // Parent - final GHEventPayload.Push event4 = gitHub.parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), - GHEventPayload.Push.class); - assertThat(event4.getRepository().getParent().getFullName(), is("hub4j/github-api")); + public void label_created() throws Exception { + final GHEventPayload.Label labelPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Label.class); + GHLabel label = labelPayload.getLabel(); + assertThat(labelPayload.getAction(), is("created")); + assertThat(labelPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); + assertThat(label.getId(), is(2901546662L)); + assertThat(label.getNodeId(), is("MDU6TGFiZWwyOTAxNTQ2NjYy")); + assertThat(label.getName(), is("new-label")); + assertThat(label.getColor(), is("f9d0c4")); + assertThat(label.isDefault(), is(false)); + assertThat(label.getDescription(), is("description")); } /** - * Release published. + * Label deleted. * * @throws Exception * the exception */ @Test - public void release_published() throws Exception { - final GHEventPayload.Release event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Release.class); + public void label_deleted() throws Exception { + GHEventPayload.Label labelPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Label.class); + GHLabel label = labelPayload.getLabel(); - assertThat(event.getAction(), is("published")); - assertThat(event.getSender().getLogin(), is("seregamorph")); - assertThat(event.getRepository().getName(), is("company-rest-api-framework")); - assertThat(event.getOrganization().getLogin(), is("company-group")); - assertThat(event.getInstallation(), nullValue()); - assertThat(event.getRelease().getName(), is("4.2")); - assertThat(event.getRelease().getTagName(), is("rest-api-framework-4.2")); - assertThat(event.getRelease().getBody(), is("REST-269 - unique test executions (#86) Sergey Chernov")); + assertThat(labelPayload.getAction(), is("deleted")); + assertThat(labelPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); + assertThat(label.getId(), is(2901546662L)); + assertThat(label.getNodeId(), is("MDU6TGFiZWwyOTAxNTQ2NjYy")); + assertThat(label.getName(), is("new-label-updated")); + assertThat(label.getColor(), is("4AE686")); + assertThat(label.isDefault(), is(false)); + assertThat(label.getDescription(), is("description")); } /** - * Repository. + * Label edited. * * @throws Exception * the exception */ @Test - public void repository() throws Exception { - final GHEventPayload.Repository event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Repository.class); - assertThat(event.getAction(), is("created")); - assertThat(event.getRepository().getName(), is("new-repository")); - assertThat(event.getRepository().getOwner().getLogin(), is("baxterandthehackers")); - assertThat(event.getOrganization().getLogin(), is("baxterandthehackers")); - assertThat(event.getSender().getLogin(), is("baxterthehacker")); - } + public void label_edited() throws Exception { + final GHEventPayload.Label labelPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Label.class); + GHLabel label = labelPayload.getLabel(); - /** - * Repository renamed. - * - * @throws Exception - * the exception - */ - @Test - public void repository_renamed() throws Exception { - final GHEventPayload.Repository event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Repository.class); - assertThat(event.getAction(), is("renamed")); - assertThat(event.getChanges().getRepository().getName().getFrom(), is("react-workshop")); - assertThat(event.getRepository().getName(), is("react-workshop-renamed")); - assertThat(event.getRepository().getOwner().getLogin(), is("EJG-Organization")); - assertThat(event.getOrganization().getLogin(), is("EJG-Organization")); - assertThat(event.getSender().getLogin(), is("eleanorgoh")); + assertThat(labelPayload.getAction(), is("edited")); + assertThat(labelPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); + assertThat(label.getId(), is(2901546662L)); + assertThat(label.getNodeId(), is("MDU6TGFiZWwyOTAxNTQ2NjYy")); + assertThat(label.getName(), is("new-label-updated")); + assertThat(label.getColor(), is("4AE686")); + assertThat(label.isDefault(), is(false)); + assertThat(label.getDescription(), is("description")); + + assertThat(labelPayload.getChanges().getName().getFrom(), is("new-label")); + assertThat(labelPayload.getChanges().getColor().getFrom(), is("f9d0c4")); } /** - * Repository ownership transferred to an organization. + * Member added. * * @throws Exception * the exception */ @Test - public void repository_transferred_to_org() throws Exception { - final GHEventPayload.Repository event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Repository.class); - assertThat(event.getAction(), is("transferred")); - assertThat(event.getChanges().getOwner().getFrom().getUser().getLogin(), is("eleanorgoh")); - assertThat(event.getChanges().getOwner().getFrom().getUser().getId(), is(66235606L)); - assertThat(event.getChanges().getOwner().getFrom().getUser().getType(), is("User")); + public void member_added() throws Exception { + final GHEventPayload.Member memberPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Member.class); + + assertThat(memberPayload.getAction(), is("added")); + + assertThat(memberPayload.getOrganization().getLogin(), is("gsmet-bot-playground")); + assertThat(memberPayload.getRepository().getName(), is("github-automation-with-quarkus-demo-playground")); + + GHUser member = memberPayload.getMember(); + assertThat(member.getId(), is(412878L)); + assertThat(member.getLogin(), is("yrodiere")); + + GHMemberChanges changes = memberPayload.getChanges(); + assertThat(changes.getPermission().getFrom(), is(nullValue())); + assertThat(changes.getPermission().getTo(), is("admin")); } /** - * Repository ownership transferred to a user. + * Member added with role name defined. * * @throws Exception * the exception */ @Test - public void repository_transferred_to_user() throws Exception { - final GHEventPayload.Repository event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Repository.class); - assertThat(event.getAction(), is("transferred")); - assertThat(event.getChanges().getOwner().getFrom().getOrganization().getLogin(), is("EJG-Organization")); - assertThat(event.getChanges().getOwner().getFrom().getOrganization().getId(), is(168135412L)); - assertThat(event.getRepository().getOwner().getLogin(), is("eleanorgoh")); - assertThat(event.getRepository().getOwner().getType(), is("User")); + public void member_added_role_name() throws Exception { + final GHEventPayload.Member memberPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Member.class); + + assertThat(memberPayload.getAction(), is("added")); + + assertThat(memberPayload.getOrganization().getLogin(), is("gsmet-bot-playground")); + assertThat(memberPayload.getRepository().getName(), is("github-automation-with-quarkus-demo-playground")); + + GHUser member = memberPayload.getMember(); + assertThat(member.getId(), is(412878L)); + assertThat(member.getLogin(), is("yrodiere")); + + GHMemberChanges changes = memberPayload.getChanges(); + assertThat(changes.getPermission().getFrom(), is(nullValue())); + assertThat(changes.getPermission().getTo(), is("write")); + assertThat(changes.getRoleName().getTo(), is("maintain")); } /** - * Status. + * Member edited. * * @throws Exception * the exception */ @Test - public void status() throws Exception { - final GHEventPayload.Status event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Status.class); - assertThat(event.getContext(), is("default")); - assertThat(event.getDescription(), is("status description")); - assertThat(event.getState(), is(GHCommitState.SUCCESS)); - assertThat(event.getCommit().getSHA1(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); - assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); - assertThat(event.getTargetUrl(), nullValue()); - assertThat(event.getCommit().getOwner(), sameInstance(event.getRepository())); + public void member_edited() throws Exception { + final GHEventPayload.Member memberPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Member.class); + + assertThat(memberPayload.getAction(), is("edited")); + + assertThat(memberPayload.getOrganization().getLogin(), is("gsmet-bot-playground")); + assertThat(memberPayload.getRepository().getName(), is("github-automation-with-quarkus-demo-playground")); + + GHUser member = memberPayload.getMember(); + assertThat(member.getId(), is(412878L)); + assertThat(member.getLogin(), is("yrodiere")); + + GHMemberChanges changes = memberPayload.getChanges(); + assertThat(changes.getPermission().getFrom(), is("admin")); + assertThat(changes.getPermission().getTo(), is("triage")); } /** - * Status 2. + * Membership added. * * @throws Exception * the exception */ @Test - public void status2() throws Exception { - final GHEventPayload.Status event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Status.class); - assertThat(event.getTargetUrl(), is("https://www.wikipedia.org/")); + public void membership_added() throws Exception { + final GHEventPayload.Membership membershipPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Membership.class); - assertThat(event.getCommit().getOwner(), sameInstance(event.getRepository())); - } + assertThat(membershipPayload.getAction(), is("added")); - // TODO implement support classes and write test - // @Test - // public void team_add() throws Exception {} + assertThat(membershipPayload.getOrganization().getLogin(), is("gsmet-bot-playground")); - // TODO implement support classes and write test - // @Test - // public void watch() throws Exception {} + GHUser member = membershipPayload.getMember(); + assertThat(member.getId(), is(1279749L)); + assertThat(member.getLogin(), is("gsmet")); + + GHTeam team = membershipPayload.getTeam(); + assertThat(team.getId(), is(9709063L)); + assertThat(team.getName(), is("New team")); + assertThat(team.getNodeId(), is("T_kwDOBNft-M4AlCYH")); + assertThat(team.getDescription(), is("Description")); + assertThat(team.getPrivacy(), is(Privacy.CLOSED)); + assertThat(team.getOrganization().getLogin(), is("gsmet-bot-playground")); + } /** - * Check run event. + * Ping. * * @throws Exception * the exception */ @Test - @Payload("check-run") - public void checkRunEvent() throws Exception { - final GHEventPayload.CheckRun event = GitHub.offline() - .parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), GHEventPayload.CheckRun.class); - final GHCheckRun checkRun = verifyBasicCheckRunEvent(event); - assertThat("pull body not populated offline", checkRun.getPullRequests().get(0).getBody(), nullValue()); - assertThat("using offline github", mockGitHub.getRequestCount(), equalTo(0)); - assertThat(checkRun.getPullRequests().get(0).getRepository(), sameInstance(event.getRepository())); - - gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()).build(); - final GHEventPayload.CheckRun event2 = gitHub.parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), - GHEventPayload.CheckRun.class); - final GHCheckRun checkRun2 = verifyBasicCheckRunEvent(event2); + public void ping() throws Exception { + final GHEventPayload.Ping event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Ping.class); - int expectedRequestCount = 2; - assertThat("pull body should be populated", - checkRun2.getPullRequests().get(0).getBody(), - equalTo("This is a pretty simple change that we need to pull into main.")); - assertThat("multiple getPullRequests() calls are made, the pull is populated only once", - mockGitHub.getRequestCount(), - equalTo(expectedRequestCount)); + assertThat(event.getAction(), nullValue()); + assertThat(event.getSender().getLogin(), is("seregamorph")); + assertThat(event.getRepository().getName(), is("acme-project-project")); + assertThat(event.getOrganization(), nullValue()); } - private GHCheckRun verifyBasicCheckRunEvent(final GHEventPayload.CheckRun event) throws IOException { - assertThat(event.getRepository().getName(), is("Hello-World")); - assertThat(event.getRepository().getOwner().getLogin(), is("Codertocat")); - assertThat(event.getAction(), is("created")); - assertThat(event.getRequestedAction(), nullValue()); + /** + * Projectsv 2 item archived. + * + * @throws Exception + * the exception + */ + @Test + public void projectsv2item_archived() throws Exception { + final GHEventPayload.ProjectsV2Item projectsV2ItemPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.ProjectsV2Item.class); - // Checks the deserialization of check_run - final GHCheckRun checkRun = event.getCheckRun(); - assertThat(checkRun.getName(), is("Octocoders-linter")); - assertThat(checkRun.getHeadSha(), is("ec26c3e57ca3a959ca5aad62de7213c562f8c821")); - assertThat(checkRun.getStatus(), is(Status.COMPLETED)); - assertThat(checkRun.getNodeId(), is("MDg6Q2hlY2tSdW4xMjg2MjAyMjg=")); - assertThat(checkRun.getExternalId(), is("")); + assertThat(projectsV2ItemPayload.getAction(), is("archived")); - SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - formatter.setTimeZone(TimeZone.getTimeZone("UTC")); - assertThat(formatter.format(checkRun.getStartedAt()), is("2019-05-15T15:21:12Z")); - assertThat(formatter.format(checkRun.getCompletedAt()), is("2019-05-15T20:22:22Z")); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getId(), is(8083794L)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreatedAt().toEpochMilli(), is(1659532431000L)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getUpdatedAt().toEpochMilli(), is(1660086629000L)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getArchivedAt().toEpochMilli(), is(1660086629000L)); - assertThat(checkRun.getConclusion(), is(Conclusion.SUCCESS)); - assertThat(checkRun.getUrl().toString(), endsWith("/repos/Codertocat/Hello-World/check-runs/128620228")); - assertThat(checkRun.getHtmlUrl().toString(), - endsWith("https://github.com/Codertocat/Hello-World/runs/128620228")); - assertThat(checkRun.getDetailsUrl().toString(), is("https://octocoders.io")); - assertThat(checkRun.getApp().getId(), is(29310L)); - assertThat(checkRun.getCheckSuite().getId(), is(118578147L)); - assertThat(checkRun.getOutput().getTitle(), is("check-run output")); - assertThat(checkRun.getOutput().getSummary(), nullValue()); - assertThat(checkRun.getOutput().getText(), nullValue()); - assertThat(checkRun.getOutput().getAnnotationsCount(), is(0)); - assertThat(checkRun.getOutput().getAnnotationsUrl().toString(), - endsWith("/repos/Codertocat/Hello-World/check-runs/128620228/annotations")); + assertThat(projectsV2ItemPayload.getChanges().getArchivedAt().getFrom(), is(nullValue())); + assertThat(projectsV2ItemPayload.getChanges().getArchivedAt().getTo().toEpochMilli(), is(1660086629000L)); + } - // Checks the deserialization of sender - assertThat(event.getSender().getId(), is(21031067L)); + // TODO implement support classes and write test + // @Test + // public void team_add() throws Exception {} - assertThat(checkRun.getPullRequests(), notNullValue()); - assertThat(checkRun.getPullRequests().size(), equalTo(1)); - assertThat(checkRun.getPullRequests().get(0).getNumber(), equalTo(2)); - return checkRun; - } + // TODO implement support classes and write test + // @Test + // public void watch() throws Exception {} /** - * Check suite event. + * Projectsv 2 item created. * * @throws Exception * the exception */ @Test - @Payload("check-suite") - public void checkSuiteEvent() throws Exception { - final GHEventPayload.CheckSuite event = GitHub.offline() - .parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), GHEventPayload.CheckSuite.class); - final GHCheckSuite checkSuite = verifyBasicCheckSuiteEvent(event); - assertThat("pull body not populated offline", checkSuite.getPullRequests().get(0).getBody(), nullValue()); - assertThat("using offline github", mockGitHub.getRequestCount(), equalTo(0)); - assertThat(checkSuite.getPullRequests().get(0).getRepository(), sameInstance(event.getRepository())); - - gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()).build(); - final GHEventPayload.CheckSuite event2 = gitHub.parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), - GHEventPayload.CheckSuite.class); - final GHCheckSuite checkSuite2 = verifyBasicCheckSuiteEvent(event2); - - int expectedRequestCount = mockGitHub.isUseProxy() ? 3 : 2; - assertThat("pull body should be populated", - checkSuite2.getPullRequests().get(0).getBody(), - equalTo("This is a pretty simple change that we need to pull into main.")); - assertThat("multiple getPullRequests() calls are made, the pull is populated only once", - mockGitHub.getRequestCount(), - lessThanOrEqualTo(expectedRequestCount)); - } + public void projectsv2item_created() throws Exception { + final GHEventPayload.ProjectsV2Item projectsV2ItemPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.ProjectsV2Item.class); - private GHCheckSuite verifyBasicCheckSuiteEvent(final GHEventPayload.CheckSuite event) throws IOException { - assertThat(event.getRepository().getName(), is("Hello-World")); - assertThat(event.getRepository().getOwner().getLogin(), is("Codertocat")); - assertThat(event.getAction(), is("completed")); - assertThat(event.getSender().getId(), is(21031067L)); + assertThat(projectsV2ItemPayload.getAction(), is("created")); - // Checks the deserialization of check_suite - final GHCheckSuite checkSuite = event.getCheckSuite(); - assertThat(checkSuite.getNodeId(), is("MDEwOkNoZWNrU3VpdGUxMTg1NzgxNDc=")); - assertThat(checkSuite.getHeadBranch(), is("changes")); - assertThat(checkSuite.getHeadSha(), is("ec26c3e57ca3a959ca5aad62de7213c562f8c821")); - assertThat(checkSuite.getStatus(), is("completed")); - assertThat(checkSuite.getConclusion(), is("success")); - assertThat(checkSuite.getBefore(), is("6113728f27ae82c7b1a177c8d03f9e96e0adf246")); - assertThat(checkSuite.getAfter(), is("ec26c3e57ca3a959ca5aad62de7213c562f8c821")); - assertThat(checkSuite.getLatestCheckRunsCount(), is(1)); - assertThat(checkSuite.getCheckRunsUrl().toString(), - endsWith("/repos/Codertocat/Hello-World/check-suites/118578147/check-runs")); - assertThat(checkSuite.getHeadCommit().getMessage(), is("Update README.md")); - assertThat(checkSuite.getHeadCommit().getId(), is("ec26c3e57ca3a959ca5aad62de7213c562f8c821")); - assertThat(checkSuite.getHeadCommit().getTreeId(), is("31b122c26a97cf9af023e9ddab94a82c6e77b0ea")); - assertThat(checkSuite.getHeadCommit().getAuthor().getName(), is("Codertocat")); - assertThat(checkSuite.getHeadCommit().getCommitter().getName(), is("Codertocat")); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getId(), is(8083254L)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getNodeId(), is("PVTI_lADOBNft-M4AEjBWzgB7VzY")); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getProjectNodeId(), is("PVT_kwDOBNft-M4AEjBW")); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getContentNodeId(), is("I_kwDOFOkjw85Ozz26")); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getContentType(), is(ContentType.ISSUE)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreator().getLogin(), is("gsmet")); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreator().getNodeId(), is("MDQ6VXNlcjEyNzk3NDk=")); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreatedAt().toEpochMilli(), is(1659532028000L)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getUpdatedAt().toEpochMilli(), is(1659532028000L)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getArchivedAt(), is(nullValue())); - SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - formatter.setTimeZone(TimeZone.getTimeZone("UTC")); - assertThat(formatter.format(checkSuite.getHeadCommit().getTimestamp()), is("2019-05-15T15:20:30Z")); + assertThat(projectsV2ItemPayload.getOrganization().getLogin(), is("gsmet-bot-playground")); + assertThat(projectsV2ItemPayload.getOrganization().getId(), is(81260024L)); + assertThat(projectsV2ItemPayload.getOrganization().getNodeId(), is("MDEyOk9yZ2FuaXphdGlvbjgxMjYwMDI0")); - assertThat(checkSuite.getApp().getId(), is(29310L)); + assertThat(projectsV2ItemPayload.getSender().getLogin(), is("gsmet")); + assertThat(projectsV2ItemPayload.getSender().getId(), is(1279749L)); + assertThat(projectsV2ItemPayload.getSender().getNodeId(), is("MDQ6VXNlcjEyNzk3NDk=")); - assertThat(checkSuite.getPullRequests(), notNullValue()); - assertThat(checkSuite.getPullRequests().size(), equalTo(1)); - assertThat(checkSuite.getPullRequests().get(0).getNumber(), equalTo(2)); - return checkSuite; + assertThat(projectsV2ItemPayload.getInstallation().getId(), is(16779846L)); + assertThat(projectsV2ItemPayload.getInstallation().getNodeId(), + is("MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTY3Nzk4NDY=")); } /** - * Installation repositories event. + * Projectsv 2 item edited. * * @throws Exception * the exception */ @Test - @Payload("installation_repositories") - public void InstallationRepositoriesEvent() throws Exception { - final GHEventPayload.InstallationRepositories event = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.InstallationRepositories.class); + public void projectsv2item_edited() throws Exception { + final GHEventPayload.ProjectsV2Item projectsV2ItemPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.ProjectsV2Item.class); - assertThat(event.getAction(), is("added")); - assertThat(event.getInstallation().getId(), is(957387L)); - assertThat(event.getInstallation().getAccount().getLogin(), is("Codertocat")); - assertThat(event.getRepositorySelection(), is("selected")); + assertThat(projectsV2ItemPayload.getAction(), is("edited")); - assertThat(event.getRepositoriesAdded().get(0).getId(), is(186853007L)); - assertThat(event.getRepositoriesAdded().get(0).getNodeId(), is("MDEwOlJlcG9zaXRvcnkxODY4NTMwMDc=")); - assertThat(event.getRepositoriesAdded().get(0).getName(), is("Space")); - assertThat(event.getRepositoriesAdded().get(0).getFullName(), is("Codertocat/Space")); - assertThat(event.getRepositoriesAdded().get(0).isPrivate(), is(false)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getId(), is(8083254L)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreatedAt().toEpochMilli(), is(1659532028000L)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getUpdatedAt().toEpochMilli(), is(1659532033000L)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getArchivedAt(), is(nullValue())); - assertThat(event.getRepositoriesRemoved(), is(Collections.emptyList())); - assertThat(event.getSender().getLogin(), is("Codertocat")); + assertThat(projectsV2ItemPayload.getChanges().getFieldValue().getFieldNodeId(), + is("PVTF_lADOBNft-M4AEjBWzgCnp5Q")); + assertThat(projectsV2ItemPayload.getChanges().getFieldValue().getFieldType(), is(FieldType.SINGLE_SELECT)); } /** - * Installation event. + * Projectsv 2 item reordered. * * @throws Exception * the exception */ @Test - @Payload("installation_created") - public void InstallationCreatedEvent() throws Exception { - final GHEventPayload.Installation event = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()) - .build() - .parseEventPayload(payload.asReader(), GHEventPayload.Installation.class); + public void projectsv2item_reordered() throws Exception { + final GHEventPayload.ProjectsV2Item projectsV2ItemPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.ProjectsV2Item.class); - assertThat(event.getAction(), is("created")); - assertThat(event.getInstallation().getId(), is(43898337L)); - assertThat(event.getInstallation().getAccount().getLogin(), is("CronFire")); + assertThat(projectsV2ItemPayload.getAction(), is("reordered")); - assertThat(event.getRepositories().isEmpty(), is(false)); - assertThat(event.getRepositories().get(0).getId(), is(1296269L)); - assertThat(event.getRawRepositories().isEmpty(), is(false)); - assertThat(event.getRawRepositories().get(0).getId(), is(1296269L)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getId(), is(8083794L)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreatedAt().toEpochMilli(), is(1659532431000L)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getUpdatedAt().toEpochMilli(), is(1659532439000L)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getArchivedAt(), is(nullValue())); - assertThat(event.getSender().getLogin(), is("Haarolean")); + assertThat(projectsV2ItemPayload.getChanges().getPreviousProjectsV2ItemNodeId().getFrom(), + is("PVTI_lADOBNft-M4AEjBWzgB7VzY")); + assertThat(projectsV2ItemPayload.getChanges().getPreviousProjectsV2ItemNodeId().getTo(), + is("PVTI_lADOBNft-M4AEjBWzgB7VzY")); } /** - * Installation event. + * Projectsv 2 item restored. * * @throws Exception * the exception */ @Test - @Payload("installation_deleted") - public void InstallationDeletedEvent() throws Exception { - final GHEventPayload.Installation event = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()) - .build() - .parseEventPayload(payload.asReader(), GHEventPayload.Installation.class); + public void projectsv2item_restored() throws Exception { + final GHEventPayload.ProjectsV2Item projectsV2ItemPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.ProjectsV2Item.class); - assertThat(event.getAction(), is("deleted")); - assertThat(event.getInstallation().getId(), is(2L)); - assertThat(event.getInstallation().getAccount().getLogin(), is("octocat")); + assertThat(projectsV2ItemPayload.getAction(), is("restored")); - assertThrows(IllegalStateException.class, () -> event.getRepositories().isEmpty()); - assertThat(event.getRawRepositories().isEmpty(), is(false)); - assertThat(event.getRawRepositories().get(0).getId(), is(1296269L)); - assertThat(event.getRawRepositories().get(0).getNodeId(), is("MDEwOlJlcG9zaXRvcnkxMjk2MjY5")); - assertThat(event.getRawRepositories().get(0).getName(), is("Hello-World")); - assertThat(event.getRawRepositories().get(0).getFullName(), is("octocat/Hello-World")); - assertThat(event.getRawRepositories().get(0).isPrivate(), is(false)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getId(), is(8083254L)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreatedAt().toEpochMilli(), is(1659532028000L)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getUpdatedAt().toEpochMilli(), is(1659532419000L)); + assertThat(projectsV2ItemPayload.getProjectsV2Item().getArchivedAt(), is(nullValue())); - assertThat(event.getSender().getLogin(), is("octocat")); + assertThat(projectsV2ItemPayload.getChanges().getArchivedAt().getFrom().toEpochMilli(), is(1659532142000L)); + assertThat(projectsV2ItemPayload.getChanges().getArchivedAt().getTo(), is(nullValue())); } /** - * Workflow dispatch. + * Public. * * @throws Exception * the exception */ @Test - public void workflow_dispatch() throws Exception { - final GHEventPayload.WorkflowDispatch workflowDispatchPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.WorkflowDispatch.class); - - assertThat(workflowDispatchPayload.getRef(), is("refs/heads/main")); - assertThat(workflowDispatchPayload.getAction(), is(nullValue())); - assertThat(workflowDispatchPayload.getWorkflow(), is(".github/workflows/main.yml")); - assertThat(workflowDispatchPayload.getInputs(), aMapWithSize(1)); - assertThat(workflowDispatchPayload.getInputs().keySet(), contains("logLevel")); - assertThat(workflowDispatchPayload.getInputs().values(), contains("warning")); - assertThat(workflowDispatchPayload.getRepository().getName(), is("quarkus-bot-java-playground")); - assertThat(workflowDispatchPayload.getSender().getLogin(), is("gsmet")); + @Payload("public") + public void public_() throws Exception { + final GHEventPayload.Public event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Public.class); + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); } /** - * Workflow run. + * Pull request. * * @throws Exception * the exception */ @Test - public void workflow_run() throws Exception { - final GHEventPayload.WorkflowRun workflowRunPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.WorkflowRun.class); - - assertThat(workflowRunPayload.getAction(), is("completed")); - assertThat(workflowRunPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); - assertThat(workflowRunPayload.getSender().getLogin(), is("gsmet")); - - GHWorkflow workflow = workflowRunPayload.getWorkflow(); - assertThat(workflow.getId(), is(7087581L)); - assertThat(workflow.getName(), is("CI")); - assertThat(workflow.getPath(), is(".github/workflows/main.yml")); - assertThat(workflow.getState(), is("active")); - assertThat(workflow.getUrl().toString(), - is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/actions/workflows/7087581")); - assertThat(workflow.getHtmlUrl().toString(), - is("https://github.com/gsmet/quarkus-bot-java-playground/blob/main/.github/workflows/main.yml")); - assertThat(workflow.getBadgeUrl().toString(), - is("https://github.com/gsmet/quarkus-bot-java-playground/workflows/CI/badge.svg")); + public void pull_request() throws Exception { + final GHEventPayload.PullRequest event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.PullRequest.class); + assertThat(event.getAction(), is("opened")); + assertThat(event.getNumber(), is(1)); + assertThat(event.getPullRequest().getNumber(), is(1)); + assertThat(event.getPullRequest().getTitle(), is("Update the README with new information")); + assertThat(event.getPullRequest().getBody(), + is("This is a pretty simple change that we need to pull into " + "main.")); + assertThat(event.getPullRequest().getUser().getLogin(), is("baxterthehacker")); + assertThat(event.getPullRequest().getHead().getUser().getLogin(), is("baxterthehacker")); + assertThat(event.getPullRequest().getHead().getRef(), is("changes")); + assertThat(event.getPullRequest().getHead().getLabel(), is("baxterthehacker:changes")); + assertThat(event.getPullRequest().getHead().getSha(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c")); + assertThat(event.getPullRequest().getBase().getUser().getLogin(), is("baxterthehacker")); + assertThat(event.getPullRequest().getBase().getRef(), is("main")); + assertThat(event.getPullRequest().getBase().getLabel(), is("baxterthehacker:main")); + assertThat(event.getPullRequest().getBase().getSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); + assertThat(event.getPullRequest().isMerged(), is(false)); + assertThat(event.getPullRequest().isPullRequest(), is(true)); + assertThat(event.getPullRequest().getMergeable(), nullValue()); + assertThat(event.getPullRequest().getMergeableState(), is("unknown")); + assertThat(event.getPullRequest().getMergedBy(), nullValue()); + assertThat(event.getPullRequest().getCommentsCount(), is(0)); + assertThat(event.getPullRequest().getReviewComments(), is(0)); + assertThat(event.getPullRequest().getAdditions(), is(1)); + assertThat(event.getPullRequest().getDeletions(), is(1)); + assertThat(event.getPullRequest().getChangedFiles(), is(1)); + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); - GHWorkflowRun workflowRun = workflowRunPayload.getWorkflowRun(); - assertThat(workflowRun.getId(), is(680604745L)); - assertThat(workflowRun.getName(), is("CI")); - assertThat(workflowRun.getHeadBranch(), is("main")); - assertThat(workflowRun.getDisplayTitle(), is("its-display-title")); - assertThat(workflowRun.getHeadSha(), is("dbea8d8b6ed2cf764dfd84a215f3f9040b3d4423")); - assertThat(workflowRun.getRunNumber(), is(6L)); - assertThat(workflowRun.getEvent(), is(GHEvent.WORKFLOW_DISPATCH)); - assertThat(workflowRun.getStatus(), is(GHWorkflowRun.Status.COMPLETED)); - assertThat(workflowRun.getConclusion(), is(GHWorkflowRun.Conclusion.SUCCESS)); - assertThat(workflowRun.getWorkflowId(), is(7087581L)); - assertThat(workflowRun.getUrl().toString(), - is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/actions/runs/680604745")); - assertThat(workflowRun.getHtmlUrl().toString(), - is("https://github.com/gsmet/quarkus-bot-java-playground/actions/runs/680604745")); - assertThat(workflowRun.getJobsUrl().toString(), - is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/actions/runs/680604745/jobs")); - assertThat(workflowRun.getLogsUrl().toString(), - is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/actions/runs/680604745/logs")); - assertThat(workflowRun.getCheckSuiteUrl().toString(), - is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/check-suites/2327154397")); - assertThat(workflowRun.getArtifactsUrl().toString(), - is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/actions/runs/680604745/artifacts")); - assertThat(workflowRun.getCancelUrl().toString(), - is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/actions/runs/680604745/cancel")); - assertThat(workflowRun.getRerunUrl().toString(), - is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/actions/runs/680604745/rerun")); - assertThat(workflowRun.getWorkflowUrl().toString(), - is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/actions/workflows/7087581")); - assertThat(workflowRun.getCreatedAt().getTime(), is(1616524526000L)); - assertThat(workflowRun.getUpdatedAt().getTime(), is(1616524543000L)); - assertThat(workflowRun.getRunAttempt(), is(1L)); - assertThat(workflowRun.getRunStartedAt().getTime(), is(1616524526000L)); - assertThat(workflowRun.getHeadCommit().getId(), is("dbea8d8b6ed2cf764dfd84a215f3f9040b3d4423")); - assertThat(workflowRun.getHeadCommit().getTreeId(), is("b17089e6a2574ec1002566fe980923e62dce3026")); - assertThat(workflowRun.getHeadCommit().getMessage(), is("Update main.yml")); - assertThat(workflowRun.getHeadCommit().getTimestamp().getTime(), is(1616523390000L)); - assertThat(workflowRun.getHeadCommit().getAuthor().getName(), is("Guillaume Smet")); - assertThat(workflowRun.getHeadCommit().getAuthor().getEmail(), is("guillaume.smet@gmail.com")); - assertThat(workflowRun.getHeadCommit().getCommitter().getName(), is("GitHub")); - assertThat(workflowRun.getHeadCommit().getCommitter().getEmail(), is("noreply@github.com")); - assertThat(workflowRun.getHeadRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); - assertThat(workflowRun.getRepository(), sameInstance(workflowRunPayload.getRepository())); + assertThat(event.getPullRequest().getRepository(), sameInstance(event.getRepository())); } /** - * Workflow run pull request. + * Pull request edited base. * * @throws Exception * the exception */ @Test - public void workflow_run_pull_request() throws Exception { - final GHEventPayload.WorkflowRun workflowRunPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.WorkflowRun.class); - - List pullRequests = workflowRunPayload.getWorkflowRun().getPullRequests(); - assertThat(pullRequests.size(), is(1)); + public void pull_request_edited_base() throws Exception { + final GHEventPayload.PullRequest event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.PullRequest.class); - GHPullRequest pullRequest = pullRequests.get(0); - assertThat(pullRequest.getId(), is(599098265L)); - assertThat(pullRequest.getRepository(), sameInstance(workflowRunPayload.getRepository())); + assertThat(event.getAction(), is("edited")); + assertThat(event.getChanges().getTitle(), nullValue()); + assertThat(event.getPullRequest().getTitle(), is("REST-276 - easy-random")); + assertThat(event.getChanges().getBase().getRef().getFrom(), is("develop")); + assertThat(event.getChanges().getBase().getSha().getFrom(), is("4b0f3b9fd582b071652ccfccd10bfc8c143cff96")); + assertThat(event.getPullRequest().getBase().getRef(), is("4.3")); + assertThat(event.getPullRequest().getBody(), startsWith("**JIRA Ticket URL:**")); + assertThat(event.getChanges().getBody(), nullValue()); } /** - * Workflow run other repository. + * Pull request edited title. * * @throws Exception * the exception */ @Test - public void workflow_run_other_repository() throws Exception { - final GHEventPayload.WorkflowRun workflowRunPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.WorkflowRun.class); - GHWorkflowRun workflowRun = workflowRunPayload.getWorkflowRun(); + public void pull_request_edited_title() throws Exception { + final GHEventPayload.PullRequest event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.PullRequest.class); - assertThat(workflowRunPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); - assertThat(workflowRun.getHeadRepository().getFullName(), - is("gsmet-bot-playground/quarkus-bot-java-playground")); - assertThat(workflowRun.getRepository(), sameInstance(workflowRunPayload.getRepository())); - assertThat(workflowRunPayload.getWorkflow().getRepository(), sameInstance(workflowRunPayload.getRepository())); + assertThat(event.getAction(), is("edited")); + assertThat(event.getChanges().getTitle().getFrom(), is("REST-276 - easy-random")); + assertThat(event.getPullRequest().getTitle(), is("REST-276 - easy-random 4.3.0")); + assertThat(event.getChanges().getBase(), nullValue()); + assertThat(event.getPullRequest().getBase().getRef(), is("4.3")); + assertThat(event.getPullRequest().getBody(), startsWith("**JIRA Ticket URL:**")); + assertThat(event.getChanges().getBody(), nullValue()); } /** - * Workflow job. + * Pull request labeled. * * @throws Exception * the exception */ @Test - public void workflow_job() throws Exception { - final GHEventPayload.WorkflowJob workflowJobPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.WorkflowJob.class); - - assertThat(workflowJobPayload.getAction(), is("completed")); - assertThat(workflowJobPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); - assertThat(workflowJobPayload.getSender().getLogin(), is("gsmet")); - - GHWorkflowJob workflowJob = workflowJobPayload.getWorkflowJob(); - assertThat(workflowJob.getId(), is(6653410527L)); - assertThat(workflowJob.getRunId(), is(2408553341L)); - assertThat(workflowJob.getRunAttempt(), is(1)); - assertThat(workflowJob.getUrl().toString(), - is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/actions/jobs/6653410527")); - assertThat(workflowJob.getHtmlUrl().toString(), - is("https://github.com/gsmet/quarkus-bot-java-playground/runs/6653410527?check_suite_focus=true")); - assertThat(workflowJob.getNodeId(), is("CR_kwDOEq3cwc8AAAABjJL83w")); - assertThat(workflowJob.getHeadSha(), is("5dd2dadfbdc2a722c08a8ad42ae4e26e3e731042")); - assertThat(workflowJob.getStatus(), is(GHWorkflowRun.Status.COMPLETED)); - assertThat(workflowJob.getConclusion(), is(GHWorkflowRun.Conclusion.FAILURE)); - assertThat(workflowJob.getStartedAt().getTime(), is(1653908125000L)); - assertThat(workflowJob.getCompletedAt().getTime(), is(1653908157000L)); - assertThat(workflowJob.getName(), is("JVM Tests - JDK JDK16")); - assertThat(workflowJob.getSteps(), - contains(hasProperty("name", is("Set up job")), - hasProperty("name", is("Run actions/checkout@v2")), - hasProperty("name", is("Build with Maven")), - hasProperty("name", is("Post Run actions/checkout@v2")), - hasProperty("name", is("Complete job")))); - assertThat(workflowJob.getCheckRunUrl().toString(), - is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/check-runs/6653410527")); + public void pull_request_labeled() throws Exception { + final GHEventPayload.PullRequest event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.PullRequest.class); + assertThat(event.getAction(), is("labeled")); + assertThat(event.getNumber(), is(79)); + assertThat(event.getPullRequest().getNumber(), is(79)); + assertThat(event.getPullRequest().getTitle(), is("Base POJO test enhancement")); + assertThat(event.getPullRequest().getBody(), + is("This is a pretty simple change that we need to pull into develop.")); + assertThat(event.getPullRequest().getUser().getLogin(), is("seregamorph")); + assertThat(event.getPullRequest().getHead().getUser().getLogin(), is("trilogy-group")); + assertThat(event.getPullRequest().getHead().getRef(), is("changes")); + assertThat(event.getPullRequest().getHead().getLabel(), is("trilogy-group:changes")); + assertThat(event.getPullRequest().getHead().getSha(), is("4b91e3a970fb967fb7be4d52e0969f8e3fb063d0")); + assertThat(event.getPullRequest().getBase().getUser().getLogin(), is("trilogy-group")); + assertThat(event.getPullRequest().getBase().getRef(), is("3.10")); + assertThat(event.getPullRequest().getBase().getLabel(), is("trilogy-group:3.10")); + assertThat(event.getPullRequest().getBase().getSha(), is("7a735f17d686c6a1fc7df5b9d395e5863868f364")); + assertThat(event.getPullRequest().isMerged(), is(false)); + assertThat(event.getPullRequest().getMergeable(), is(true)); + assertThat(event.getPullRequest().getMergeableState(), is("draft")); + assertThat(event.getPullRequest().getMergedBy(), nullValue()); + assertThat(event.getPullRequest().getCommentsCount(), is(1)); + assertThat(event.getPullRequest().getReviewComments(), is(14)); + assertThat(event.getPullRequest().getAdditions(), is(137)); + assertThat(event.getPullRequest().getDeletions(), is(81)); + assertThat(event.getPullRequest().getChangedFiles(), is(22)); + assertThat(event.getPullRequest().getLabels().iterator().next().getName(), is("Ready for Review")); + assertThat(event.getRepository().getName(), is("trilogy-rest-api-framework")); + assertThat(event.getRepository().getOwner().getLogin(), is("trilogy-group")); + assertThat(event.getSender().getLogin(), is("schernov-xo")); + assertThat(event.getLabel().getUrl(), + is("https://api.github.com/repos/trilogy-group/trilogy-rest-api-framework/labels/rest%20api")); + assertThat(event.getLabel().getName(), is("rest api")); + assertThat(event.getLabel().getColor(), is("fef2c0")); + assertThat(event.getLabel().getDescription(), is("REST API pull request")); + assertThat(event.getOrganization().getLogin(), is("trilogy-group")); } /** - * Label created. + * Pull request review. * * @throws Exception * the exception */ @Test - public void label_created() throws Exception { - final GHEventPayload.Label labelPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Label.class); - GHLabel label = labelPayload.getLabel(); + public void pull_request_review() throws Exception { + final GHEventPayload.PullRequestReview event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.PullRequestReview.class); + assertThat(event.getAction(), is("submitted")); - assertThat(labelPayload.getAction(), is("created")); - assertThat(labelPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); - assertThat(label.getId(), is(2901546662L)); - assertThat(label.getNodeId(), is("MDU6TGFiZWwyOTAxNTQ2NjYy")); - assertThat(label.getName(), is("new-label")); - assertThat(label.getColor(), is("f9d0c4")); - assertThat(label.isDefault(), is(false)); - assertThat(label.getDescription(), is("description")); + assertThat(event.getReview().getId(), is(2626884L)); + assertThat(event.getReview().getBody(), is("Looks great!\n")); + assertThat(event.getReview().getState(), is(GHPullRequestReviewState.APPROVED)); + + assertThat(event.getPullRequest().getNumber(), is(8)); + assertThat(event.getPullRequest().getTitle(), is("Add a README description")); + assertThat(event.getPullRequest().getBody(), is("Just a few more details")); + assertThat(event.getReview().getHtmlUrl(), + hasToString("https://github.com/baxterthehacker/public-repo/pull/8#pullrequestreview-2626884")); + assertThat(event.getPullRequest().getUser().getLogin(), is("skalnik")); + assertThat(event.getPullRequest().getHead().getUser().getLogin(), is("skalnik")); + assertThat(event.getPullRequest().getHead().getRef(), is("patch-2")); + assertThat(event.getPullRequest().getHead().getLabel(), is("skalnik:patch-2")); + assertThat(event.getPullRequest().getHead().getSha(), is("b7a1f9c27caa4e03c14a88feb56e2d4f7500aa63")); + assertThat(event.getPullRequest().getBase().getUser().getLogin(), is("baxterthehacker")); + assertThat(event.getPullRequest().getBase().getRef(), is("main")); + assertThat(event.getPullRequest().getBase().getLabel(), is("baxterthehacker:main")); + assertThat(event.getPullRequest().getBase().getSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); + + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + + assertThat(event.getSender().getLogin(), is("baxterthehacker")); + + assertThat(event.getPullRequest().getRepository(), sameInstance(event.getRepository())); + assertThat(event.getReview().getParent(), sameInstance(event.getPullRequest())); } /** - * Label edited. + * Pull request review comment. * * @throws Exception * the exception */ @Test - public void label_edited() throws Exception { - final GHEventPayload.Label labelPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Label.class); - GHLabel label = labelPayload.getLabel(); + public void pull_request_review_comment() throws Exception { + final GHEventPayload.PullRequestReviewComment event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.PullRequestReviewComment.class); + assertThat(event.getAction(), is("created")); - assertThat(labelPayload.getAction(), is("edited")); - assertThat(labelPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); - assertThat(label.getId(), is(2901546662L)); - assertThat(label.getNodeId(), is("MDU6TGFiZWwyOTAxNTQ2NjYy")); - assertThat(label.getName(), is("new-label-updated")); - assertThat(label.getColor(), is("4AE686")); - assertThat(label.isDefault(), is(false)); - assertThat(label.getDescription(), is("description")); - - assertThat(labelPayload.getChanges().getName().getFrom(), is("new-label")); - assertThat(labelPayload.getChanges().getColor().getFrom(), is("f9d0c4")); - } - - /** - * Label deleted. - * - * @throws Exception - * the exception - */ - @Test - public void label_deleted() throws Exception { - GHEventPayload.Label labelPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Label.class); - GHLabel label = labelPayload.getLabel(); - - assertThat(labelPayload.getAction(), is("deleted")); - assertThat(labelPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); - assertThat(label.getId(), is(2901546662L)); - assertThat(label.getNodeId(), is("MDU6TGFiZWwyOTAxNTQ2NjYy")); - assertThat(label.getName(), is("new-label-updated")); - assertThat(label.getColor(), is("4AE686")); - assertThat(label.isDefault(), is(false)); - assertThat(label.getDescription(), is("description")); - } - - /** - * Discussion created. - * - * @throws Exception - * the exception - */ - @Test - public void discussion_created() throws Exception { - final GHEventPayload.Discussion discussionPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Discussion.class); - - assertThat(discussionPayload.getAction(), is("created")); - assertThat(discussionPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); - assertThat(discussionPayload.getSender().getLogin(), is("gsmet")); - - GHRepositoryDiscussion discussion = discussionPayload.getDiscussion(); - - GHRepositoryDiscussion.Category category = discussion.getCategory(); - - assertThat(category.getId(), is(33522033L)); - assertThat(category.getNodeId(), is("DIC_kwDOEq3cwc4B_4Fx")); - assertThat(category.getEmoji(), is(":pray:")); - assertThat(category.getName(), is("Q&A")); - assertThat(category.getDescription(), is("Ask the community for help")); - assertThat(category.getCreatedAt().getTime(), is(1636991431000L)); - assertThat(category.getUpdatedAt().getTime(), is(1636991431000L)); - assertThat(category.getSlug(), is("q-a")); - assertThat(category.isAnswerable(), is(true)); + assertThat(event.getComment().getBody(), is("Maybe you should use more emojji on this line.")); - assertThat(discussion.getAnswerHtmlUrl(), is(nullValue())); - assertThat(discussion.getAnswerChosenAt(), is(nullValue())); - assertThat(discussion.getAnswerChosenBy(), is(nullValue())); + assertThat(event.getPullRequest().getNumber(), is(1)); + assertThat(event.getPullRequest().getTitle(), is("Update the README with new information")); + assertThat(event.getPullRequest().getBody(), + is("This is a pretty simple change that we need to pull into main.")); + assertThat(event.getPullRequest().getUser().getLogin(), is("baxterthehacker")); + assertThat(event.getPullRequest().getHead().getUser().getLogin(), is("baxterthehacker")); + assertThat(event.getPullRequest().getHead().getRef(), is("changes")); + assertThat(event.getPullRequest().getHead().getLabel(), is("baxterthehacker:changes")); + assertThat(event.getPullRequest().getHead().getSha(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c")); + assertThat(event.getPullRequest().getBase().getUser().getLogin(), is("baxterthehacker")); + assertThat(event.getPullRequest().getBase().getRef(), is("main")); + assertThat(event.getPullRequest().getBase().getLabel(), is("baxterthehacker:main")); + assertThat(event.getPullRequest().getBase().getSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); - assertThat(discussion.getHtmlUrl().toString(), - is("https://github.com/gsmet/quarkus-bot-java-playground/discussions/78")); - assertThat(discussion.getId(), is(3698909L)); - assertThat(discussion.getNodeId(), is("D_kwDOEq3cwc4AOHDd")); - assertThat(discussion.getNumber(), is(78)); - assertThat(discussion.getTitle(), is("Title of discussion")); + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); - assertThat(discussion.getUser().getLogin(), is("gsmet")); - assertThat(discussion.getUser().getId(), is(1279749L)); - assertThat(discussion.getUser().getNodeId(), is("MDQ6VXNlcjEyNzk3NDk=")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); - assertThat(discussion.getState(), is(GHRepositoryDiscussion.State.OPEN)); - assertThat(discussion.isLocked(), is(false)); - assertThat(discussion.getComments(), is(0)); - assertThat(discussion.getCreatedAt().getTime(), is(1637584949000L)); - assertThat(discussion.getUpdatedAt().getTime(), is(1637584949000L)); - assertThat(discussion.getAuthorAssociation(), is(GHCommentAuthorAssociation.OWNER)); - assertThat(discussion.getActiveLockReason(), is(nullValue())); - assertThat(discussion.getBody(), is("Body of discussion.")); + assertThat(event.getPullRequest().getRepository(), sameInstance(event.getRepository())); + assertThat(event.getComment().getParent(), sameInstance(event.getPullRequest())); } /** - * Discussion answered. + * Pull request review comment edited. * * @throws Exception * the exception */ @Test - public void discussion_answered() throws Exception { - final GHEventPayload.Discussion discussionPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Discussion.class); - - assertThat(discussionPayload.getAction(), is("answered")); - assertThat(discussionPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); - assertThat(discussionPayload.getSender().getLogin(), is("gsmet")); - - GHRepositoryDiscussion discussion = discussionPayload.getDiscussion(); - - GHRepositoryDiscussion.Category category = discussion.getCategory(); - - assertThat(category.getId(), is(33522033L)); - assertThat(category.getNodeId(), is("DIC_kwDOEq3cwc4B_4Fx")); - assertThat(category.getEmoji(), is(":pray:")); - assertThat(category.getName(), is("Q&A")); - assertThat(category.getDescription(), is("Ask the community for help")); - assertThat(category.getCreatedAt().getTime(), is(1636991431000L)); - assertThat(category.getUpdatedAt().getTime(), is(1636991431000L)); - assertThat(category.getSlug(), is("q-a")); - assertThat(category.isAnswerable(), is(true)); - - assertThat(discussion.getAnswerHtmlUrl().toString(), - is("https://github.com/gsmet/quarkus-bot-java-playground/discussions/78#discussioncomment-1681242")); - assertThat(discussion.getAnswerChosenAt().getTime(), is(1637585047000L)); - assertThat(discussion.getAnswerChosenBy().getLogin(), is("gsmet")); - - assertThat(discussion.getHtmlUrl().toString(), - is("https://github.com/gsmet/quarkus-bot-java-playground/discussions/78")); - assertThat(discussion.getId(), is(3698909L)); - assertThat(discussion.getNodeId(), is("D_kwDOEq3cwc4AOHDd")); - assertThat(discussion.getNumber(), is(78)); - assertThat(discussion.getTitle(), is("Title of discussion")); - - assertThat(discussion.getUser().getLogin(), is("gsmet")); - assertThat(discussion.getUser().getId(), is(1279749L)); - assertThat(discussion.getUser().getNodeId(), is("MDQ6VXNlcjEyNzk3NDk=")); - - assertThat(discussion.getState(), is(GHRepositoryDiscussion.State.OPEN)); - assertThat(discussion.isLocked(), is(false)); - assertThat(discussion.getComments(), is(1)); - assertThat(discussion.getCreatedAt().getTime(), is(1637584949000L)); - assertThat(discussion.getUpdatedAt().getTime(), is(1637585047000L)); - assertThat(discussion.getAuthorAssociation(), is(GHCommentAuthorAssociation.OWNER)); - assertThat(discussion.getActiveLockReason(), is(nullValue())); - assertThat(discussion.getBody(), is("Body of discussion.")); + public void pull_request_review_comment_edited() throws Exception { + final GHEventPayload.PullRequestReviewComment event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.PullRequestReviewComment.class); + assertThat(event.getAction(), is("edited")); + assertThat(event.getPullRequest().getNumber(), is(4)); + assertThat(event.getComment().getBody(), is("This is the pull request review comment AFTER edit.")); + assertThat(event.getChanges().getBody().getFrom(), is("This is the pull request review comment BEFORE edit.")); } /** - * Discussion labeled. + * Push. * * @throws Exception * the exception */ @Test - public void discussion_labeled() throws Exception { - final GHEventPayload.Discussion discussionPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Discussion.class); - - assertThat(discussionPayload.getAction(), is("labeled")); - assertThat(discussionPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); - assertThat(discussionPayload.getSender().getLogin(), is("gsmet")); - - GHRepositoryDiscussion discussion = discussionPayload.getDiscussion(); - - GHRepositoryDiscussion.Category category = discussion.getCategory(); - - assertThat(category.getId(), is(33522033L)); - assertThat(category.getNodeId(), is("DIC_kwDOEq3cwc4B_4Fx")); - assertThat(category.getEmoji(), is(":pray:")); - assertThat(category.getName(), is("Q&A")); - assertThat(category.getDescription(), is("Ask the community for help")); - assertThat(category.getCreatedAt().getTime(), is(1636991431000L)); - assertThat(category.getUpdatedAt().getTime(), is(1636991431000L)); - assertThat(category.getSlug(), is("q-a")); - assertThat(category.isAnswerable(), is(true)); - - assertThat(discussion.getAnswerHtmlUrl(), is(nullValue())); - assertThat(discussion.getAnswerChosenAt(), is(nullValue())); - assertThat(discussion.getAnswerChosenBy(), is(nullValue())); - - assertThat(discussion.getHtmlUrl().toString(), - is("https://github.com/gsmet/quarkus-bot-java-playground/discussions/78")); - assertThat(discussion.getId(), is(3698909L)); - assertThat(discussion.getNodeId(), is("D_kwDOEq3cwc4AOHDd")); - assertThat(discussion.getNumber(), is(78)); - assertThat(discussion.getTitle(), is("Title of discussion")); - - assertThat(discussion.getUser().getLogin(), is("gsmet")); - assertThat(discussion.getUser().getId(), is(1279749L)); - assertThat(discussion.getUser().getNodeId(), is("MDQ6VXNlcjEyNzk3NDk=")); + public void push() throws Exception { + final GHEventPayload.Push event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Push.class); + assertThat(event.getRef(), is("refs/heads/changes")); + assertThat(event.getBefore(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); + assertThat(event.getHead(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c")); + assertThat(event.isCreated(), is(false)); + assertThat(event.isDeleted(), is(false)); + assertThat(event.isForced(), is(false)); + assertThat(event.getCommits().size(), is(1)); + assertThat(event.getCommits().get(0).getSha(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c")); + assertThat(event.getCommits().get(0).getAuthor().getEmail(), is("baxterthehacker@users.noreply.github.com")); + assertThat(event.getCommits().get(0).getAuthor().getUsername(), is("baxterthehacker")); + assertThat(event.getCommits().get(0).getCommitter().getEmail(), is("baxterthehacker@users.noreply.github.com")); + assertThat(event.getCommits().get(0).getCommitter().getUsername(), is("baxterthehacker")); + assertThat(event.getCommits().get(0).getAdded().size(), is(0)); + assertThat(event.getCommits().get(0).getRemoved().size(), is(0)); + assertThat(event.getCommits().get(0).getModified().size(), is(1)); + assertThat(event.getCommits().get(0).getModified().get(0), is("README.md")); - assertThat(discussion.getState(), is(GHRepositoryDiscussion.State.OPEN)); - assertThat(discussion.isLocked(), is(false)); - assertThat(discussion.getComments(), is(0)); - assertThat(discussion.getCreatedAt().getTime(), is(1637584949000L)); - assertThat(discussion.getUpdatedAt().getTime(), is(1637584961000L)); - assertThat(discussion.getAuthorAssociation(), is(GHCommentAuthorAssociation.OWNER)); - assertThat(discussion.getActiveLockReason(), is(nullValue())); - assertThat(discussion.getBody(), is("Body of discussion.")); + assertThat(event.getHeadCommit().getSha(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c")); + assertThat(event.getHeadCommit().getAuthor().getEmail(), is("baxterthehacker@users.noreply.github.com")); + assertThat(event.getHeadCommit().getAuthor().getUsername(), is("baxterthehacker")); + assertThat(event.getHeadCommit().getCommitter().getEmail(), is("baxterthehacker@users.noreply.github.com")); + assertThat(event.getHeadCommit().getCommitter().getUsername(), is("baxterthehacker")); + assertThat(event.getHeadCommit().getAdded().size(), is(0)); + assertThat(event.getHeadCommit().getRemoved().size(), is(0)); + assertThat(event.getHeadCommit().getModified().size(), is(1)); + assertThat(event.getHeadCommit().getModified().get(0), is("README.md")); + assertThat(event.getHeadCommit().getMessage(), is("Update README.md")); - GHLabel label = discussionPayload.getLabel(); - assertThat(label.getId(), is(2543373314L)); - assertThat(label.getNodeId(), is("MDU6TGFiZWwyNTQzMzczMzE0")); - assertThat(label.getUrl().toString(), - is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/labels/area/hibernate-validator")); - assertThat(label.getName(), is("area/hibernate-validator")); - assertThat(label.getColor(), is("ededed")); - assertThat(label.isDefault(), is(false)); - assertThat(label.getDescription(), is(nullValue())); + assertThat(GitHubClient.printInstant(event.getCommits().get(0).getTimestamp()), is("2015-05-05T23:40:15Z")); + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwnerName(), is("baxterthehacker")); + assertThat(event.getRepository().getUrl().toExternalForm(), + is("https://github.com/baxterthehacker/public-repo")); + assertThat(event.getPusher().getName(), is("baxterthehacker")); + assertThat(event.getPusher().getEmail(), is("baxterthehacker@users.noreply.github.com")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); + assertThat(event.getCompare(), + is("https://github.com/baxterthehacker/public-repo/compare/9049f1265b7d...0d1a26e67d8f")); } /** - * Discussion comment created. + * Push to fork. * * @throws Exception * the exception */ @Test - public void discussion_comment_created() throws Exception { - final GHEventPayload.DiscussionComment discussionCommentPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.DiscussionComment.class); - - assertThat(discussionCommentPayload.getAction(), is("created")); - assertThat(discussionCommentPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); - assertThat(discussionCommentPayload.getSender().getLogin(), is("gsmet")); - - GHRepositoryDiscussion discussion = discussionCommentPayload.getDiscussion(); - - GHRepositoryDiscussion.Category category = discussion.getCategory(); - - assertThat(category.getId(), is(33522033L)); - assertThat(category.getNodeId(), is("DIC_kwDOEq3cwc4B_4Fx")); - assertThat(category.getEmoji(), is(":pray:")); - assertThat(category.getName(), is("Q&A")); - assertThat(category.getDescription(), is("Ask the community for help")); - assertThat(category.getCreatedAt().getTime(), is(1636991431000L)); - assertThat(category.getUpdatedAt().getTime(), is(1636991431000L)); - assertThat(category.getSlug(), is("q-a")); - assertThat(category.isAnswerable(), is(true)); - - assertThat(discussion.getAnswerHtmlUrl(), is(nullValue())); - assertThat(discussion.getAnswerChosenAt(), is(nullValue())); - assertThat(discussion.getAnswerChosenBy(), is(nullValue())); - - assertThat(discussion.getHtmlUrl().toString(), - is("https://github.com/gsmet/quarkus-bot-java-playground/discussions/162")); - assertThat(discussion.getId(), is(6090566L)); - assertThat(discussion.getNodeId(), is("D_kwDOEq3cwc4AXO9G")); - assertThat(discussion.getNumber(), is(162)); - assertThat(discussion.getTitle(), is("New test question")); - - assertThat(discussion.getUser().getLogin(), is("gsmet")); - assertThat(discussion.getUser().getId(), is(1279749L)); - assertThat(discussion.getUser().getNodeId(), is("MDQ6VXNlcjEyNzk3NDk=")); - - assertThat(discussion.getState(), is(GHRepositoryDiscussion.State.OPEN)); - assertThat(discussion.isLocked(), is(false)); - assertThat(discussion.getComments(), is(1)); - assertThat(discussion.getCreatedAt().getTime(), is(1705586390000L)); - assertThat(discussion.getUpdatedAt().getTime(), is(1705586399000L)); - assertThat(discussion.getAuthorAssociation(), is(GHCommentAuthorAssociation.OWNER)); - assertThat(discussion.getActiveLockReason(), is(nullValue())); - assertThat(discussion.getBody(), is("Test question")); + @Payload("push.fork") + public void pushToFork() throws Exception { + gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()).build(); - GHRepositoryDiscussionComment comment = discussionCommentPayload.getComment(); + final GHEventPayload.Push event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Push.class); + assertThat(event.getRef(), is("refs/heads/changes")); + assertThat(event.getBefore(), is("85c44b352958bf6d81b74ab8b21920f1d313a287")); + assertThat(event.getHead(), is("1393706f1364742defbc28ba459082630ca979af")); + assertThat(event.isCreated(), is(false)); + assertThat(event.isDeleted(), is(false)); + assertThat(event.isForced(), is(false)); + assertThat(event.getCommits().size(), is(1)); + assertThat(event.getCommits().get(0).getSha(), is("1393706f1364742defbc28ba459082630ca979af")); + assertThat(event.getCommits().get(0).getAuthor().getEmail(), is("bitwiseman@gmail.com")); + assertThat(event.getCommits().get(0).getCommitter().getEmail(), is("bitwiseman@gmail.com")); + assertThat(event.getCommits().get(0).getAdded().size(), is(6)); + assertThat(event.getCommits().get(0).getRemoved().size(), is(0)); + assertThat(event.getCommits().get(0).getModified().size(), is(2)); + assertThat(event.getCommits().get(0).getModified().get(0), + is("src/main/java/org/kohsuke/github/GHLicense.java")); + assertThat(event.getRepository().getName(), is("github-api")); + assertThat(event.getRepository().getOwnerName(), is("hub4j-test-org")); + assertThat(event.getRepository().getUrl().toExternalForm(), is("https://github.com/hub4j-test-org/github-api")); + assertThat(event.getPusher().getName(), is("bitwiseman")); + assertThat(event.getPusher().getEmail(), is("bitwiseman@gmail.com")); + assertThat(event.getSender().getLogin(), is("bitwiseman")); - assertThat(comment.getHtmlUrl().toString(), - is("https://github.com/gsmet/quarkus-bot-java-playground/discussions/162#discussioncomment-8169669")); - assertThat(comment.getId(), is(8169669L)); - assertThat(comment.getNodeId(), is("DC_kwDOEq3cwc4AfKjF")); - assertThat(comment.getAuthorAssociation(), is(GHCommentAuthorAssociation.OWNER)); - assertThat(comment.getCreatedAt().getTime(), is(1705586398000L)); - assertThat(comment.getUpdatedAt().getTime(), is(1705586399000L)); - assertThat(comment.getBody(), is("Test comment.")); - assertThat(comment.getUser().getLogin(), is("gsmet")); - assertThat(comment.getUser().getId(), is(1279749L)); - assertThat(comment.getUser().getNodeId(), is("MDQ6VXNlcjEyNzk3NDk=")); - assertThat(comment.getParentId(), is(nullValue())); - assertThat(comment.getChildCommentCount(), is(0)); - } + assertThat(event.getRepository().isFork(), is(true)); - /** - * Starred. - * - * @throws Exception - * the exception - */ - @Test - public void starred() throws Exception { - final GHEventPayload.Star starPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Star.class); + // in offliine mode, we should not populate missing fields + assertThat(event.getRepository().getSource(), is(nullValue())); + assertThat(event.getRepository().getParent(), is(nullValue())); - assertThat(starPayload.getAction(), is("created")); - assertThat(starPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); - assertThat(starPayload.getSender().getLogin(), is("gsmet")); - assertThat(starPayload.getStarredAt().getTime(), is(1654017876000L)); - } + assertThat(event.getRepository().getUrl().toString(), is("https://github.com/hub4j-test-org/github-api")); + assertThat(event.getRepository().getHttpTransportUrl().toString(), + is("https://github.com/hub4j-test-org/github-api.git")); - /** - * Projectsv 2 item created. - * - * @throws Exception - * the exception - */ - @Test - public void projectsv2item_created() throws Exception { - final GHEventPayload.ProjectsV2Item projectsV2ItemPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.ProjectsV2Item.class); + // Test repository populate + final GHEventPayload.Push event2 = gitHub.parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), + GHEventPayload.Push.class); + assertThat(event2.getRepository().getUrl().toString(), is("https://github.com/hub4j-test-org/github-api")); + assertThat(event2.getRepository().getHttpTransportUrl(), + is("https://github.com/hub4j-test-org/github-api.git")); - assertThat(projectsV2ItemPayload.getAction(), is("created")); + event2.getRepository().populate(); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getId(), is(8083254L)); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getNodeId(), is("PVTI_lADOBNft-M4AEjBWzgB7VzY")); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getProjectNodeId(), is("PVT_kwDOBNft-M4AEjBW")); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getContentNodeId(), is("I_kwDOFOkjw85Ozz26")); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getContentType(), is(ContentType.ISSUE)); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreator().getLogin(), is("gsmet")); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreator().getNodeId(), is("MDQ6VXNlcjEyNzk3NDk=")); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreatedAt().getTime(), is(1659532028000L)); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getUpdatedAt().getTime(), is(1659532028000L)); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getArchivedAt(), is(nullValue())); + // After populate the url is fixed to point to the correct API endpoint + assertThat(event2.getRepository().getUrl().toString(), + is(mockGitHub.apiServer().baseUrl() + "/repos/hub4j-test-org/github-api")); + assertThat(event2.getRepository().getHttpTransportUrl(), + is("https://github.com/hub4j-test-org/github-api.git")); - assertThat(projectsV2ItemPayload.getOrganization().getLogin(), is("gsmet-bot-playground")); - assertThat(projectsV2ItemPayload.getOrganization().getId(), is(81260024L)); - assertThat(projectsV2ItemPayload.getOrganization().getNodeId(), is("MDEyOk9yZ2FuaXphdGlvbjgxMjYwMDI0")); + // ensure that root has been bound after populate + event2.getRepository().getSource().getRef("heads/main"); + event2.getRepository().getParent().getRef("heads/main"); - assertThat(projectsV2ItemPayload.getSender().getLogin(), is("gsmet")); - assertThat(projectsV2ItemPayload.getSender().getId(), is(1279749L)); - assertThat(projectsV2ItemPayload.getSender().getNodeId(), is("MDQ6VXNlcjEyNzk3NDk=")); + // Source + final GHEventPayload.Push event3 = gitHub.parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), + GHEventPayload.Push.class); + assertThat(event3.getRepository().getSource().getFullName(), is("hub4j/github-api")); + + // Parent + final GHEventPayload.Push event4 = gitHub.parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), + GHEventPayload.Push.class); + assertThat(event4.getRepository().getParent().getFullName(), is("hub4j/github-api")); - assertThat(projectsV2ItemPayload.getInstallation().getId(), is(16779846L)); - assertThat(projectsV2ItemPayload.getInstallation().getNodeId(), - is("MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTY3Nzk4NDY=")); } /** - * Projectsv 2 item edited. + * Release published. * * @throws Exception * the exception */ @Test - public void projectsv2item_edited() throws Exception { - final GHEventPayload.ProjectsV2Item projectsV2ItemPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.ProjectsV2Item.class); - - assertThat(projectsV2ItemPayload.getAction(), is("edited")); - - assertThat(projectsV2ItemPayload.getProjectsV2Item().getId(), is(8083254L)); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreatedAt().getTime(), is(1659532028000L)); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getUpdatedAt().getTime(), is(1659532033000L)); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getArchivedAt(), is(nullValue())); + public void release_published() throws Exception { + final GHEventPayload.Release event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Release.class); - assertThat(projectsV2ItemPayload.getChanges().getFieldValue().getFieldNodeId(), - is("PVTF_lADOBNft-M4AEjBWzgCnp5Q")); - assertThat(projectsV2ItemPayload.getChanges().getFieldValue().getFieldType(), is(FieldType.SINGLE_SELECT)); + assertThat(event.getAction(), is("published")); + assertThat(event.getSender().getLogin(), is("seregamorph")); + assertThat(event.getRepository().getName(), is("company-rest-api-framework")); + assertThat(event.getOrganization().getLogin(), is("company-group")); + assertThat(event.getInstallation(), nullValue()); + assertThat(event.getRelease().getName(), is("4.2")); + assertThat(event.getRelease().getTagName(), is("rest-api-framework-4.2")); + assertThat(event.getRelease().getBody(), is("REST-269 - unique test executions (#86) Sergey Chernov")); } /** - * Projectsv 2 item archived. + * Repository. * * @throws Exception * the exception */ @Test - public void projectsv2item_archived() throws Exception { - final GHEventPayload.ProjectsV2Item projectsV2ItemPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.ProjectsV2Item.class); - - assertThat(projectsV2ItemPayload.getAction(), is("archived")); - - assertThat(projectsV2ItemPayload.getProjectsV2Item().getId(), is(8083794L)); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreatedAt().getTime(), is(1659532431000L)); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getUpdatedAt().getTime(), is(1660086629000L)); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getArchivedAt().getTime(), is(1660086629000L)); - - assertThat(projectsV2ItemPayload.getChanges().getArchivedAt().getFrom(), is(nullValue())); - assertThat(projectsV2ItemPayload.getChanges().getArchivedAt().getTo().getTime(), is(1660086629000L)); + public void repository() throws Exception { + final GHEventPayload.Repository event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Repository.class); + assertThat(event.getAction(), is("created")); + assertThat(event.getRepository().getName(), is("new-repository")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterandthehackers")); + assertThat(event.getOrganization().getLogin(), is("baxterandthehackers")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); } /** - * Projectsv 2 item restored. + * Repository renamed. * * @throws Exception * the exception */ @Test - public void projectsv2item_restored() throws Exception { - final GHEventPayload.ProjectsV2Item projectsV2ItemPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.ProjectsV2Item.class); - - assertThat(projectsV2ItemPayload.getAction(), is("restored")); - - assertThat(projectsV2ItemPayload.getProjectsV2Item().getId(), is(8083254L)); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreatedAt().getTime(), is(1659532028000L)); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getUpdatedAt().getTime(), is(1659532419000L)); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getArchivedAt(), is(nullValue())); - - assertThat(projectsV2ItemPayload.getChanges().getArchivedAt().getFrom().getTime(), is(1659532142000L)); - assertThat(projectsV2ItemPayload.getChanges().getArchivedAt().getTo(), is(nullValue())); + public void repository_renamed() throws Exception { + final GHEventPayload.Repository event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Repository.class); + assertThat(event.getAction(), is("renamed")); + assertThat(event.getChanges().getRepository().getName().getFrom(), is("react-workshop")); + assertThat(event.getRepository().getName(), is("react-workshop-renamed")); + assertThat(event.getRepository().getOwner().getLogin(), is("EJG-Organization")); + assertThat(event.getOrganization().getLogin(), is("EJG-Organization")); + assertThat(event.getSender().getLogin(), is("eleanorgoh")); } /** - * Projectsv 2 item reordered. + * Repository ownership transferred to an organization. * * @throws Exception * the exception */ @Test - public void projectsv2item_reordered() throws Exception { - final GHEventPayload.ProjectsV2Item projectsV2ItemPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.ProjectsV2Item.class); - - assertThat(projectsV2ItemPayload.getAction(), is("reordered")); - - assertThat(projectsV2ItemPayload.getProjectsV2Item().getId(), is(8083794L)); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreatedAt().getTime(), is(1659532431000L)); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getUpdatedAt().getTime(), is(1659532439000L)); - assertThat(projectsV2ItemPayload.getProjectsV2Item().getArchivedAt(), is(nullValue())); - - assertThat(projectsV2ItemPayload.getChanges().getPreviousProjectsV2ItemNodeId().getFrom(), - is("PVTI_lADOBNft-M4AEjBWzgB7VzY")); - assertThat(projectsV2ItemPayload.getChanges().getPreviousProjectsV2ItemNodeId().getTo(), - is("PVTI_lADOBNft-M4AEjBWzgB7VzY")); + public void repository_transferred_to_org() throws Exception { + final GHEventPayload.Repository event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Repository.class); + assertThat(event.getAction(), is("transferred")); + assertThat(event.getChanges().getOwner().getFrom().getUser().getLogin(), is("eleanorgoh")); + assertThat(event.getChanges().getOwner().getFrom().getUser().getId(), is(66235606L)); + assertThat(event.getChanges().getOwner().getFrom().getUser().getType(), is("User")); } /** - * Membership added. + * Repository ownership transferred to a user. * * @throws Exception * the exception */ @Test - public void membership_added() throws Exception { - final GHEventPayload.Membership membershipPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Membership.class); - - assertThat(membershipPayload.getAction(), is("added")); - - assertThat(membershipPayload.getOrganization().getLogin(), is("gsmet-bot-playground")); - - GHUser member = membershipPayload.getMember(); - assertThat(member.getId(), is(1279749L)); - assertThat(member.getLogin(), is("gsmet")); - - GHTeam team = membershipPayload.getTeam(); - assertThat(team.getId(), is(9709063L)); - assertThat(team.getName(), is("New team")); - assertThat(team.getNodeId(), is("T_kwDOBNft-M4AlCYH")); - assertThat(team.getDescription(), is("Description")); - assertThat(team.getPrivacy(), is(Privacy.CLOSED)); - assertThat(team.getOrganization().getLogin(), is("gsmet-bot-playground")); + public void repository_transferred_to_user() throws Exception { + final GHEventPayload.Repository event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Repository.class); + assertThat(event.getAction(), is("transferred")); + assertThat(event.getChanges().getOwner().getFrom().getOrganization().getLogin(), is("EJG-Organization")); + assertThat(event.getChanges().getOwner().getFrom().getOrganization().getId(), is(168135412L)); + assertThat(event.getRepository().getOwner().getLogin(), is("eleanorgoh")); + assertThat(event.getRepository().getOwner().getType(), is("User")); } /** - * Member edited. + * Starred. * * @throws Exception * the exception */ @Test - public void member_edited() throws Exception { - final GHEventPayload.Member memberPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Member.class); - - assertThat(memberPayload.getAction(), is("edited")); - - assertThat(memberPayload.getOrganization().getLogin(), is("gsmet-bot-playground")); - assertThat(memberPayload.getRepository().getName(), is("github-automation-with-quarkus-demo-playground")); - - GHUser member = memberPayload.getMember(); - assertThat(member.getId(), is(412878L)); - assertThat(member.getLogin(), is("yrodiere")); + public void starred() throws Exception { + final GHEventPayload.Star starPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Star.class); - GHMemberChanges changes = memberPayload.getChanges(); - assertThat(changes.getPermission().getFrom(), is("admin")); - assertThat(changes.getPermission().getTo(), is("triage")); + assertThat(starPayload.getAction(), is("created")); + assertThat(starPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); + assertThat(starPayload.getSender().getLogin(), is("gsmet")); + assertThat(starPayload.getStarredAt().toEpochMilli(), is(1654017876000L)); } /** - * Member added. + * Status. * * @throws Exception * the exception */ @Test - public void member_added() throws Exception { - final GHEventPayload.Member memberPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Member.class); - - assertThat(memberPayload.getAction(), is("added")); - - assertThat(memberPayload.getOrganization().getLogin(), is("gsmet-bot-playground")); - assertThat(memberPayload.getRepository().getName(), is("github-automation-with-quarkus-demo-playground")); - - GHUser member = memberPayload.getMember(); - assertThat(member.getId(), is(412878L)); - assertThat(member.getLogin(), is("yrodiere")); - - GHMemberChanges changes = memberPayload.getChanges(); - assertThat(changes.getPermission().getFrom(), is(nullValue())); - assertThat(changes.getPermission().getTo(), is("admin")); + public void status() throws Exception { + final GHEventPayload.Status event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Status.class); + assertThat(event.getContext(), is("default")); + assertThat(event.getDescription(), is("status description")); + assertThat(event.getState(), is(GHCommitState.SUCCESS)); + assertThat(event.getCommit().getSHA1(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + assertThat(event.getTargetUrl(), nullValue()); + assertThat(event.getCommit().getOwner(), sameInstance(event.getRepository())); } /** - * Member added with role name defined. + * Status 2. * * @throws Exception * the exception */ @Test - public void member_added_role_name() throws Exception { - final GHEventPayload.Member memberPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Member.class); - - assertThat(memberPayload.getAction(), is("added")); - - assertThat(memberPayload.getOrganization().getLogin(), is("gsmet-bot-playground")); - assertThat(memberPayload.getRepository().getName(), is("github-automation-with-quarkus-demo-playground")); - - GHUser member = memberPayload.getMember(); - assertThat(member.getId(), is(412878L)); - assertThat(member.getLogin(), is("yrodiere")); + public void status2() throws Exception { + final GHEventPayload.Status event = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Status.class); + assertThat(event.getTargetUrl(), is("https://www.wikipedia.org/")); - GHMemberChanges changes = memberPayload.getChanges(); - assertThat(changes.getPermission().getFrom(), is(nullValue())); - assertThat(changes.getPermission().getTo(), is("write")); - assertThat(changes.getRoleName().getTo(), is("maintain")); + assertThat(event.getCommit().getOwner(), sameInstance(event.getRepository())); } /** @@ -1849,27 +1614,60 @@ public void team_created() throws Exception { * the exception */ @Test - public void team_edited_description() throws Exception { + public void team_edited_description() throws Exception { + final GHEventPayload.Team teamPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.Team.class); + + assertThat(teamPayload.getAction(), is("edited")); + + assertThat(teamPayload.getOrganization().getLogin(), is("gsmet-bot-playground")); + + GHTeam team = teamPayload.getTeam(); + assertThat(team.getId(), is(9709063L)); + assertThat(team.getName(), is("New team")); + assertThat(team.getNodeId(), is("T_kwDOBNft-M4AlCYH")); + assertThat(team.getDescription(), is("New description")); + assertThat(team.getPrivacy(), is(Privacy.CLOSED)); + assertThat(team.getOrganization().getLogin(), is("gsmet-bot-playground")); + + GHTeamChanges changes = teamPayload.getChanges(); + assertThat(changes.getDescription().getFrom(), is("Description")); + assertThat(changes.getName(), is(nullValue())); + assertThat(changes.getPrivacy(), is(nullValue())); + assertThat(changes.getRepository(), is(nullValue())); + } + + /** + * Team edited repository permission. + * + * @throws Exception + * the exception + */ + @Test + public void team_edited_permission() throws Exception { final GHEventPayload.Team teamPayload = GitHub.offline() .parseEventPayload(payload.asReader(), GHEventPayload.Team.class); assertThat(teamPayload.getAction(), is("edited")); assertThat(teamPayload.getOrganization().getLogin(), is("gsmet-bot-playground")); + assertThat(teamPayload.getRepository().getName(), is("github-automation-with-quarkus-demo-app")); GHTeam team = teamPayload.getTeam(); assertThat(team.getId(), is(9709063L)); assertThat(team.getName(), is("New team")); assertThat(team.getNodeId(), is("T_kwDOBNft-M4AlCYH")); assertThat(team.getDescription(), is("New description")); - assertThat(team.getPrivacy(), is(Privacy.CLOSED)); + assertThat(team.getPrivacy(), is(Privacy.SECRET)); assertThat(team.getOrganization().getLogin(), is("gsmet-bot-playground")); GHTeamChanges changes = teamPayload.getChanges(); - assertThat(changes.getDescription().getFrom(), is("Description")); + assertThat(changes.getDescription(), is(nullValue())); assertThat(changes.getName(), is(nullValue())); assertThat(changes.getPrivacy(), is(nullValue())); - assertThat(changes.getRepository(), is(nullValue())); + assertThat(changes.getRepository().getPermissions().hadPushAccess(), is(false)); + assertThat(changes.getRepository().getPermissions().hadPullAccess(), is(true)); + assertThat(changes.getRepository().getPermissions().hadAdminAccess(), is(false)); } /** @@ -1903,35 +1701,247 @@ public void team_edited_visibility() throws Exception { } /** - * Team edited repository permission. + * Workflow dispatch. * * @throws Exception * the exception */ @Test - public void team_edited_permission() throws Exception { - final GHEventPayload.Team teamPayload = GitHub.offline() - .parseEventPayload(payload.asReader(), GHEventPayload.Team.class); + public void workflow_dispatch() throws Exception { + final GHEventPayload.WorkflowDispatch workflowDispatchPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.WorkflowDispatch.class); - assertThat(teamPayload.getAction(), is("edited")); + assertThat(workflowDispatchPayload.getRef(), is("refs/heads/main")); + assertThat(workflowDispatchPayload.getAction(), is(nullValue())); + assertThat(workflowDispatchPayload.getWorkflow(), is(".github/workflows/main.yml")); + assertThat(workflowDispatchPayload.getInputs(), aMapWithSize(1)); + assertThat(workflowDispatchPayload.getInputs().keySet(), contains("logLevel")); + assertThat(workflowDispatchPayload.getInputs().values(), contains("warning")); + assertThat(workflowDispatchPayload.getRepository().getName(), is("quarkus-bot-java-playground")); + assertThat(workflowDispatchPayload.getSender().getLogin(), is("gsmet")); + } - assertThat(teamPayload.getOrganization().getLogin(), is("gsmet-bot-playground")); - assertThat(teamPayload.getRepository().getName(), is("github-automation-with-quarkus-demo-app")); + /** + * Workflow job. + * + * @throws Exception + * the exception + */ + @Test + public void workflow_job() throws Exception { + final GHEventPayload.WorkflowJob workflowJobPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.WorkflowJob.class); - GHTeam team = teamPayload.getTeam(); - assertThat(team.getId(), is(9709063L)); - assertThat(team.getName(), is("New team")); - assertThat(team.getNodeId(), is("T_kwDOBNft-M4AlCYH")); - assertThat(team.getDescription(), is("New description")); - assertThat(team.getPrivacy(), is(Privacy.SECRET)); - assertThat(team.getOrganization().getLogin(), is("gsmet-bot-playground")); + assertThat(workflowJobPayload.getAction(), is("completed")); + assertThat(workflowJobPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); + assertThat(workflowJobPayload.getSender().getLogin(), is("gsmet")); - GHTeamChanges changes = teamPayload.getChanges(); - assertThat(changes.getDescription(), is(nullValue())); - assertThat(changes.getName(), is(nullValue())); - assertThat(changes.getPrivacy(), is(nullValue())); - assertThat(changes.getRepository().getPermissions().hadPushAccess(), is(false)); - assertThat(changes.getRepository().getPermissions().hadPullAccess(), is(true)); - assertThat(changes.getRepository().getPermissions().hadAdminAccess(), is(false)); + GHWorkflowJob workflowJob = workflowJobPayload.getWorkflowJob(); + assertThat(workflowJob.getId(), is(6653410527L)); + assertThat(workflowJob.getRunId(), is(2408553341L)); + assertThat(workflowJob.getRunAttempt(), is(1)); + assertThat(workflowJob.getUrl().toString(), + is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/actions/jobs/6653410527")); + assertThat(workflowJob.getHtmlUrl().toString(), + is("https://github.com/gsmet/quarkus-bot-java-playground/runs/6653410527?check_suite_focus=true")); + assertThat(workflowJob.getNodeId(), is("CR_kwDOEq3cwc8AAAABjJL83w")); + assertThat(workflowJob.getHeadSha(), is("5dd2dadfbdc2a722c08a8ad42ae4e26e3e731042")); + assertThat(workflowJob.getStatus(), is(GHWorkflowRun.Status.COMPLETED)); + assertThat(workflowJob.getConclusion(), is(GHWorkflowRun.Conclusion.FAILURE)); + assertThat(workflowJob.getStartedAt().toEpochMilli(), is(1653908125000L)); + assertThat(workflowJob.getCompletedAt().toEpochMilli(), is(1653908157000L)); + assertThat(workflowJob.getName(), is("JVM Tests - JDK JDK16")); + assertThat(workflowJob.getSteps(), + contains(hasProperty("name", is("Set up job")), + hasProperty("name", is("Run actions/checkout@v2")), + hasProperty("name", is("Build with Maven")), + hasProperty("name", is("Post Run actions/checkout@v2")), + hasProperty("name", is("Complete job")))); + assertThat(workflowJob.getCheckRunUrl().toString(), + is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/check-runs/6653410527")); + } + + /** + * Workflow run. + * + * @throws Exception + * the exception + */ + @Test + public void workflow_run() throws Exception { + final GHEventPayload.WorkflowRun workflowRunPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.WorkflowRun.class); + + assertThat(workflowRunPayload.getAction(), is("completed")); + assertThat(workflowRunPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); + assertThat(workflowRunPayload.getSender().getLogin(), is("gsmet")); + + GHWorkflow workflow = workflowRunPayload.getWorkflow(); + assertThat(workflow.getId(), is(7087581L)); + assertThat(workflow.getName(), is("CI")); + assertThat(workflow.getPath(), is(".github/workflows/main.yml")); + assertThat(workflow.getState(), is("active")); + assertThat(workflow.getUrl().toString(), + is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/actions/workflows/7087581")); + assertThat(workflow.getHtmlUrl().toString(), + is("https://github.com/gsmet/quarkus-bot-java-playground/blob/main/.github/workflows/main.yml")); + assertThat(workflow.getBadgeUrl().toString(), + is("https://github.com/gsmet/quarkus-bot-java-playground/workflows/CI/badge.svg")); + + GHWorkflowRun workflowRun = workflowRunPayload.getWorkflowRun(); + assertThat(workflowRun.getId(), is(680604745L)); + assertThat(workflowRun.getName(), is("CI")); + assertThat(workflowRun.getHeadBranch(), is("main")); + assertThat(workflowRun.getDisplayTitle(), is("its-display-title")); + assertThat(workflowRun.getHeadSha(), is("dbea8d8b6ed2cf764dfd84a215f3f9040b3d4423")); + assertThat(workflowRun.getRunNumber(), is(6L)); + assertThat(workflowRun.getEvent(), is(GHEvent.WORKFLOW_DISPATCH)); + assertThat(workflowRun.getStatus(), is(GHWorkflowRun.Status.COMPLETED)); + assertThat(workflowRun.getConclusion(), is(GHWorkflowRun.Conclusion.SUCCESS)); + assertThat(workflowRun.getWorkflowId(), is(7087581L)); + assertThat(workflowRun.getUrl().toString(), + is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/actions/runs/680604745")); + assertThat(workflowRun.getHtmlUrl().toString(), + is("https://github.com/gsmet/quarkus-bot-java-playground/actions/runs/680604745")); + assertThat(workflowRun.getJobsUrl().toString(), + is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/actions/runs/680604745/jobs")); + assertThat(workflowRun.getLogsUrl().toString(), + is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/actions/runs/680604745/logs")); + assertThat(workflowRun.getCheckSuiteUrl().toString(), + is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/check-suites/2327154397")); + assertThat(workflowRun.getArtifactsUrl().toString(), + is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/actions/runs/680604745/artifacts")); + assertThat(workflowRun.getCancelUrl().toString(), + is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/actions/runs/680604745/cancel")); + assertThat(workflowRun.getRerunUrl().toString(), + is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/actions/runs/680604745/rerun")); + assertThat(workflowRun.getWorkflowUrl().toString(), + is("https://api.github.com/repos/gsmet/quarkus-bot-java-playground/actions/workflows/7087581")); + assertThat(workflowRun.getCreatedAt().toEpochMilli(), is(1616524526000L)); + assertThat(workflowRun.getUpdatedAt().toEpochMilli(), is(1616524543000L)); + assertThat(workflowRun.getRunAttempt(), is(1L)); + assertThat(workflowRun.getRunStartedAt().toEpochMilli(), is(1616524526000L)); + assertThat(workflowRun.getHeadCommit().getId(), is("dbea8d8b6ed2cf764dfd84a215f3f9040b3d4423")); + assertThat(workflowRun.getHeadCommit().getTreeId(), is("b17089e6a2574ec1002566fe980923e62dce3026")); + assertThat(workflowRun.getHeadCommit().getMessage(), is("Update main.yml")); + assertThat(workflowRun.getHeadCommit().getTimestamp().toEpochMilli(), is(1616523390000L)); + assertThat(workflowRun.getHeadCommit().getAuthor().getName(), is("Guillaume Smet")); + assertThat(workflowRun.getHeadCommit().getAuthor().getEmail(), is("guillaume.smet@gmail.com")); + assertThat(workflowRun.getHeadCommit().getCommitter().getName(), is("GitHub")); + assertThat(workflowRun.getHeadCommit().getCommitter().getEmail(), is("noreply@github.com")); + assertThat(workflowRun.getHeadRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); + assertThat(workflowRun.getRepository(), sameInstance(workflowRunPayload.getRepository())); + } + + /** + * Workflow run other repository. + * + * @throws Exception + * the exception + */ + @Test + public void workflow_run_other_repository() throws Exception { + final GHEventPayload.WorkflowRun workflowRunPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.WorkflowRun.class); + GHWorkflowRun workflowRun = workflowRunPayload.getWorkflowRun(); + + assertThat(workflowRunPayload.getRepository().getFullName(), is("gsmet/quarkus-bot-java-playground")); + assertThat(workflowRun.getHeadRepository().getFullName(), + is("gsmet-bot-playground/quarkus-bot-java-playground")); + assertThat(workflowRun.getRepository(), sameInstance(workflowRunPayload.getRepository())); + assertThat(workflowRunPayload.getWorkflow().getRepository(), sameInstance(workflowRunPayload.getRepository())); + } + + /** + * Workflow run pull request. + * + * @throws Exception + * the exception + */ + @Test + public void workflow_run_pull_request() throws Exception { + final GHEventPayload.WorkflowRun workflowRunPayload = GitHub.offline() + .parseEventPayload(payload.asReader(), GHEventPayload.WorkflowRun.class); + + List pullRequests = workflowRunPayload.getWorkflowRun().getPullRequests(); + assertThat(pullRequests.size(), is(1)); + + GHPullRequest pullRequest = pullRequests.get(0); + assertThat(pullRequest.getId(), is(599098265L)); + assertThat(pullRequest.getRepository(), sameInstance(workflowRunPayload.getRepository())); + } + + private GHCheckRun verifyBasicCheckRunEvent(final GHEventPayload.CheckRun event) throws IOException { + assertThat(event.getRepository().getName(), is("Hello-World")); + assertThat(event.getRepository().getOwner().getLogin(), is("Codertocat")); + assertThat(event.getAction(), is("created")); + assertThat(event.getRequestedAction(), nullValue()); + + // Checks the deserialization of check_run + final GHCheckRun checkRun = event.getCheckRun(); + assertThat(checkRun.getName(), is("Octocoders-linter")); + assertThat(checkRun.getHeadSha(), is("ec26c3e57ca3a959ca5aad62de7213c562f8c821")); + assertThat(checkRun.getStatus(), is(Status.COMPLETED)); + assertThat(checkRun.getNodeId(), is("MDg6Q2hlY2tSdW4xMjg2MjAyMjg=")); + assertThat(checkRun.getExternalId(), is("")); + + assertThat(GitHubClient.printInstant(checkRun.getStartedAt()), is("2019-05-15T15:21:12Z")); + assertThat(GitHubClient.printInstant(checkRun.getCompletedAt()), is("2019-05-15T20:22:22Z")); + + assertThat(checkRun.getConclusion(), is(Conclusion.SUCCESS)); + assertThat(checkRun.getUrl().toString(), endsWith("/repos/Codertocat/Hello-World/check-runs/128620228")); + assertThat(checkRun.getHtmlUrl().toString(), + endsWith("https://github.com/Codertocat/Hello-World/runs/128620228")); + assertThat(checkRun.getDetailsUrl().toString(), is("https://octocoders.io")); + assertThat(checkRun.getApp().getId(), is(29310L)); + assertThat(checkRun.getCheckSuite().getId(), is(118578147L)); + assertThat(checkRun.getOutput().getTitle(), is("check-run output")); + assertThat(checkRun.getOutput().getSummary(), nullValue()); + assertThat(checkRun.getOutput().getText(), nullValue()); + assertThat(checkRun.getOutput().getAnnotationsCount(), is(0)); + assertThat(checkRun.getOutput().getAnnotationsUrl().toString(), + endsWith("/repos/Codertocat/Hello-World/check-runs/128620228/annotations")); + + // Checks the deserialization of sender + assertThat(event.getSender().getId(), is(21031067L)); + + assertThat(checkRun.getPullRequests(), notNullValue()); + assertThat(checkRun.getPullRequests().size(), equalTo(1)); + assertThat(checkRun.getPullRequests().get(0).getNumber(), equalTo(2)); + return checkRun; + } + + private GHCheckSuite verifyBasicCheckSuiteEvent(final GHEventPayload.CheckSuite event) throws IOException { + assertThat(event.getRepository().getName(), is("Hello-World")); + assertThat(event.getRepository().getOwner().getLogin(), is("Codertocat")); + assertThat(event.getAction(), is("completed")); + assertThat(event.getSender().getId(), is(21031067L)); + + // Checks the deserialization of check_suite + final GHCheckSuite checkSuite = event.getCheckSuite(); + assertThat(checkSuite.getNodeId(), is("MDEwOkNoZWNrU3VpdGUxMTg1NzgxNDc=")); + assertThat(checkSuite.getHeadBranch(), is("changes")); + assertThat(checkSuite.getHeadSha(), is("ec26c3e57ca3a959ca5aad62de7213c562f8c821")); + assertThat(checkSuite.getStatus(), is("completed")); + assertThat(checkSuite.getConclusion(), is("success")); + assertThat(checkSuite.getBefore(), is("6113728f27ae82c7b1a177c8d03f9e96e0adf246")); + assertThat(checkSuite.getAfter(), is("ec26c3e57ca3a959ca5aad62de7213c562f8c821")); + assertThat(checkSuite.getLatestCheckRunsCount(), is(1)); + assertThat(checkSuite.getCheckRunsUrl().toString(), + endsWith("/repos/Codertocat/Hello-World/check-suites/118578147/check-runs")); + assertThat(checkSuite.getHeadCommit().getMessage(), is("Update README.md")); + assertThat(checkSuite.getHeadCommit().getId(), is("ec26c3e57ca3a959ca5aad62de7213c562f8c821")); + assertThat(checkSuite.getHeadCommit().getTreeId(), is("31b122c26a97cf9af023e9ddab94a82c6e77b0ea")); + assertThat(checkSuite.getHeadCommit().getAuthor().getName(), is("Codertocat")); + assertThat(checkSuite.getHeadCommit().getCommitter().getName(), is("Codertocat")); + + assertThat(GitHubClient.printInstant(checkSuite.getHeadCommit().getTimestamp()), is("2019-05-15T15:20:30Z")); + + assertThat(checkSuite.getApp().getId(), is(29310L)); + + assertThat(checkSuite.getPullRequests(), notNullValue()); + assertThat(checkSuite.getPullRequests().size(), equalTo(1)); + assertThat(checkSuite.getPullRequests().get(0).getNumber(), equalTo(2)); + return checkSuite; } } diff --git a/src/test/java/org/kohsuke/github/GHEventTest.java b/src/test/java/org/kohsuke/github/GHEventTest.java index 900063d2b8..5c6f8225ee 100644 --- a/src/test/java/org/kohsuke/github/GHEventTest.java +++ b/src/test/java/org/kohsuke/github/GHEventTest.java @@ -11,12 +11,6 @@ */ public class GHEventTest { - /** - * Create default GHEventTest instance - */ - public GHEventTest() { - } - /** * Function from GHEventInfo to transform string event to GHEvent which has been replaced by static mapping due to * complex parsing logic below @@ -33,6 +27,12 @@ private static GHEvent oldTransformationFunction(String t) { return GHEvent.UNKNOWN; } + /** + * Create default GHEventTest instance + */ + public GHEventTest() { + } + /** * Regression test. */ diff --git a/src/test/java/org/kohsuke/github/GHExternalGroupTest.java b/src/test/java/org/kohsuke/github/GHExternalGroupTest.java index da0696eece..bd6e33156e 100644 --- a/src/test/java/org/kohsuke/github/GHExternalGroupTest.java +++ b/src/test/java/org/kohsuke/github/GHExternalGroupTest.java @@ -23,13 +23,13 @@ public GHExternalGroupTest() { } /** - * Test refresh bound external group. + * Test get organization. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testRefreshBoundExternalGroup() throws IOException { + public void testGetOrganization() throws IOException { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); List groups = org.listExternalGroups().toList(); @@ -37,29 +37,19 @@ public void testRefreshBoundExternalGroup() throws IOException { assertThat(sut, isExternalGroupSummary()); - sut.refresh(); - - assertThat(sut.getId(), equalTo(467431L)); - assertThat(sut.getName(), equalTo("acme-developers")); - assertThat(sut.getUpdatedAt(), notNullValue()); - - assertThat(sut.getMembers(), notNullValue()); - assertThat(membersSummary(sut), - hasItems("158311279:john-doe_acme:John Doe:john.doe@acme.corp", - "166731041:jane-doe_acme:Jane Doe:jane.doe@acme.corp")); + final GHOrganization other = sut.getOrganization(); - assertThat(sut.getTeams(), notNullValue()); - assertThat(teamSummary(sut), hasItems("9891173:ACME-DEVELOPERS")); + assertThat(other, is(org)); } /** - * Test get organization. + * Test refresh bound external group. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testGetOrganization() throws IOException { + public void testRefreshBoundExternalGroup() throws IOException { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); List groups = org.listExternalGroups().toList(); @@ -67,9 +57,19 @@ public void testGetOrganization() throws IOException { assertThat(sut, isExternalGroupSummary()); - final GHOrganization other = sut.getOrganization(); + sut.refresh(); - assertThat(other, is(org)); + assertThat(sut.getId(), equalTo(467431L)); + assertThat(sut.getName(), equalTo("acme-developers")); + assertThat(sut.getUpdatedAt(), notNullValue()); + + assertThat(sut.getMembers(), notNullValue()); + assertThat(membersSummary(sut), + hasItems("158311279:john-doe_acme:John Doe:john.doe@acme.corp", + "166731041:jane-doe_acme:Jane Doe:jane.doe@acme.corp")); + + assertThat(sut.getTeams(), notNullValue()); + assertThat(teamSummary(sut), hasItems("9891173:ACME-DEVELOPERS")); } } diff --git a/src/test/java/org/kohsuke/github/GHGistTest.java b/src/test/java/org/kohsuke/github/GHGistTest.java index 14e02f1c23..76a02871b4 100644 --- a/src/test/java/org/kohsuke/github/GHGistTest.java +++ b/src/test/java/org/kohsuke/github/GHGistTest.java @@ -20,6 +20,33 @@ public class GHGistTest extends AbstractGitHubWireMockTest { public GHGistTest() { } + /** + * Gist file. + * + * @throws Exception + * the exception + */ + @Test + public void gistFile() throws Exception { + GHGist gist = gitHub.getGist("9903708"); + + assertThat(gist.isPublic(), is(true)); + assertThat(gist.getId(), equalTo(9903708L)); + assertThat(gist.getGistId(), equalTo("9903708")); + + assertThat(gist.getFiles().size(), equalTo(1)); + GHGistFile f = gist.getFile("keybase.md"); + + assertThat(f.getType(), equalTo("text/markdown")); + assertThat(f.getLanguage(), equalTo("Markdown")); + assertThat(f.getContent(), containsString("### Keybase proof")); + assertThat(f.getRawUrl().toString(), + equalTo("https://gist.githubusercontent.com/rtyler/9903708/raw/2b68396d836af8c5b6ba905f27c4baf94ceb0ed3/keybase.md")); + assertThat(f.getFileName(), equalTo("keybase.md")); + assertThat(f.getSize(), equalTo(2131)); + assertThat(f.isTruncated(), equalTo(false)); + } + /** * Lifecycle test. * @@ -147,26 +174,4 @@ public void starTest() throws Exception { newGist.delete(); } } - - /** - * Gist file. - * - * @throws Exception - * the exception - */ - @Test - public void gistFile() throws Exception { - GHGist gist = gitHub.getGist("9903708"); - - assertThat(gist.isPublic(), is(true)); - assertThat(gist.getId(), equalTo(9903708L)); - assertThat(gist.getGistId(), equalTo("9903708")); - - assertThat(gist.getFiles().size(), equalTo(1)); - GHGistFile f = gist.getFile("keybase.md"); - - assertThat(f.getType(), equalTo("text/markdown")); - assertThat(f.getLanguage(), equalTo("Markdown")); - assertThat(f.getContent(), containsString("### Keybase proof")); - } } diff --git a/src/test/java/org/kohsuke/github/GHGistUpdaterTest.java b/src/test/java/org/kohsuke/github/GHGistUpdaterTest.java index e5626fc034..24db84ac63 100644 --- a/src/test/java/org/kohsuke/github/GHGistUpdaterTest.java +++ b/src/test/java/org/kohsuke/github/GHGistUpdaterTest.java @@ -17,13 +17,29 @@ */ public class GHGistUpdaterTest extends AbstractGitHubWireMockTest { + private GHGist gist; + /** * Create default GHGistUpdaterTest instance */ public GHGistUpdaterTest() { } - private GHGist gist; + /** + * Clean up. + * + * @throws Exception + * the exception + */ + @After + public void cleanUp() throws Exception { + // Cleanup is only needed when proxying + if (!mockGitHub.isUseProxy()) { + return; + } + + gist.delete(); + } /** * Sets the up. @@ -43,22 +59,6 @@ public void setUp() throws IOException { .create(); } - /** - * Clean up. - * - * @throws Exception - * the exception - */ - @After - public void cleanUp() throws Exception { - // Cleanup is only needed when proxying - if (!mockGitHub.isUseProxy()) { - return; - } - - gist.delete(); - } - /** * Test git updater. * diff --git a/src/test/java/org/kohsuke/github/GHIssueEventAttributeTest.java b/src/test/java/org/kohsuke/github/GHIssueEventAttributeTest.java index 73d04232d6..152e377311 100644 --- a/src/test/java/org/kohsuke/github/GHIssueEventAttributeTest.java +++ b/src/test/java/org/kohsuke/github/GHIssueEventAttributeTest.java @@ -21,16 +21,10 @@ */ public class GHIssueEventAttributeTest extends AbstractGitHubWireMockTest { - /** - * Create default GHIssueEventAttributeTest instance - */ - public GHIssueEventAttributeTest() { - } - private enum Type implements Predicate, Consumer { - milestone(e -> assertThat(e.getMilestone(), notNullValue()), "milestoned", "demilestoned"), + assignment(e -> assertThat(e.getAssignee(), notNullValue()), "assigned", "unassigned"), label(e -> assertThat(e.getLabel(), notNullValue()), "labeled", "unlabeled"), - assignment(e -> assertThat(e.getAssignee(), notNullValue()), "assigned", "unassigned"); + milestone(e -> assertThat(e.getMilestone(), notNullValue()), "milestoned", "demilestoned"); private final Consumer assertion; private final Set subtypes; @@ -41,22 +35,20 @@ private enum Type implements Predicate, Consumer { } @Override - public boolean test(final GHIssueEvent event) { - return this.subtypes.contains(event.getEvent()); + public void accept(final GHIssueEvent event) { + this.assertion.accept(event); } @Override - public void accept(final GHIssueEvent event) { - this.assertion.accept(event); + public boolean test(final GHIssueEvent event) { + return this.subtypes.contains(event.getEvent()); } } - private List listEvents(final Type type) throws IOException { - return StreamSupport - .stream(gitHub.getRepository("chids/project-milestone-test").getIssue(1).listEvents().spliterator(), - false) - .filter(type) - .collect(toList()); + /** + * Create default GHIssueEventAttributeTest instance + */ + public GHIssueEventAttributeTest() { } /** @@ -73,4 +65,12 @@ public void testEventSpecificAttributes() throws IOException { events.forEach(type); } } + + private List listEvents(final Type type) throws IOException { + return StreamSupport + .stream(gitHub.getRepository("chids/project-milestone-test").getIssue(1).listEvents().spliterator(), + false) + .filter(type) + .collect(toList()); + } } diff --git a/src/test/java/org/kohsuke/github/GHIssueEventTest.java b/src/test/java/org/kohsuke/github/GHIssueEventTest.java index 5ac4c2b1df..1404f7fde6 100644 --- a/src/test/java/org/kohsuke/github/GHIssueEventTest.java +++ b/src/test/java/org/kohsuke/github/GHIssueEventTest.java @@ -22,6 +22,46 @@ public class GHIssueEventTest extends AbstractGitHubWireMockTest { public GHIssueEventTest() { } + /** + * Test events for issue rename. + * + * @throws Exception + * the exception + */ + @Test + public void testEventsForIssueRename() throws Exception { + // Create the issue. + GHRepository repo = getRepository(); + GHIssueBuilder builder = repo.createIssue("Some invalid issue name"); + GHIssue issue = builder.create(); + + // Generate rename event. + issue.setTitle("Fixed issue name"); + + // Test that the event is present. + List list = issue.listEvents().toList(); + assertThat(list.size(), equalTo(1)); + + GHIssueEvent event = list.get(0); + assertThat(event.getIssue().getNumber(), equalTo(issue.getNumber())); + assertThat(event.getEvent(), equalTo("renamed")); + assertThat(event.getRename(), notNullValue()); + assertThat(event.getRename().getFrom(), equalTo("Some invalid issue name")); + assertThat(event.getRename().getTo(), equalTo("Fixed issue name")); + + // Test that we can get a single event directly. + GHIssueEvent eventFromRepo = repo.getIssueEvent(event.getId()); + assertThat(eventFromRepo.getId(), equalTo(event.getId())); + assertThat(eventFromRepo.getCreatedAt(), equalTo(event.getCreatedAt())); + assertThat(eventFromRepo.getEvent(), equalTo("renamed")); + assertThat(eventFromRepo.getRename(), notNullValue()); + assertThat(eventFromRepo.getRename().getFrom(), equalTo("Some invalid issue name")); + assertThat(eventFromRepo.getRename().getTo(), equalTo("Fixed issue name")); + + // Close the issue. + issue.close(); + } + /** * Test events for single issue. * @@ -81,51 +121,15 @@ public void testIssueReviewRequestedEvent() throws Exception { assertThat(event.getReviewRequester().getLogin(), equalTo("t0m4uk1991")); assertThat(event.getRequestedReviewer(), notNullValue()); assertThat(event.getRequestedReviewer().getLogin(), equalTo("bitwiseman")); + assertThat(event.getNodeId(), equalTo("MDIwOlJldmlld1JlcXVlc3RlZEV2ZW50NTA2NDM2MzE3OQ==")); + assertThat(event.getCommitId(), nullValue()); + assertThat(event.getCommitUrl(), nullValue()); + assertThat(event.toString(), equalTo("Issue 434 was review_requested by t0m4uk1991 on 2021-07-24T20:28:28Z")); // Close the PR. pullRequest.close(); } - /** - * Test events for issue rename. - * - * @throws Exception - * the exception - */ - @Test - public void testEventsForIssueRename() throws Exception { - // Create the issue. - GHRepository repo = getRepository(); - GHIssueBuilder builder = repo.createIssue("Some invalid issue name"); - GHIssue issue = builder.create(); - - // Generate rename event. - issue.setTitle("Fixed issue name"); - - // Test that the event is present. - List list = issue.listEvents().toList(); - assertThat(list.size(), equalTo(1)); - - GHIssueEvent event = list.get(0); - assertThat(event.getIssue().getNumber(), equalTo(issue.getNumber())); - assertThat(event.getEvent(), equalTo("renamed")); - assertThat(event.getRename(), notNullValue()); - assertThat(event.getRename().getFrom(), equalTo("Some invalid issue name")); - assertThat(event.getRename().getTo(), equalTo("Fixed issue name")); - - // Test that we can get a single event directly. - GHIssueEvent eventFromRepo = repo.getIssueEvent(event.getId()); - assertThat(eventFromRepo.getId(), equalTo(event.getId())); - assertThat(eventFromRepo.getCreatedAt(), equalTo(event.getCreatedAt())); - assertThat(eventFromRepo.getEvent(), equalTo("renamed")); - assertThat(eventFromRepo.getRename(), notNullValue()); - assertThat(eventFromRepo.getRename().getFrom(), equalTo("Some invalid issue name")); - assertThat(eventFromRepo.getRename().getTo(), equalTo("Fixed issue name")); - - // Close the issue. - issue.close(); - } - /** * Test repository events. * @@ -139,11 +143,26 @@ public void testRepositoryEvents() throws Exception { assertThat(list, is(not(empty()))); int i = 0; + int successfulChecks = 0; for (GHIssueEvent event : list) { + + if ("merged".equals(event.getEvent()) + && "ecec449372b1e8270524a35c1a5aa8fdaf0e6676".equals(event.getCommitId())) { + assertThat(event.getCommitUrl(), endsWith("/ecec449372b1e8270524a35c1a5aa8fdaf0e6676")); + assertThat(event.getActor().getLogin(), equalTo("bitwiseman")); + assertThat(event.getActor().getLogin(), equalTo("bitwiseman")); + assertThat(event.getIssue().getPullRequest().getUrl().toString(), endsWith("/github-api/pull/267")); + successfulChecks++; + } assertThat(event.getIssue(), notNullValue()); - if (i++ > 10) + if (i++ > 100) break; } + assertThat("All issue checks must be found and passed", successfulChecks, equalTo(1)); + } + + private GHRepository getRepository(GitHub gitHub) throws IOException { + return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); } /** @@ -156,8 +175,4 @@ public void testRepositoryEvents() throws Exception { protected GHRepository getRepository() throws IOException { return getRepository(gitHub); } - - private GHRepository getRepository(GitHub gitHub) throws IOException { - return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); - } } diff --git a/src/test/java/org/kohsuke/github/GHIssueTest.java b/src/test/java/org/kohsuke/github/GHIssueTest.java index 5b1a45f3d6..6fdaf379bc 100644 --- a/src/test/java/org/kohsuke/github/GHIssueTest.java +++ b/src/test/java/org/kohsuke/github/GHIssueTest.java @@ -5,7 +5,7 @@ import org.junit.Test; import java.io.IOException; -import java.time.temporal.ChronoUnit; +import java.time.Instant; import java.util.Collection; import java.util.Date; import java.util.List; @@ -35,6 +35,67 @@ public class GHIssueTest extends AbstractGitHubWireMockTest { public GHIssueTest() { } + /** + * Adds the labels. + * + * @throws Exception + * the exception + */ + @Test + // Requires push access to the test repo to pass + public void addLabels() throws Exception { + GHIssue issue = getRepository().createIssue("addLabels").body("## test").create(); + String addedLabel1 = "addLabels_label_name_1"; + String addedLabel2 = "addLabels_label_name_2"; + String addedLabel3 = "addLabels_label_name_3"; + + List resultingLabels = issue.addLabels(addedLabel1); + assertThat(resultingLabels.size(), equalTo(1)); + GHLabel ghLabel = resultingLabels.get(0); + assertThat(ghLabel.getName(), equalTo(addedLabel1)); + + int requestCount = mockGitHub.getRequestCount(); + resultingLabels = issue.addLabels(addedLabel2, addedLabel3); + // multiple labels can be added with one api call + assertThat(mockGitHub.getRequestCount(), equalTo(requestCount + 1)); + + assertThat(resultingLabels.size(), equalTo(3)); + assertThat(resultingLabels, + containsInAnyOrder(hasProperty("name", equalTo(addedLabel1)), + hasProperty("name", equalTo(addedLabel2)), + hasProperty("name", equalTo(addedLabel3)))); + + // Adding a label which is already present does not throw an error + resultingLabels = issue.addLabels(ghLabel); + assertThat(resultingLabels.size(), equalTo(3)); + } + + /** + * Adds the labels concurrency issue. + * + * @throws Exception + * the exception + */ + @Test + // Requires push access to the test repo to pass + public void addLabelsConcurrencyIssue() throws Exception { + String addedLabel1 = "addLabelsConcurrencyIssue_label_name_1"; + String addedLabel2 = "addLabelsConcurrencyIssue_label_name_2"; + + GHIssue issue1 = getRepository().createIssue("addLabelsConcurrencyIssue").body("## test").create(); + issue1.getLabels(); + + GHIssue issue2 = getRepository().getIssue(issue1.getNumber()); + issue2.addLabels(addedLabel2); + + Collection labels = issue1.addLabels(addedLabel1); + + assertThat(labels.size(), equalTo(2)); + assertThat(labels, + containsInAnyOrder(hasProperty("name", equalTo(addedLabel1)), + hasProperty("name", equalTo(addedLabel2)))); + } + /** * Clean up. * @@ -54,6 +115,48 @@ public void cleanUp() throws Exception { } } + /** + * Close issue. + * + * @throws Exception + * the exception + */ + @Test + public void closeIssue() throws Exception { + String name = "closeIssue"; + GHIssue issue = getRepository().createIssue(name).body("## test").create(); + assertThat(issue.getTitle(), equalTo(name)); + assertThat(getRepository().getIssue(issue.getNumber()).getState(), equalTo(GHIssueState.OPEN)); + issue.close(); + GHIssue closedIssued = getRepository().getIssue(issue.getNumber()); + assertThat(closedIssued.getState(), equalTo(GHIssueState.CLOSED)); + assertThat(closedIssued.getStateReason(), equalTo(GHIssueStateReason.COMPLETED)); + } + + /** + * Close issue as not planned. + * + * @throws Exception + * the exception + */ + @Test + public void closeIssueNotPlanned() throws Exception { + String name = "closeIssueNotPlanned"; + GHIssue issue = getRepository().createIssue(name).body("## test").create(); + assertThat(issue.getTitle(), equalTo(name)); + + GHIssue createdIssue = issue.getRepository().getIssue(issue.getNumber()); + + assertThat(createdIssue.getState(), equalTo(GHIssueState.OPEN)); + assertThat(createdIssue.getStateReason(), nullValue()); + + issue.close(GHIssueStateReason.NOT_PLANNED); + + GHIssue closedIssued = getRepository().getIssue(issue.getNumber()); + assertThat(closedIssued.getState(), equalTo(GHIssueState.CLOSED)); + assertThat(closedIssued.getStateReason(), equalTo(GHIssueStateReason.NOT_PLANNED)); + } + /** * Creates the issue. * @@ -68,6 +171,24 @@ public void createIssue() throws Exception { assertThat(issue.getTitle(), equalTo(name)); } + /** + * Gets the user test. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void getUserTest() throws IOException { + GHIssue issue = getRepository().createIssue("getUserTest").create(); + GHIssue issueSingle = getRepository().getIssue(issue.getNumber()); + assertThat(issueSingle.getUser().root(), notNullValue()); + + PagedIterable ghIssues = getRepository().queryIssues().state(GHIssueState.OPEN).list(); + for (GHIssue otherIssue : ghIssues) { + assertThat(otherIssue.getUser().root(), notNullValue()); + } + } + /** * Issue comment. * @@ -86,13 +207,13 @@ public void issueComment() throws Exception { assertThat(comments, hasSize(0)); GHIssueComment firstComment = issue.comment("First comment"); - Date firstCommentCreatedAt = firstComment.getCreatedAt(); - Date firstCommentCreatedAtPlus1Second = Date - .from(firstComment.getCreatedAt().toInstant().plus(1, ChronoUnit.SECONDS)); + Instant firstCommentCreatedAt = firstComment.getCreatedAt(); + Instant firstCommentCreatedAtPlus1Second = firstComment.getCreatedAt().plusSeconds(1); comments = issue.listComments().toList(); assertThat(comments, hasSize(1)); assertThat(comments, contains(hasProperty("body", equalTo("First comment")))); + assertThat(comments.get(0).getAuthorAssociation(), equalTo(GHCommentAuthorAssociation.NONE)); comments = issue.queryComments().list().toList(); assertThat(comments, hasSize(1)); @@ -112,13 +233,12 @@ public void issueComment() throws Exception { Thread.sleep(2000); GHIssueComment secondComment = issue.comment("Second comment"); - Date secondCommentCreatedAt = secondComment.getCreatedAt(); - Date secondCommentCreatedAtPlus1Second = Date - .from(secondComment.getCreatedAt().toInstant().plus(1, ChronoUnit.SECONDS)); + Instant secondCommentCreatedAt = secondComment.getCreatedAt(); + Instant secondCommentCreatedAtPlus1Second = secondComment.getCreatedAt().plusSeconds(1); assertThat( "There's an error in the setup of this test; please fix it." + " The second comment should be created at least one second after the first one.", - firstCommentCreatedAtPlus1Second.getTime() <= secondCommentCreatedAt.getTime()); + firstCommentCreatedAtPlus1Second.isBefore(secondCommentCreatedAt)); comments = issue.listComments().toList(); assertThat(comments, hasSize(2)); @@ -140,143 +260,18 @@ public void issueComment() throws Exception { comments = issue.queryComments().since(firstCommentCreatedAtPlus1Second).list().toList(); assertThat(comments, hasSize(1)); assertThat(comments, contains(hasProperty("body", equalTo("Second comment")))); - comments = issue.queryComments().since(secondCommentCreatedAt).list().toList(); + comments = issue.queryComments().since(Date.from(secondCommentCreatedAt)).list().toList(); assertThat(comments, hasSize(1)); assertThat(comments, contains(hasProperty("body", equalTo("Second comment")))); comments = issue.queryComments().since(secondCommentCreatedAtPlus1Second).list().toList(); assertThat(comments, hasSize(0)); // Test "since" with timestamp instead of Date - comments = issue.queryComments().since(secondCommentCreatedAt.getTime()).list().toList(); + comments = issue.queryComments().since(secondCommentCreatedAt.toEpochMilli()).list().toList(); assertThat(comments, hasSize(1)); assertThat(comments, contains(hasProperty("body", equalTo("Second comment")))); } - /** - * Close issue. - * - * @throws Exception - * the exception - */ - @Test - public void closeIssue() throws Exception { - String name = "closeIssue"; - GHIssue issue = getRepository().createIssue(name).body("## test").create(); - assertThat(issue.getTitle(), equalTo(name)); - assertThat(getRepository().getIssue(issue.getNumber()).getState(), equalTo(GHIssueState.OPEN)); - issue.close(); - GHIssue closedIssued = getRepository().getIssue(issue.getNumber()); - assertThat(closedIssued.getState(), equalTo(GHIssueState.CLOSED)); - assertThat(closedIssued.getStateReason(), equalTo(GHIssueStateReason.COMPLETED)); - } - - /** - * Close issue as not planned. - * - * @throws Exception - * the exception - */ - @Test - public void closeIssueNotPlanned() throws Exception { - String name = "closeIssueNotPlanned"; - GHIssue issue = getRepository().createIssue(name).body("## test").create(); - assertThat(issue.getTitle(), equalTo(name)); - - GHIssue createdIssue = issue.getRepository().getIssue(issue.getNumber()); - - assertThat(createdIssue.getState(), equalTo(GHIssueState.OPEN)); - assertThat(createdIssue.getStateReason(), nullValue()); - - issue.close(GHIssueStateReason.NOT_PLANNED); - - GHIssue closedIssued = getRepository().getIssue(issue.getNumber()); - assertThat(closedIssued.getState(), equalTo(GHIssueState.CLOSED)); - assertThat(closedIssued.getStateReason(), equalTo(GHIssueStateReason.NOT_PLANNED)); - } - - /** - * Sets the labels. - * - * @throws Exception - * the exception - */ - @Test - // Requires push access to the test repo to pass - public void setLabels() throws Exception { - GHIssue issue = getRepository().createIssue("setLabels").body("## test").create(); - String label = "setLabels_label_name"; - issue.setLabels(label); - - Collection labels = getRepository().getIssue(issue.getNumber()).getLabels(); - assertThat(labels.size(), equalTo(1)); - GHLabel savedLabel = labels.iterator().next(); - assertThat(savedLabel.getName(), equalTo(label)); - assertThat(savedLabel.getId(), notNullValue()); - assertThat(savedLabel.getNodeId(), notNullValue()); - assertThat(savedLabel.isDefault(), is(false)); - } - - /** - * Adds the labels. - * - * @throws Exception - * the exception - */ - @Test - // Requires push access to the test repo to pass - public void addLabels() throws Exception { - GHIssue issue = getRepository().createIssue("addLabels").body("## test").create(); - String addedLabel1 = "addLabels_label_name_1"; - String addedLabel2 = "addLabels_label_name_2"; - String addedLabel3 = "addLabels_label_name_3"; - - List resultingLabels = issue.addLabels(addedLabel1); - assertThat(resultingLabels.size(), equalTo(1)); - GHLabel ghLabel = resultingLabels.get(0); - assertThat(ghLabel.getName(), equalTo(addedLabel1)); - - int requestCount = mockGitHub.getRequestCount(); - resultingLabels = issue.addLabels(addedLabel2, addedLabel3); - // multiple labels can be added with one api call - assertThat(mockGitHub.getRequestCount(), equalTo(requestCount + 1)); - - assertThat(resultingLabels.size(), equalTo(3)); - assertThat(resultingLabels, - containsInAnyOrder(hasProperty("name", equalTo(addedLabel1)), - hasProperty("name", equalTo(addedLabel2)), - hasProperty("name", equalTo(addedLabel3)))); - - // Adding a label which is already present does not throw an error - resultingLabels = issue.addLabels(ghLabel); - assertThat(resultingLabels.size(), equalTo(3)); - } - - /** - * Adds the labels concurrency issue. - * - * @throws Exception - * the exception - */ - @Test - // Requires push access to the test repo to pass - public void addLabelsConcurrencyIssue() throws Exception { - String addedLabel1 = "addLabelsConcurrencyIssue_label_name_1"; - String addedLabel2 = "addLabelsConcurrencyIssue_label_name_2"; - - GHIssue issue1 = getRepository().createIssue("addLabelsConcurrencyIssue").body("## test").create(); - issue1.getLabels(); - - GHIssue issue2 = getRepository().getIssue(issue1.getNumber()); - issue2.addLabels(addedLabel2); - - Collection labels = issue1.addLabels(addedLabel1); - - assertThat(labels.size(), equalTo(2)); - assertThat(labels, - containsInAnyOrder(hasProperty("name", equalTo(addedLabel1)), - hasProperty("name", equalTo(addedLabel2)))); - } - /** * Removes the labels. * @@ -334,21 +329,29 @@ public void setAssignee() throws Exception { } /** - * Gets the user test. + * Sets the labels. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void getUserTest() throws IOException { - GHIssue issue = getRepository().createIssue("getUserTest").create(); - GHIssue issueSingle = getRepository().getIssue(issue.getNumber()); - assertThat(issueSingle.getUser().root(), notNullValue()); + // Requires push access to the test repo to pass + public void setLabels() throws Exception { + GHIssue issue = getRepository().createIssue("setLabels").body("## test").create(); + String label = "setLabels_label_name"; + issue.setLabels(label); - PagedIterable ghIssues = getRepository().queryIssues().state(GHIssueState.OPEN).list(); - for (GHIssue otherIssue : ghIssues) { - assertThat(otherIssue.getUser().root(), notNullValue()); - } + Collection labels = getRepository().getIssue(issue.getNumber()).getLabels(); + assertThat(labels.size(), equalTo(1)); + GHLabel savedLabel = labels.iterator().next(); + assertThat(savedLabel.getName(), equalTo(label)); + assertThat(savedLabel.getId(), notNullValue()); + assertThat(savedLabel.getNodeId(), notNullValue()); + assertThat(savedLabel.isDefault(), is(false)); + } + + private GHRepository getRepository(GitHub gitHub) throws IOException { + return gitHub.getOrganization(GITHUB_API_TEST_ORG).getRepository("GHIssueTest"); } /** @@ -362,8 +365,4 @@ protected GHRepository getRepository() throws IOException { return getRepository(gitHub); } - private GHRepository getRepository(GitHub gitHub) throws IOException { - return gitHub.getOrganization(GITHUB_API_TEST_ORG).getRepository("GHIssueTest"); - } - } diff --git a/src/test/java/org/kohsuke/github/GHLicenseTest.java b/src/test/java/org/kohsuke/github/GHLicenseTest.java index ff63d24241..63e37b5f02 100644 --- a/src/test/java/org/kohsuke/github/GHLicenseTest.java +++ b/src/test/java/org/kohsuke/github/GHLicenseTest.java @@ -47,59 +47,26 @@ public GHLicenseTest() { } /** - * Basic test to ensure that the list of licenses from {@link GitHub#listLicenses()} is returned. - */ - @Test - public void listLicenses() { - Iterable licenses = gitHub.listLicenses(); - assertThat(licenses, is(not(emptyIterable()))); - } - - /** - * Tests that {@link GitHub#listLicenses()} returns the MIT license in the expected manner. - * - * @throws IOException - * if test fails - */ - @Test - public void listLicensesCheckIndividualLicense() throws IOException { - PagedIterable licenses = gitHub.listLicenses(); - for (GHLicense lic : licenses) { - if (lic.getKey().equals("mit")) { - assertThat(lic.getUrl(), equalTo(new URL(mockGitHub.apiServer().baseUrl() + "/licenses/mit"))); - return; - } - } - fail("The MIT license was not found"); - } - - /** - * Checks that the request for an individual license using {@link GitHub#getLicense(String)} returns expected values - * (not all properties are checked). + * Accesses the 'kohsuke/github-api' repo using {@link GitHub#getRepository(String)} and then calls + * {@link GHRepository#getLicense()} and checks that certain properties are correct. * * @throws IOException * if test fails */ @Test - public void getLicense() throws IOException { - String key = "mit"; - GHLicense license = gitHub.getLicense(key); - assertThat(license, notNullValue()); - assertThat("The name is correct", license.getName(), equalTo("MIT License")); + public void checkRepositoryFullLicense() throws IOException { + GHRepository repo = gitHub.getRepository("hub4j/github-api"); + GHLicense license = repo.getLicense(); + assertThat("The license is populated", license, notNullValue()); + assertThat("The key is correct", license.getKey(), equalTo("mit")); assertThat("The SPDX ID is correct", license.getSpdxId(), is(equalTo("MIT"))); + assertThat("The name is correct", license.getName(), equalTo("MIT License")); + assertThat("The URL is correct", + license.getUrl(), + equalTo(new URL(mockGitHub.apiServer().baseUrl() + "/licenses/mit"))); assertThat("The HTML URL is correct", license.getHtmlUrl(), equalTo(new URL("http://choosealicense.com/licenses/mit/"))); - assertThat(license.getBody(), startsWith("MIT License\n" + "\n" + "Copyright (c) [year] [fullname]\n\n")); - assertThat(license.getForbidden(), is(empty())); - assertThat(license.getPermitted(), is(empty())); - assertThat(license.getRequired(), is(empty())); - assertThat(license.getImplementation(), - equalTo("Create a text file (typically named LICENSE or LICENSE.txt) in the root of your source code and copy the text of the license into the file. Replace [year] with the current year and [fullname] with the name (or names) of the copyright holders.")); - assertThat(license.getCategory(), nullValue()); - assertThat(license.isFeatured(), equalTo(true)); - assertThat(license.equals(null), equalTo(false)); - assertThat(license.equals(gitHub.getLicense(key)), equalTo(true)); } /** @@ -141,6 +108,46 @@ public void checkRepositoryLicenseAtom() throws IOException { equalTo(new URL(mockGitHub.apiServer().baseUrl() + "/licenses/mit"))); } + /** + * Accesses the 'pomes/pomes' repo using {@link GitHub#getRepository(String)} and then calls + * {@link GHRepository#getLicenseContent()} and checks that certain properties are correct. + * + * @throws IOException + * if test fails + */ + @Test + public void checkRepositoryLicenseContent() throws IOException { + GHRepository repo = gitHub.getRepository("pomes/pomes"); + GHContent content = repo.getLicenseContent(); + assertThat("The license content is populated", content, notNullValue()); + assertThat("The type is 'file'", content.getType(), equalTo("file")); + assertThat("The license file is 'LICENSE'", content.getName(), equalTo("LICENSE")); + + if (content.getEncoding().equals("base64")) { + String licenseText = new String(IOUtils.toByteArray(content.read())); + assertThat("The license appears to be an Apache License", licenseText.contains("Apache License")); + } else { + fail("Expected the license to be Base64 encoded but instead it was " + content.getEncoding()); + } + } + + /** + * Accesses the 'bndtools/bnd' repo using {@link GitHub#getRepository(String)} and then calls + * {@link GHRepository#getLicense()}. The description is null due to multiple licences + * + * @throws IOException + * if test fails + */ + @Test + public void checkRepositoryLicenseForIndeterminate() throws IOException { + GHRepository repo = gitHub.getRepository("bndtools/bnd"); + GHLicense license = repo.getLicense(); + assertThat("The license is populated", license, notNullValue()); + assertThat(license.getKey(), equalTo("other")); + assertThat(license.getDescription(), is(nullValue())); + assertThat(license.getUrl(), is(nullValue())); + } + /** * Accesses the 'pomes/pomes' repo using {@link GitHub#getRepository(String)} and checks that the license is * correct. @@ -176,65 +183,58 @@ public void checkRepositoryWithoutLicense() throws IOException { } /** - * Accesses the 'kohsuke/github-api' repo using {@link GitHub#getRepository(String)} and then calls - * {@link GHRepository#getLicense()} and checks that certain properties are correct. + * Checks that the request for an individual license using {@link GitHub#getLicense(String)} returns expected values + * (not all properties are checked). * * @throws IOException * if test fails */ @Test - public void checkRepositoryFullLicense() throws IOException { - GHRepository repo = gitHub.getRepository("hub4j/github-api"); - GHLicense license = repo.getLicense(); - assertThat("The license is populated", license, notNullValue()); - assertThat("The key is correct", license.getKey(), equalTo("mit")); - assertThat("The SPDX ID is correct", license.getSpdxId(), is(equalTo("MIT"))); + public void getLicense() throws IOException { + String key = "mit"; + GHLicense license = gitHub.getLicense(key); + assertThat(license, notNullValue()); assertThat("The name is correct", license.getName(), equalTo("MIT License")); - assertThat("The URL is correct", - license.getUrl(), - equalTo(new URL(mockGitHub.apiServer().baseUrl() + "/licenses/mit"))); + assertThat("The SPDX ID is correct", license.getSpdxId(), is(equalTo("MIT"))); assertThat("The HTML URL is correct", license.getHtmlUrl(), equalTo(new URL("http://choosealicense.com/licenses/mit/"))); + assertThat(license.getBody(), startsWith("MIT License\n" + "\n" + "Copyright (c) [year] [fullname]\n\n")); + assertThat(license.getForbidden(), is(empty())); + assertThat(license.getPermitted(), is(empty())); + assertThat(license.getRequired(), is(empty())); + assertThat(license.getImplementation(), + equalTo("Create a text file (typically named LICENSE or LICENSE.txt) in the root of your source code and copy the text of the license into the file. Replace [year] with the current year and [fullname] with the name (or names) of the copyright holders.")); + assertThat(license.getCategory(), nullValue()); + assertThat(license.isFeatured(), equalTo(true)); + assertThat(license.equals(null), equalTo(false)); + assertThat(license.equals(gitHub.getLicense(key)), equalTo(true)); } /** - * Accesses the 'pomes/pomes' repo using {@link GitHub#getRepository(String)} and then calls - * {@link GHRepository#getLicenseContent()} and checks that certain properties are correct. - * - * @throws IOException - * if test fails + * Basic test to ensure that the list of licenses from {@link GitHub#listLicenses()} is returned. */ @Test - public void checkRepositoryLicenseContent() throws IOException { - GHRepository repo = gitHub.getRepository("pomes/pomes"); - GHContent content = repo.getLicenseContent(); - assertThat("The license content is populated", content, notNullValue()); - assertThat("The type is 'file'", content.getType(), equalTo("file")); - assertThat("The license file is 'LICENSE'", content.getName(), equalTo("LICENSE")); - - if (content.getEncoding().equals("base64")) { - String licenseText = new String(IOUtils.toByteArray(content.read())); - assertThat("The license appears to be an Apache License", licenseText.contains("Apache License")); - } else { - fail("Expected the license to be Base64 encoded but instead it was " + content.getEncoding()); - } + public void listLicenses() { + Iterable licenses = gitHub.listLicenses(); + assertThat(licenses, is(not(emptyIterable()))); } /** - * Accesses the 'bndtools/bnd' repo using {@link GitHub#getRepository(String)} and then calls - * {@link GHRepository#getLicense()}. The description is null due to multiple licences + * Tests that {@link GitHub#listLicenses()} returns the MIT license in the expected manner. * * @throws IOException * if test fails */ @Test - public void checkRepositoryLicenseForIndeterminate() throws IOException { - GHRepository repo = gitHub.getRepository("bndtools/bnd"); - GHLicense license = repo.getLicense(); - assertThat("The license is populated", license, notNullValue()); - assertThat(license.getKey(), equalTo("other")); - assertThat(license.getDescription(), is(nullValue())); - assertThat(license.getUrl(), is(nullValue())); + public void listLicensesCheckIndividualLicense() throws IOException { + PagedIterable licenses = gitHub.listLicenses(); + for (GHLicense lic : licenses) { + if (lic.getKey().equals("mit")) { + assertThat(lic.getUrl(), equalTo(new URL(mockGitHub.apiServer().baseUrl() + "/licenses/mit"))); + return; + } + } + fail("The MIT license was not found"); } } diff --git a/src/test/java/org/kohsuke/github/GHMarketplacePlanTest.java b/src/test/java/org/kohsuke/github/GHMarketplacePlanTest.java index ef8b5690cd..02edb19a9e 100644 --- a/src/test/java/org/kohsuke/github/GHMarketplacePlanTest.java +++ b/src/test/java/org/kohsuke/github/GHMarketplacePlanTest.java @@ -20,35 +20,93 @@ */ public class GHMarketplacePlanTest extends AbstractGitHubWireMockTest { - /** - * Create default GHMarketplacePlanTest instance - */ - public GHMarketplacePlanTest() { + static void testMarketplaceAccount(GHMarketplaceAccountPlan account) { + // Non-nullable fields + assertThat(account.getLogin(), notNullValue()); + assertThat(account.getUrl(), notNullValue()); + assertThat(account.getType(), notNullValue()); + assertThat(account.getMarketplacePurchase(), notNullValue()); + testMarketplacePurchase(account.getMarketplacePurchase()); + + // primitive fields + assertThat(account.getId(), not(0L)); + + /* logical combination tests */ + // Rationale: organization_billing_email is only set when account type is ORGANIZATION. + if (account.getType() == ORGANIZATION) + assertThat(account.getOrganizationBillingEmail(), notNullValue()); + else + assertThat(account.getOrganizationBillingEmail(), nullValue()); + + // Rationale: marketplace_pending_change isn't always set... This is what GitHub says about it: + // "When someone submits a plan change that won't be processed until the end of their billing cycle, + // you will also see the upcoming pending change." + if (account.getMarketplacePendingChange() != null) + testMarketplacePendingChange(account.getMarketplacePendingChange()); } - /** - * Gets the git hub builder. - * - * @return the git hub builder - */ - protected GitHubBuilder getGitHubBuilder() { - return super.getGitHubBuilder() - // ensure that only JWT will be used against the tests below - .withOAuthToken(null, null) - .withJwtToken("bogus"); + static void testMarketplacePendingChange(GHMarketplacePendingChange marketplacePendingChange) { + // Non-nullable fields + assertThat(marketplacePendingChange.getEffectiveDate(), notNullValue()); + testMarketplacePlan(marketplacePendingChange.getPlan()); + + // primitive fields + assertThat(marketplacePendingChange.getId(), not(0L)); + + /* logical combination tests */ + // Rationale: if price model is PER_UNIT then unit_count can't be null + if (marketplacePendingChange.getPlan().getPriceModel() == GHMarketplacePriceModel.PER_UNIT) + assertThat(marketplacePendingChange.getUnitCount(), notNullValue()); + else + assertThat(marketplacePendingChange.getUnitCount(), nullValue()); + + } + + static void testMarketplacePlan(GHMarketplacePlan plan) { + // Non-nullable fields + assertThat(plan.getUrl(), notNullValue()); + assertThat(plan.getAccountsUrl(), notNullValue()); + assertThat(plan.getName(), notNullValue()); + assertThat(plan.getDescription(), notNullValue()); + assertThat(plan.getPriceModel(), notNullValue()); + assertThat(plan.getState(), notNullValue()); + + // primitive fields + assertThat(plan.getId(), not(0L)); + assertThat(plan.getNumber(), not(0L)); + assertThat(plan.getMonthlyPriceInCents(), greaterThanOrEqualTo(0L)); + + // list + assertThat(plan.getBullets().size(), Matchers.in(Arrays.asList(2, 3))); + } + + static void testMarketplacePurchase(GHMarketplacePurchase marketplacePurchase) { + // Non-nullable fields + assertThat(marketplacePurchase.getBillingCycle(), notNullValue()); + assertThat(marketplacePurchase.getNextBillingDate(), notNullValue()); + assertThat(marketplacePurchase.getUpdatedAt(), notNullValue()); + testMarketplacePlan(marketplacePurchase.getPlan()); + + /* logical combination tests */ + // Rationale: if onFreeTrial is true, then we should see free_trial_ends_on property set to something + // different than null + if (marketplacePurchase.isOnFreeTrial()) + assertThat(marketplacePurchase.getFreeTrialEndsOn(), notNullValue()); + else + assertThat(marketplacePurchase.getFreeTrialEndsOn(), nullValue()); + + // Rationale: if price model is PER_UNIT then unit_count can't be null + if (marketplacePurchase.getPlan().getPriceModel() == GHMarketplacePriceModel.PER_UNIT) + assertThat(marketplacePurchase.getUnitCount(), notNullValue()); + else + assertThat(marketplacePurchase.getUnitCount(), Matchers.anyOf(nullValue(), is(1L))); + } /** - * List marketplace plans. - * - * @throws IOException - * Signals that an I/O exception has occurred. + * Create default GHMarketplacePlanTest instance */ - @Test - public void listMarketplacePlans() throws IOException { - List plans = gitHub.listMarketplacePlans().toList(); - assertThat(plans.size(), equalTo(3)); - plans.forEach(GHMarketplacePlanTest::testMarketplacePlan); + public GHMarketplacePlanTest() { } /** @@ -111,87 +169,29 @@ public void listAccountsWithSortAndDirection() throws IOException { } - static void testMarketplacePlan(GHMarketplacePlan plan) { - // Non-nullable fields - assertThat(plan.getUrl(), notNullValue()); - assertThat(plan.getAccountsUrl(), notNullValue()); - assertThat(plan.getName(), notNullValue()); - assertThat(plan.getDescription(), notNullValue()); - assertThat(plan.getPriceModel(), notNullValue()); - assertThat(plan.getState(), notNullValue()); - - // primitive fields - assertThat(plan.getId(), not(0L)); - assertThat(plan.getNumber(), not(0L)); - assertThat(plan.getMonthlyPriceInCents(), greaterThanOrEqualTo(0L)); - - // list - assertThat(plan.getBullets().size(), Matchers.in(Arrays.asList(2, 3))); - } - - static void testMarketplaceAccount(GHMarketplaceAccountPlan account) { - // Non-nullable fields - assertThat(account.getLogin(), notNullValue()); - assertThat(account.getUrl(), notNullValue()); - assertThat(account.getType(), notNullValue()); - assertThat(account.getMarketplacePurchase(), notNullValue()); - testMarketplacePurchase(account.getMarketplacePurchase()); - - // primitive fields - assertThat(account.getId(), not(0L)); - - /* logical combination tests */ - // Rationale: organization_billing_email is only set when account type is ORGANIZATION. - if (account.getType() == ORGANIZATION) - assertThat(account.getOrganizationBillingEmail(), notNullValue()); - else - assertThat(account.getOrganizationBillingEmail(), nullValue()); - - // Rationale: marketplace_pending_change isn't always set... This is what GitHub says about it: - // "When someone submits a plan change that won't be processed until the end of their billing cycle, - // you will also see the upcoming pending change." - if (account.getMarketplacePendingChange() != null) - testMarketplacePendingChange(account.getMarketplacePendingChange()); - } - - static void testMarketplacePurchase(GHMarketplacePurchase marketplacePurchase) { - // Non-nullable fields - assertThat(marketplacePurchase.getBillingCycle(), notNullValue()); - assertThat(marketplacePurchase.getNextBillingDate(), notNullValue()); - assertThat(marketplacePurchase.getUpdatedAt(), notNullValue()); - testMarketplacePlan(marketplacePurchase.getPlan()); - - /* logical combination tests */ - // Rationale: if onFreeTrial is true, then we should see free_trial_ends_on property set to something - // different than null - if (marketplacePurchase.isOnFreeTrial()) - assertThat(marketplacePurchase.getFreeTrialEndsOn(), notNullValue()); - else - assertThat(marketplacePurchase.getFreeTrialEndsOn(), nullValue()); - - // Rationale: if price model is PER_UNIT then unit_count can't be null - if (marketplacePurchase.getPlan().getPriceModel() == GHMarketplacePriceModel.PER_UNIT) - assertThat(marketplacePurchase.getUnitCount(), notNullValue()); - else - assertThat(marketplacePurchase.getUnitCount(), Matchers.anyOf(nullValue(), is(1L))); - + /** + * List marketplace plans. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void listMarketplacePlans() throws IOException { + List plans = gitHub.listMarketplacePlans().toList(); + assertThat(plans.size(), equalTo(3)); + plans.forEach(GHMarketplacePlanTest::testMarketplacePlan); } - static void testMarketplacePendingChange(GHMarketplacePendingChange marketplacePendingChange) { - // Non-nullable fields - assertThat(marketplacePendingChange.getEffectiveDate(), notNullValue()); - testMarketplacePlan(marketplacePendingChange.getPlan()); - - // primitive fields - assertThat(marketplacePendingChange.getId(), not(0L)); - - /* logical combination tests */ - // Rationale: if price model is PER_UNIT then unit_count can't be null - if (marketplacePendingChange.getPlan().getPriceModel() == GHMarketplacePriceModel.PER_UNIT) - assertThat(marketplacePendingChange.getUnitCount(), notNullValue()); - else - assertThat(marketplacePendingChange.getUnitCount(), nullValue()); - + /** + * Gets the git hub builder. + * + * @return the git hub builder + */ + protected GitHubBuilder getGitHubBuilder() { + return super.getGitHubBuilder() + // ensure that only JWT will be used against the tests below + .withOAuthToken(null, null) + .withJwtToken("bogus"); } } diff --git a/src/test/java/org/kohsuke/github/GHMilestoneTest.java b/src/test/java/org/kohsuke/github/GHMilestoneTest.java index 78867c4cd3..b7e6994d3e 100644 --- a/src/test/java/org/kohsuke/github/GHMilestoneTest.java +++ b/src/test/java/org/kohsuke/github/GHMilestoneTest.java @@ -5,10 +5,10 @@ import org.junit.Test; import java.io.IOException; +import java.time.Instant; import java.util.Date; import static org.hamcrest.Matchers.*; -import static org.hamcrest.Matchers.containsString; // TODO: Auto-generated Javadoc /** @@ -46,41 +46,6 @@ public void cleanUp() throws Exception { } } - /** - * Test update milestone. - * - * @throws Exception - * the exception - */ - @Test - public void testUpdateMilestone() throws Exception { - GHRepository repo = getRepository(); - GHMilestone milestone = repo.createMilestone("Original Title", "To test the update methods"); - - String NEW_TITLE = "Updated Title"; - String NEW_DESCRIPTION = "Updated Description"; - Date NEW_DUE_DATE = GitHubClient.parseDate("2020-10-05T13:00:00Z"); - Date OUTPUT_DUE_DATE = GitHubClient.parseDate("2020-10-05T07:00:00Z"); - - milestone.setTitle(NEW_TITLE); - milestone.setDescription(NEW_DESCRIPTION); - milestone.setDueOn(NEW_DUE_DATE); - - // Force reload. - milestone = repo.getMilestone(milestone.getNumber()); - - assertThat(milestone.getTitle(), equalTo(NEW_TITLE)); - assertThat(milestone.getDescription(), equalTo(NEW_DESCRIPTION)); - - // The time is truncated when sent to the server, but still part of the returned value - // 07:00 midnight PDT - assertThat(milestone.getDueOn(), equalTo(OUTPUT_DUE_DATE)); - assertThat(milestone.getHtmlUrl().toString(), containsString("/hub4j-test-org/github-api/milestone/")); - assertThat(milestone.getUrl().toString(), containsString("/repos/hub4j-test-org/github-api/milestones/")); - assertThat(milestone.getClosedIssues(), equalTo(0)); - assertThat(milestone.getOpenIssues(), equalTo(0)); - } - /** * Test unset milestone. * @@ -128,6 +93,46 @@ public void testUnsetMilestoneFromPullRequest() throws IOException { assertThat(p.getMilestone(), nullValue()); } + /** + * Test update milestone. + * + * @throws Exception + * the exception + */ + @Test + public void testUpdateMilestone() throws Exception { + GHRepository repo = getRepository(); + GHMilestone milestone = repo.createMilestone("Original Title", "To test the update methods"); + + String NEW_TITLE = "Updated Title"; + String NEW_DESCRIPTION = "Updated Description"; + Date NEW_DUE_DATE = Date.from(GitHubClient.parseInstant("2020-10-05T13:00:00Z")); + Instant OUTPUT_DUE_DATE = GitHubClient.parseInstant("2020-10-05T07:00:00Z"); + + milestone.setTitle(NEW_TITLE); + milestone.setDescription(NEW_DESCRIPTION); + milestone.setDueOn(NEW_DUE_DATE); + + // Force reload. + milestone = repo.getMilestone(milestone.getNumber()); + + assertThat(milestone.getTitle(), equalTo(NEW_TITLE)); + assertThat(milestone.getDescription(), equalTo(NEW_DESCRIPTION)); + + // The time is truncated when sent to the server, but still part of the returned value + // 07:00 midnight PDT + assertThat(milestone.getDueOn(), equalTo(OUTPUT_DUE_DATE)); + assertThat(milestone.getClosedAt(), nullValue()); + assertThat(milestone.getHtmlUrl().toString(), containsString("/hub4j-test-org/github-api/milestone/")); + assertThat(milestone.getUrl().toString(), containsString("/repos/hub4j-test-org/github-api/milestones/")); + assertThat(milestone.getClosedIssues(), equalTo(0)); + assertThat(milestone.getOpenIssues(), equalTo(0)); + } + + private GHRepository getRepository(GitHub gitHub) throws IOException { + return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); + } + /** * Gets the repository. * @@ -138,8 +143,4 @@ public void testUnsetMilestoneFromPullRequest() throws IOException { protected GHRepository getRepository() throws IOException { return getRepository(gitHub); } - - private GHRepository getRepository(GitHub gitHub) throws IOException { - return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); - } } diff --git a/src/test/java/org/kohsuke/github/GHOrganizationTest.java b/src/test/java/org/kohsuke/github/GHOrganizationTest.java index 7457869b1d..cb9694bb41 100644 --- a/src/test/java/org/kohsuke/github/GHOrganizationTest.java +++ b/src/test/java/org/kohsuke/github/GHOrganizationTest.java @@ -23,29 +23,19 @@ */ public class GHOrganizationTest extends AbstractGitHubWireMockTest { - /** - * Create default GHOrganizationTest instance - */ - public GHOrganizationTest() { - } + /** The Constant GITHUB_API_TEMPLATE_TEST. */ + public static final String GITHUB_API_TEMPLATE_TEST = "github-api-template-test"; /** The Constant GITHUB_API_TEST. */ public static final String GITHUB_API_TEST = "github-api-test"; - /** The Constant GITHUB_API_TEMPLATE_TEST. */ - public static final String GITHUB_API_TEMPLATE_TEST = "github-api-template-test"; - /** The Constant TEAM_NAME_CREATE. */ public static final String TEAM_NAME_CREATE = "create-team-test"; /** - * Enable response templating to allow support validating pagination of external groups - * - * @return the updated WireMock options + * Create default GHOrganizationTest instance */ - @Override - protected WireMockConfiguration getWireMockOptions() { - return super.getWireMockOptions().extensions(templating.newResponseTransformer()); + public GHOrganizationTest() { } /** @@ -70,6 +60,46 @@ public void cleanUpTeam() throws IOException { getNonRecordingGitHub().getOrganization(GITHUB_API_TEST_ORG).enableOrganizationProjects(true); } + /** + * Test are organization projects enabled. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testAreOrganizationProjectsEnabled() throws IOException { + // Arrange + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + + // Act + boolean result = org.areOrganizationProjectsEnabled(); + + // Assert + assertThat(result, is(true)); + } + + /** + * Test create all args team. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testCreateAllArgsTeam() throws IOException { + String REPO_NAME = "github-api"; + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + + GHTeam team = org.createTeam(TEAM_NAME_CREATE) + .description("Team description") + .maintainers("bitwiseman") + .repositories(REPO_NAME) + .privacy(GHTeam.Privacy.CLOSED) + .parentTeamId(3617900) + .create(); + assertThat(team.getDescription(), equalTo("Team description")); + assertThat(team.getPrivacy(), equalTo(GHTeam.Privacy.CLOSED)); + } + /** * Test create repository. * @@ -90,6 +120,43 @@ public void testCreateRepository() throws IOException { assertThat(repository, notNullValue()); } + /** + * Test create repository with template repository null. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testCreateRepositoryFromTemplateRepositoryNull() throws IOException { + cleanupRepository(GITHUB_API_TEST_ORG + '/' + GITHUB_API_TEST); + + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + assertThrows(NullPointerException.class, () -> { + org.createRepository(GITHUB_API_TEST).fromTemplateRepository(null).owner(GITHUB_API_TEST_ORG).create(); + }); + } + + /** + * Test create repository when repository template is not a template. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testCreateRepositoryWhenRepositoryTemplateIsNotATemplate() throws IOException { + cleanupRepository(GITHUB_API_TEST_ORG + '/' + GITHUB_API_TEST); + + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + GHRepository templateRepository = org.getRepository(GITHUB_API_TEMPLATE_TEST); + + assertThrows(IllegalArgumentException.class, () -> { + org.createRepository(GITHUB_API_TEST) + .fromTemplateRepository(templateRepository) + .owner(GITHUB_API_TEST_ORG) + .create(); + }); + } + /** * Test create repository with auto initialization. * @@ -198,438 +265,320 @@ public void testCreateRepositoryWithTemplateAndGHRepository() throws IOException } /** - * Test create repository with template repository null. + * Test create a repository from a template with all branches included * * @throws IOException * Signals that an I/O exception has occurred. + * @throws InterruptedException + * Signals that Thread.sleep() was interrupted */ + @Test - public void testCreateRepositoryFromTemplateRepositoryNull() throws IOException { + public void testCreateRepositoryWithTemplateAndIncludeAllBranches() throws IOException, InterruptedException { cleanupRepository(GITHUB_API_TEST_ORG + '/' + GITHUB_API_TEST); GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - assertThrows(NullPointerException.class, () -> { - org.createRepository(GITHUB_API_TEST).fromTemplateRepository(null).owner(GITHUB_API_TEST_ORG).create(); - }); + GHRepository templateRepository = org.getRepository(GITHUB_API_TEMPLATE_TEST); + + GHRepository repository = gitHub.createRepository(GITHUB_API_TEST) + .fromTemplateRepository(templateRepository) + .includeAllBranches(true) + .owner(GITHUB_API_TEST_ORG) + .create(); + + assertThat(repository, notNullValue()); + + // give it a moment for branches to be created + Thread.sleep(1500); + + assertThat(repository.getBranches().keySet(), equalTo(templateRepository.getBranches().keySet())); + } /** - * Test create repository when repository template is not a template. + * Test create team. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testCreateRepositoryWhenRepositoryTemplateIsNotATemplate() throws IOException { - cleanupRepository(GITHUB_API_TEST_ORG + '/' + GITHUB_API_TEST); + public void testCreateTeam() throws IOException { + String REPO_NAME = "github-api"; + String DEFAULT_PERMISSION = Permission.PULL.toString().toLowerCase(); GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - GHRepository templateRepository = org.getRepository(GITHUB_API_TEMPLATE_TEST); + GHRepository repo = org.getRepository(REPO_NAME); - assertThrows(IllegalArgumentException.class, () -> { - org.createRepository(GITHUB_API_TEST) - .fromTemplateRepository(templateRepository) - .owner(GITHUB_API_TEST_ORG) - .create(); - }); + // Create team with no permission field. Verify that default permission is pull + GHTeam team = org.createTeam(TEAM_NAME_CREATE).repositories(repo.getFullName()).create(); + assertThat(team.getRepositories().containsKey(REPO_NAME), is(true)); + assertThat(team.getPermission(), equalTo(DEFAULT_PERMISSION)); } /** - * Test invite user. + * Test create team with null perm. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void testInviteUser() throws IOException { - GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - GHUser user = gitHub.getUser("martinvanzijl2"); + public void testCreateTeamWithNullPerm() throws Exception { + String REPO_NAME = "github-api"; - // First remove the user - if (org.hasMember(user)) { - org.remove(user); - } + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + GHRepository repo = org.getRepository(REPO_NAME); - // Then invite the user again - org.add(user, GHOrganization.Role.MEMBER); + // Create team with access to repository. Check access was granted. + GHTeam team = org.createTeam(TEAM_NAME_CREATE).create(); - // Now the user has to accept the invitation - // Can this be automated? - // user.acceptInvitationTo(org); // ? + team.add(repo); - // Check the invitation has worked. - // assertTrue(org.hasMember(user)); + assertThat( + repo.getTeams() + .stream() + .filter(t -> TEAM_NAME_CREATE.equals(t.getName())) + .findFirst() + .get() + .getPermission(), + equalTo(Permission.PULL.toString().toLowerCase())); } /** - * Test get user membership + * Test create team with repo access. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testGetMembership() throws IOException { - GHOrganization org = gitHub.getOrganization("hub4j-test-org"); + public void testCreateTeamWithRepoAccess() throws IOException { + String REPO_NAME = "github-api"; - GHMembership membership = org.getMembership("fv316"); + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + GHRepository repo = org.getRepository(REPO_NAME); - assertThat(membership, notNullValue()); - assertThat(membership.getRole(), equalTo(GHMembership.Role.ADMIN)); - assertThat(membership.getState(), equalTo(GHMembership.State.ACTIVE)); - assertThat(membership.getUser().getLogin(), equalTo("fv316")); - assertThat(membership.getOrganization().login, equalTo("hub4j-test-org")); + // Create team with access to repository. Check access was granted. + GHTeam team = org.createTeam(TEAM_NAME_CREATE) + .repositories(repo.getFullName()) + .permission(Permission.PUSH) + .create(); + assertThat(team.getRepositories().containsKey(REPO_NAME), is(true)); + assertThat(team.getPermission(), equalTo(Permission.PUSH.toString().toLowerCase())); } /** - * Test list members with filter. + * Test create team with repo perm. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void testListMembersWithFilter() throws IOException { + public void testCreateTeamWithRepoPerm() throws Exception { + String REPO_NAME = "github-api"; + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + GHRepository repo = org.getRepository(REPO_NAME); - List admins = org.listMembersWithFilter("all").toList(); + // Create team with access to repository. Check access was granted. + GHTeam team = org.createTeam(TEAM_NAME_CREATE).create(); + + team.add(repo, GHOrganization.RepositoryRole.from(Permission.PUSH)); + + assertThat( + repo.getTeams() + .stream() + .filter(t -> TEAM_NAME_CREATE.equals(t.getName())) + .findFirst() + .get() + .getPermission(), + equalTo(Permission.PUSH.toString().toLowerCase())); - assertThat(admins, notNullValue()); - // In case more are added in the future - assertThat(admins.size(), greaterThanOrEqualTo(12)); - assertThat(admins.stream().map(GHUser::getLogin).collect(Collectors.toList()), - hasItems("alexanderrtaylor", - "asthinasthi", - "bitwiseman", - "farmdawgnation", - "halkeye", - "jberglund-BSFT", - "kohsuke", - "kohsuke2", - "martinvanzijl", - "PauloMigAlmeida", - "Sage-Pierce", - "timja")); } /** - * Test list members with role. + * Test create team with repo role. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testListMembersWithRole() throws IOException { + public void testCreateTeamWithRepoRole() throws IOException { + String REPO_NAME = "github-api"; + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + GHRepository repo = org.getRepository(REPO_NAME); - List admins = org.listMembersWithRole("admin").toList(); + // Create team with access to repository. Check access was granted. + GHTeam team = org.createTeam(TEAM_NAME_CREATE).create(); - assertThat(admins, notNullValue()); - // In case more are added in the future - assertThat(admins.size(), greaterThanOrEqualTo(12)); - assertThat(admins.stream().map(GHUser::getLogin).collect(Collectors.toList()), - hasItems("alexanderrtaylor", - "asthinasthi", - "bitwiseman", - "farmdawgnation", - "halkeye", - "jberglund-BSFT", - "kohsuke", - "kohsuke2", - "martinvanzijl", - "PauloMigAlmeida", - "Sage-Pierce", - "timja")); - } + RepositoryRole role = RepositoryRole.from(Permission.TRIAGE); + team.add(repo, role); - /** - * Test list security managers. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Test - public void testListSecurityManagers() throws IOException { - GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - - List securityManagers = org.listSecurityManagers().toList(); - - assertThat(securityManagers, notNullValue()); - // In case more are added in the future - assertThat(securityManagers.size(), greaterThanOrEqualTo(1)); - assertThat(securityManagers.stream().map(GHTeam::getName).collect(Collectors.toList()), - hasItems("security team")); + // 'getPermission' does not return triage even though the UI shows that value + // assertThat( + // repo.getTeams() + // .stream() + // .filter(t -> TEAM_NAME_CREATE.equals(t.getName())) + // .findFirst() + // .get() + // .getPermission(), + // equalTo(role.toString())); } - /** - * Test list outside collaborators. + * Test create visible team. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testListOutsideCollaborators() throws IOException { + public void testCreateVisibleTeam() throws IOException { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - List admins = org.listOutsideCollaborators().toList(); - - assertThat(admins, notNullValue()); - // In case more are added in the future - assertThat(admins.size(), greaterThanOrEqualTo(12)); - assertThat(admins.stream().map(GHUser::getLogin).collect(Collectors.toList()), - hasItems("alexanderrtaylor", - "asthinasthi", - "bitwiseman", - "farmdawgnation", - "halkeye", - "jberglund-BSFT", - "kohsuke", - "kohsuke2", - "martinvanzijl", - "PauloMigAlmeida", - "Sage-Pierce", - "timja")); + GHTeam team = org.createTeam(TEAM_NAME_CREATE).privacy(GHTeam.Privacy.CLOSED).create(); + assertThat(team.getPrivacy(), equalTo(GHTeam.Privacy.CLOSED)); } + /** - * Test list outside collaborators with filter. + * Test enable organization projects. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testListOutsideCollaboratorsWithFilter() throws IOException { + public void testEnableOrganizationProjects() throws IOException { + // Arrange GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - List admins = org.listOutsideCollaboratorsWithFilter("all").toList(); + // Act + org.enableOrganizationProjects(false); - assertThat(admins, notNullValue()); - // In case more are added in the future - assertThat(admins.size(), greaterThanOrEqualTo(12)); - assertThat(admins.stream().map(GHUser::getLogin).collect(Collectors.toList()), - hasItems("alexanderrtaylor", - "asthinasthi", - "bitwiseman", - "farmdawgnation", - "halkeye", - "jberglund-BSFT", - "kohsuke", - "kohsuke2", - "martinvanzijl", - "PauloMigAlmeida", - "Sage-Pierce", - "timja")); + // Assert + assertThat(org.areOrganizationProjectsEnabled(), is(false)); } /** - * Test create team with repo access. + * Test get external group * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testCreateTeamWithRepoAccess() throws IOException { - String REPO_NAME = "github-api"; - - GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - GHRepository repo = org.getRepository(REPO_NAME); - - // Create team with access to repository. Check access was granted. - GHTeam team = org.createTeam(TEAM_NAME_CREATE) - .repositories(repo.getFullName()) - .permission(Permission.PUSH) - .create(); - assertThat(team.getRepositories().containsKey(REPO_NAME), is(true)); - assertThat(team.getPermission(), equalTo(Permission.PUSH.toString().toLowerCase())); - } - - /** - * Test create team with null perm. - * - * @throws Exception - * the exception - */ - @Test - public void testCreateTeamWithNullPerm() throws Exception { - String REPO_NAME = "github-api"; - + public void testGetExternalGroup() throws IOException { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - GHRepository repo = org.getRepository(REPO_NAME); - // Create team with access to repository. Check access was granted. - GHTeam team = org.createTeam(TEAM_NAME_CREATE).create(); - - team.add(repo); - - assertThat( - repo.getTeams() - .stream() - .filter(t -> TEAM_NAME_CREATE.equals(t.getName())) - .findFirst() - .get() - .getPermission(), - equalTo(Permission.PULL.toString().toLowerCase())); - } - - /** - * Test create team with repo perm. - * - * @throws Exception - * the exception - */ - @Test - public void testCreateTeamWithRepoPerm() throws Exception { - String REPO_NAME = "github-api"; - - GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - GHRepository repo = org.getRepository(REPO_NAME); + GHExternalGroup group = org.getExternalGroup(467431L); - // Create team with access to repository. Check access was granted. - GHTeam team = org.createTeam(TEAM_NAME_CREATE).create(); + assertThat(group, not(isExternalGroupSummary())); - team.add(repo, GHOrganization.RepositoryRole.from(Permission.PUSH)); + assertThat(group.getId(), equalTo(467431L)); + assertThat(group.getName(), equalTo("acme-developers")); + assertThat(group.getUpdatedAt(), notNullValue()); - assertThat( - repo.getTeams() - .stream() - .filter(t -> TEAM_NAME_CREATE.equals(t.getName())) - .findFirst() - .get() - .getPermission(), - equalTo(Permission.PUSH.toString().toLowerCase())); + assertThat(group.getMembers(), notNullValue()); + assertThat(membersSummary(group), + hasItems("158311279:john-doe_acme:John Doe:john.doe@acme.corp", + "166731041:jane-doe_acme:Jane Doe:jane.doe@acme.corp")); + assertThat(group.getTeams(), notNullValue()); + assertThat(teamSummary(group), hasItems("9891173:ACME-DEVELOPERS")); } /** - * Test create team with repo role. + * Test get external group for not enterprise managed organization * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testCreateTeamWithRepoRole() throws IOException { - String REPO_NAME = "github-api"; - + public void testGetExternalGroupNotEnterpriseManagedOrganization() throws IOException { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - GHRepository repo = org.getRepository(REPO_NAME); - - // Create team with access to repository. Check access was granted. - GHTeam team = org.createTeam(TEAM_NAME_CREATE).create(); - RepositoryRole role = RepositoryRole.from(Permission.TRIAGE); - team.add(repo, role); + final GHIOException failure = assertThrows(GHNotExternallyManagedEnterpriseException.class, + () -> org.getExternalGroup(12345)); - // 'getPermission' does not return triage even though the UI shows that value - // assertThat( - // repo.getTeams() - // .stream() - // .filter(t -> TEAM_NAME_CREATE.equals(t.getName())) - // .findFirst() - // .get() - // .getPermission(), - // equalTo(role.toString())); + assertThat(failure.getMessage(), equalTo("Could not retrieve organization external group")); } /** - * Test create team. + * Test get user membership * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testCreateTeam() throws IOException { - String REPO_NAME = "github-api"; - String DEFAULT_PERMISSION = Permission.PULL.toString().toLowerCase(); + public void testGetMembership() throws IOException { + GHOrganization org = gitHub.getOrganization("hub4j-test-org"); - GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - GHRepository repo = org.getRepository(REPO_NAME); + GHMembership membership = org.getMembership("fv316"); - // Create team with no permission field. Verify that default permission is pull - GHTeam team = org.createTeam(TEAM_NAME_CREATE).repositories(repo.getFullName()).create(); - assertThat(team.getRepositories().containsKey(REPO_NAME), is(true)); - assertThat(team.getPermission(), equalTo(DEFAULT_PERMISSION)); + assertThat(membership, notNullValue()); + assertThat(membership.getRole(), equalTo(GHMembership.Role.ADMIN)); + assertThat(membership.getState(), equalTo(GHMembership.State.ACTIVE)); + assertThat(membership.getUser().getLogin(), equalTo("fv316")); + assertThat(membership.getOrganization().login, equalTo("hub4j-test-org")); } /** - * Test create visible team. + * Test invite user. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testCreateVisibleTeam() throws IOException { + public void testInviteUser() throws IOException { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + GHUser user = gitHub.getUser("martinvanzijl2"); - GHTeam team = org.createTeam(TEAM_NAME_CREATE).privacy(GHTeam.Privacy.CLOSED).create(); - assertThat(team.getPrivacy(), equalTo(GHTeam.Privacy.CLOSED)); - } + // First remove the user + if (org.hasMember(user)) { + org.remove(user); + } - /** - * Test create all args team. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Test - public void testCreateAllArgsTeam() throws IOException { - String REPO_NAME = "github-api"; - GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + // Then invite the user again + org.add(user, GHOrganization.Role.MEMBER); - GHTeam team = org.createTeam(TEAM_NAME_CREATE) - .description("Team description") - .maintainers("bitwiseman") - .repositories(REPO_NAME) - .privacy(GHTeam.Privacy.CLOSED) - .parentTeamId(3617900) - .create(); - assertThat(team.getDescription(), equalTo("Team description")); - assertThat(team.getPrivacy(), equalTo(GHTeam.Privacy.CLOSED)); + // Now the user has to accept the invitation + // Can this be automated? + // user.acceptInvitationTo(org); // ? + + // Check the invitation has worked. + // assertTrue(org.hasMember(user)); } /** - * Test are organization projects enabled. + * Test list external groups without pagination for non enterprise managed organization. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testAreOrganizationProjectsEnabled() throws IOException { - // Arrange + public void testListExternalGroupsNotEnterpriseManagedOrganization() throws IOException { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - // Act - boolean result = org.areOrganizationProjectsEnabled(); - - // Assert - assertThat(result, is(true)); - } + final GHNotExternallyManagedEnterpriseException failure = assertThrows( + GHNotExternallyManagedEnterpriseException.class, + () -> org.listExternalGroups().toList()); - /** - * Test enable organization projects. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Test - public void testEnableOrganizationProjects() throws IOException { - // Arrange - GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + assertThat(failure.getMessage(), equalTo("Could not retrieve organization external groups")); - // Act - org.enableOrganizationProjects(false); + final GHError error = failure.getError(); - // Assert - assertThat(org.areOrganizationProjectsEnabled(), is(false)); + assertThat(error, notNullValue()); + assertThat(error.getMessage(), + equalTo(EnterpriseManagedSupport.NOT_PART_OF_EXTERNALLY_MANAGED_ENTERPRISE_ERROR)); + assertThat(error.getDocumentationUrl(), notNullValue()); } /** - * Test list external groups without pagination. + * Test list external groups with name filtering. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testListExternalGroupsWithoutPagination() throws IOException { + public void testListExternalGroupsWithFilter() throws IOException { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - List groups = org.listExternalGroups().toList(); + List groups = org.listExternalGroups("acme").toList(); assertThat(groups, notNullValue()); // In case more are added in the future @@ -641,9 +590,6 @@ public void testListExternalGroupsWithoutPagination() throws IOException { "467433:acme-technical-leads")); groups.forEach(group -> assertThat(group, isExternalGroupSummary())); - - // We are doing one request to get the organization and one to get the external groups - assertThat(mockGitHub.getRequestCount(), greaterThanOrEqualTo(2)); } /** @@ -674,16 +620,16 @@ public void testListExternalGroupsWithPagination() throws IOException { } /** - * Test list external groups with name filtering. + * Test list external groups without pagination. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testListExternalGroupsWithFilter() throws IOException { + public void testListExternalGroupsWithoutPagination() throws IOException { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - List groups = org.listExternalGroups("acme").toList(); + List groups = org.listExternalGroups().toList(); assertThat(groups, notNullValue()); // In case more are added in the future @@ -695,73 +641,158 @@ public void testListExternalGroupsWithFilter() throws IOException { "467433:acme-technical-leads")); groups.forEach(group -> assertThat(group, isExternalGroupSummary())); + + // We are doing one request to get the organization and one to get the external groups + assertThat(mockGitHub.getRequestCount(), greaterThanOrEqualTo(2)); } /** - * Test list external groups without pagination for non enterprise managed organization. + * Test list members with filter. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testListExternalGroupsNotEnterpriseManagedOrganization() throws IOException { + public void testListMembersWithFilter() throws IOException { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - final GHNotExternallyManagedEnterpriseException failure = assertThrows( - GHNotExternallyManagedEnterpriseException.class, - () -> org.listExternalGroups().toList()); + List admins = org.listMembersWithFilter("all").toList(); - assertThat(failure.getMessage(), equalTo("Could not retrieve organization external groups")); + assertThat(admins, notNullValue()); + // In case more are added in the future + assertThat(admins.size(), greaterThanOrEqualTo(12)); + assertThat(admins.stream().map(GHUser::getLogin).collect(Collectors.toList()), + hasItems("alexanderrtaylor", + "asthinasthi", + "bitwiseman", + "farmdawgnation", + "halkeye", + "jberglund-BSFT", + "kohsuke", + "kohsuke2", + "martinvanzijl", + "PauloMigAlmeida", + "Sage-Pierce", + "timja")); + } - final GHError error = failure.getError(); + /** + * Test list members with role. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testListMembersWithRole() throws IOException { + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - assertThat(error, notNullValue()); - assertThat(error.getMessage(), - equalTo(EnterpriseManagedSupport.NOT_PART_OF_EXTERNALLY_MANAGED_ENTERPRISE_ERROR)); - assertThat(error.getDocumentationUrl(), notNullValue()); + List admins = org.listMembersWithRole("admin").toList(); + + assertThat(admins, notNullValue()); + // In case more are added in the future + assertThat(admins.size(), greaterThanOrEqualTo(12)); + assertThat(admins.stream().map(GHUser::getLogin).collect(Collectors.toList()), + hasItems("alexanderrtaylor", + "asthinasthi", + "bitwiseman", + "farmdawgnation", + "halkeye", + "jberglund-BSFT", + "kohsuke", + "kohsuke2", + "martinvanzijl", + "PauloMigAlmeida", + "Sage-Pierce", + "timja")); } /** - * Test get external group + * Test list outside collaborators. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testGetExternalGroup() throws IOException { + public void testListOutsideCollaborators() throws IOException { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - GHExternalGroup group = org.getExternalGroup(467431L); + List admins = org.listOutsideCollaborators().toList(); - assertThat(group, not(isExternalGroupSummary())); + assertThat(admins, notNullValue()); + // In case more are added in the future + assertThat(admins.size(), greaterThanOrEqualTo(12)); + assertThat(admins.stream().map(GHUser::getLogin).collect(Collectors.toList()), + hasItems("alexanderrtaylor", + "asthinasthi", + "bitwiseman", + "farmdawgnation", + "halkeye", + "jberglund-BSFT", + "kohsuke", + "kohsuke2", + "martinvanzijl", + "PauloMigAlmeida", + "Sage-Pierce", + "timja")); + } - assertThat(group.getId(), equalTo(467431L)); - assertThat(group.getName(), equalTo("acme-developers")); - assertThat(group.getUpdatedAt(), notNullValue()); + /** + * Test list outside collaborators with filter. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testListOutsideCollaboratorsWithFilter() throws IOException { + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - assertThat(group.getMembers(), notNullValue()); - assertThat(membersSummary(group), - hasItems("158311279:john-doe_acme:John Doe:john.doe@acme.corp", - "166731041:jane-doe_acme:Jane Doe:jane.doe@acme.corp")); + List admins = org.listOutsideCollaboratorsWithFilter("all").toList(); - assertThat(group.getTeams(), notNullValue()); - assertThat(teamSummary(group), hasItems("9891173:ACME-DEVELOPERS")); + assertThat(admins, notNullValue()); + // In case more are added in the future + assertThat(admins.size(), greaterThanOrEqualTo(12)); + assertThat(admins.stream().map(GHUser::getLogin).collect(Collectors.toList()), + hasItems("alexanderrtaylor", + "asthinasthi", + "bitwiseman", + "farmdawgnation", + "halkeye", + "jberglund-BSFT", + "kohsuke", + "kohsuke2", + "martinvanzijl", + "PauloMigAlmeida", + "Sage-Pierce", + "timja")); } /** - * Test get external group for not enterprise managed organization + * Test list security managers. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testGetExternalGroupNotEnterpriseManagedOrganization() throws IOException { + public void testListSecurityManagers() throws IOException { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - final GHIOException failure = assertThrows(GHNotExternallyManagedEnterpriseException.class, - () -> org.getExternalGroup(12345)); + List securityManagers = org.listSecurityManagers().toList(); - assertThat(failure.getMessage(), equalTo("Could not retrieve organization external group")); + assertThat(securityManagers, notNullValue()); + // In case more are added in the future + assertThat(securityManagers.size(), greaterThanOrEqualTo(1)); + assertThat(securityManagers.stream().map(GHTeam::getName).collect(Collectors.toList()), + hasItems("security team")); + } + + /** + * Enable response templating to allow support validating pagination of external groups + * + * @return the updated WireMock options + */ + @Override + protected WireMockConfiguration getWireMockOptions() { + return super.getWireMockOptions().extensions(templating.newResponseTransformer()); } } diff --git a/src/test/java/org/kohsuke/github/GHPersonTest.java b/src/test/java/org/kohsuke/github/GHPersonTest.java index 199885c01c..46fb05dd66 100644 --- a/src/test/java/org/kohsuke/github/GHPersonTest.java +++ b/src/test/java/org/kohsuke/github/GHPersonTest.java @@ -48,6 +48,10 @@ public void testFieldsForUser() throws Exception { assertThat(user.isSiteAdmin(), notNullValue()); } + private GHRepository getRepository(GitHub gitHub) throws IOException { + return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); + } + /** * Gets the repository. * @@ -58,8 +62,4 @@ public void testFieldsForUser() throws Exception { protected GHRepository getRepository() throws IOException { return getRepository(gitHub); } - - private GHRepository getRepository(GitHub gitHub) throws IOException { - return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); - } } diff --git a/src/test/java/org/kohsuke/github/GHProjectCardTest.java b/src/test/java/org/kohsuke/github/GHProjectCardTest.java index f3494b8a63..093bb56139 100644 --- a/src/test/java/org/kohsuke/github/GHProjectCardTest.java +++ b/src/test/java/org/kohsuke/github/GHProjectCardTest.java @@ -17,16 +17,55 @@ */ public class GHProjectCardTest extends AbstractGitHubWireMockTest { + private GHProjectCard card; + + private GHProjectColumn column; + private GHOrganization org; + private GHProject project; /** * Create default GHProjectCardTest instance */ public GHProjectCardTest() { } - private GHOrganization org; - private GHProject project; - private GHProjectColumn column; - private GHProjectCard card; + /** + * After. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @After + public void after() throws IOException { + if (mockGitHub.isUseProxy()) { + if (card != null) { + card = getNonRecordingGitHub().getProjectCard(card.getId()); + try { + card.delete(); + card = null; + } catch (FileNotFoundException e) { + card = null; + } + } + if (column != null) { + column = getNonRecordingGitHub().getProjectColumn(column.getId()); + try { + column.delete(); + column = null; + } catch (FileNotFoundException e) { + column = null; + } + } + if (project != null) { + project = getNonRecordingGitHub().getProject(project.getId()); + try { + project.delete(); + project = null; + } catch (FileNotFoundException e) { + project = null; + } + } + } + } /** * Sets the up. @@ -42,29 +81,6 @@ public void setUp() throws Exception { card = column.createCard("This is a card"); } - /** - * Test created card. - */ - @Test - public void testCreatedCard() { - assertThat(card.getNote(), equalTo("This is a card")); - assertThat(card.isArchived(), is(false)); - } - - /** - * Test edit card note. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Test - public void testEditCardNote() throws IOException { - card.setNote("New note"); - card = gitHub.getProjectCard(card.getId()); - assertThat(card.getNote(), equalTo("New note")); - assertThat(card.isArchived(), is(false)); - } - /** * Test archive card. * @@ -91,9 +107,14 @@ public void testCreateCardFromIssue() throws IOException { try { GHIssue issue = repo.createIssue("new-issue").body("With body").create(); GHProjectCard card = column.createCard(issue); + assertThat(column.getProjectUrl(), equalTo(card.getProjectUrl())); + assertThat(card.getContentUrl(), equalTo(issue.getUrl())); assertThat(card.getContent().getUrl(), equalTo(issue.getUrl())); assertThat(card.getContent().getRepository().getUrl(), equalTo(repo.getUrl())); + assertThat(card.getProjectUrl().toString(), endsWith("/projects/13495086")); + assertThat(card.getColumnUrl().toString(), endsWith("/projects/columns/16361848")); + } finally { repo.delete(); } @@ -131,6 +152,15 @@ public void testCreateCardFromPR() throws IOException { } } + /** + * Test created card. + */ + @Test + public void testCreatedCard() { + assertThat(card.getNote(), equalTo("This is a card")); + assertThat(card.isArchived(), is(false)); + } + /** * Test delete card. * @@ -149,41 +179,16 @@ public void testDeleteCard() throws IOException { } /** - * After. + * Test edit card note. * * @throws IOException * Signals that an I/O exception has occurred. */ - @After - public void after() throws IOException { - if (mockGitHub.isUseProxy()) { - if (card != null) { - card = getNonRecordingGitHub().getProjectCard(card.getId()); - try { - card.delete(); - card = null; - } catch (FileNotFoundException e) { - card = null; - } - } - if (column != null) { - column = getNonRecordingGitHub().getProjectColumn(column.getId()); - try { - column.delete(); - column = null; - } catch (FileNotFoundException e) { - column = null; - } - } - if (project != null) { - project = getNonRecordingGitHub().getProject(project.getId()); - try { - project.delete(); - project = null; - } catch (FileNotFoundException e) { - project = null; - } - } - } + @Test + public void testEditCardNote() throws IOException { + card.setNote("New note"); + card = gitHub.getProjectCard(card.getId()); + assertThat(card.getNote(), equalTo("New note")); + assertThat(card.isArchived(), is(false)); } } diff --git a/src/test/java/org/kohsuke/github/GHProjectColumnTest.java b/src/test/java/org/kohsuke/github/GHProjectColumnTest.java index ec09a4e3a2..b858b4b208 100644 --- a/src/test/java/org/kohsuke/github/GHProjectColumnTest.java +++ b/src/test/java/org/kohsuke/github/GHProjectColumnTest.java @@ -18,14 +18,44 @@ */ public class GHProjectColumnTest extends AbstractGitHubWireMockTest { + private GHProjectColumn column; + + private GHProject project; /** * Create default GHProjectColumnTest instance */ public GHProjectColumnTest() { } - private GHProject project; - private GHProjectColumn column; + /** + * After. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @After + public void after() throws IOException { + if (mockGitHub.isUseProxy()) { + if (column != null) { + column = getNonRecordingGitHub().getProjectColumn(column.getId()); + try { + column.delete(); + column = null; + } catch (FileNotFoundException e) { + column = null; + } + } + if (project != null) { + project = getNonRecordingGitHub().getProject(project.getId()); + try { + project.delete(); + project = null; + } catch (FileNotFoundException e) { + project = null; + } + } + } + } /** * Sets the up. @@ -47,19 +77,6 @@ public void testCreatedColumn() { assertThat(column.getName(), equalTo("column-one")); } - /** - * Test edit column name. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Test - public void testEditColumnName() throws IOException { - column.setName("new-name"); - column = gitHub.getProjectColumn(column.getId()); - assertThat(column.getName(), equalTo("new-name")); - } - /** * Test delete column. * @@ -78,32 +95,15 @@ public void testDeleteColumn() throws IOException { } /** - * After. + * Test edit column name. * * @throws IOException * Signals that an I/O exception has occurred. */ - @After - public void after() throws IOException { - if (mockGitHub.isUseProxy()) { - if (column != null) { - column = getNonRecordingGitHub().getProjectColumn(column.getId()); - try { - column.delete(); - column = null; - } catch (FileNotFoundException e) { - column = null; - } - } - if (project != null) { - project = getNonRecordingGitHub().getProject(project.getId()); - try { - project.delete(); - project = null; - } catch (FileNotFoundException e) { - project = null; - } - } - } + @Test + public void testEditColumnName() throws IOException { + column.setName("new-name"); + column = gitHub.getProjectColumn(column.getId()); + assertThat(column.getName(), equalTo("new-name")); } } diff --git a/src/test/java/org/kohsuke/github/GHProjectTest.java b/src/test/java/org/kohsuke/github/GHProjectTest.java index ffef245ca4..81fce58f89 100644 --- a/src/test/java/org/kohsuke/github/GHProjectTest.java +++ b/src/test/java/org/kohsuke/github/GHProjectTest.java @@ -17,13 +17,34 @@ */ public class GHProjectTest extends AbstractGitHubWireMockTest { + private GHProject project; + /** * Create default GHProjectTest instance */ public GHProjectTest() { } - private GHProject project; + /** + * After. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @After + public void after() throws IOException { + if (mockGitHub.isUseProxy()) { + if (project != null) { + project = getNonRecordingGitHub().getProject(project.getId()); + try { + project.delete(); + project = null; + } catch (FileNotFoundException e) { + project = null; + } + } + } + } /** * Sets the up. @@ -47,21 +68,25 @@ public void testCreatedProject() { assertThat(project.getState(), equalTo(GHProject.ProjectState.OPEN)); assertThat(project.getHtmlUrl().toString(), containsString("/orgs/hub4j-test-org/projects/")); assertThat(project.getUrl().toString(), containsString("/projects/")); + assertThat(project.getOwnerUrl().toString(), endsWith("/orgs/hub4j-test-org")); + } /** - * Test edit project name. + * Test delete project. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testEditProjectName() throws IOException { - project.setName("new-name"); - project = gitHub.getProject(project.getId()); - assertThat(project.getName(), equalTo("new-name")); - assertThat(project.getBody(), equalTo("This is a test project")); - assertThat(project.getState(), equalTo(GHProject.ProjectState.OPEN)); + public void testDeleteProject() throws IOException { + project.delete(); + try { + project = gitHub.getProject(project.getId()); + assertThat(project, nullValue()); + } catch (FileNotFoundException e) { + project = null; + } } /** @@ -80,55 +105,32 @@ public void testEditProjectBody() throws IOException { } /** - * Test edit project state. + * Test edit project name. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testEditProjectState() throws IOException { - project.setState(GHProject.ProjectState.CLOSED); + public void testEditProjectName() throws IOException { + project.setName("new-name"); project = gitHub.getProject(project.getId()); - assertThat(project.getName(), equalTo("test-project")); + assertThat(project.getName(), equalTo("new-name")); assertThat(project.getBody(), equalTo("This is a test project")); - assertThat(project.getState(), equalTo(GHProject.ProjectState.CLOSED)); + assertThat(project.getState(), equalTo(GHProject.ProjectState.OPEN)); } /** - * Test delete project. + * Test edit project state. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testDeleteProject() throws IOException { - project.delete(); - try { - project = gitHub.getProject(project.getId()); - assertThat(project, nullValue()); - } catch (FileNotFoundException e) { - project = null; - } - } - - /** - * After. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @After - public void after() throws IOException { - if (mockGitHub.isUseProxy()) { - if (project != null) { - project = getNonRecordingGitHub().getProject(project.getId()); - try { - project.delete(); - project = null; - } catch (FileNotFoundException e) { - project = null; - } - } - } + public void testEditProjectState() throws IOException { + project.setState(GHProject.ProjectState.CLOSED); + project = gitHub.getProject(project.getId()); + assertThat(project.getName(), equalTo("test-project")); + assertThat(project.getBody(), equalTo("This is a test project")); + assertThat(project.getState(), equalTo(GHProject.ProjectState.CLOSED)); } } diff --git a/src/test/java/org/kohsuke/github/GHPublicKeyTest.java b/src/test/java/org/kohsuke/github/GHPublicKeyTest.java index b4f4a07b8c..023a506e40 100644 --- a/src/test/java/org/kohsuke/github/GHPublicKeyTest.java +++ b/src/test/java/org/kohsuke/github/GHPublicKeyTest.java @@ -9,15 +9,15 @@ */ public class GHPublicKeyTest extends AbstractGitHubWireMockTest { + private static final String TMP_KEY_NAME = "Temporary user key"; + + private static final String WIREMOCK_SSH_PUBLIC_KEY = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDepW2/BSVFM2AfuGGsvi+vjQzC0EBD3R+/7PNEvP0/nvTWxiC/tthfvvCJR6TKrsprCir5tiJFm73gX+K18W0RKYpkyg8H6d1eZu3q/JOiGvoDPeN8Oe9hOGeeexw1WOiz7ESPHzZYXI981evzHAzxxn8zibr2EryopVNsXyoenw=="; /** * Create default GHPublicKeyTest instance */ public GHPublicKeyTest() { } - private static final String TMP_KEY_NAME = "Temporary user key"; - private static final String WIREMOCK_SSH_PUBLIC_KEY = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDepW2/BSVFM2AfuGGsvi+vjQzC0EBD3R+/7PNEvP0/nvTWxiC/tthfvvCJR6TKrsprCir5tiJFm73gX+K18W0RKYpkyg8H6d1eZu3q/JOiGvoDPeN8Oe9hOGeeexw1WOiz7ESPHzZYXI981evzHAzxxn8zibr2EryopVNsXyoenw=="; - /** * Test adding a public key to the user * diff --git a/src/test/java/org/kohsuke/github/GHPullRequestMockTest.java b/src/test/java/org/kohsuke/github/GHPullRequestMockTest.java index 45dd9af9f5..b84c905546 100644 --- a/src/test/java/org/kohsuke/github/GHPullRequestMockTest.java +++ b/src/test/java/org/kohsuke/github/GHPullRequestMockTest.java @@ -33,5 +33,4 @@ public void shouldMockGHPullRequest() throws IOException { assertThat("Mock should return true", pullRequest.isDraft()); } - } diff --git a/src/test/java/org/kohsuke/github/GHPullRequestTest.java b/src/test/java/org/kohsuke/github/GHPullRequestTest.java index 0a2e4a2712..aa1aa231f4 100644 --- a/src/test/java/org/kohsuke/github/GHPullRequestTest.java +++ b/src/test/java/org/kohsuke/github/GHPullRequestTest.java @@ -6,10 +6,9 @@ import org.kohsuke.github.GHPullRequest.AutoMerge; import java.io.IOException; -import java.time.temporal.ChronoUnit; +import java.time.Instant; import java.util.Collection; import java.util.Collections; -import java.util.Date; import java.util.List; import java.util.Optional; @@ -17,6 +16,7 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasProperty; import static org.hamcrest.Matchers.hasSize; @@ -25,6 +25,8 @@ import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; +import static org.kohsuke.github.GHPullRequestReviewComment.Side.LEFT; +import static org.kohsuke.github.GHPullRequestReviewComment.Side.RIGHT; // TODO: Auto-generated Javadoc /** @@ -40,6 +42,118 @@ public class GHPullRequestTest extends AbstractGitHubWireMockTest { public GHPullRequestTest() { } + /** + * Adds the labels. + * + * @throws Exception + * the exception + */ + @Test + // Requires push access to the test repo to pass + public void addLabels() throws Exception { + GHPullRequest p = getRepository().createPullRequest("addLabels", "test/stable", "main", "## test"); + String addedLabel1 = "addLabels_label_name_1"; + String addedLabel2 = "addLabels_label_name_2"; + String addedLabel3 = "addLabels_label_name_3"; + + List resultingLabels = p.addLabels(addedLabel1); + assertThat(resultingLabels.size(), equalTo(1)); + GHLabel ghLabel = resultingLabels.get(0); + assertThat(ghLabel.getName(), equalTo(addedLabel1)); + + int requestCount = mockGitHub.getRequestCount(); + resultingLabels = p.addLabels(addedLabel2, addedLabel3); + // multiple labels can be added with one api call + assertThat(mockGitHub.getRequestCount(), equalTo(requestCount + 1)); + + assertThat(resultingLabels.size(), equalTo(3)); + assertThat(resultingLabels, + containsInAnyOrder(hasProperty("name", equalTo(addedLabel1)), + hasProperty("name", equalTo(addedLabel2)), + hasProperty("name", equalTo(addedLabel3)))); + + // Adding a label which is already present does not throw an error + resultingLabels = p.addLabels(ghLabel); + assertThat(resultingLabels.size(), equalTo(3)); + } + + /** + * Adds the labels concurrency issue. + * + * @throws Exception + * the exception + */ + @Test + // Requires push access to the test repo to pass + public void addLabelsConcurrencyIssue() throws Exception { + String addedLabel1 = "addLabelsConcurrencyIssue_label_name_1"; + String addedLabel2 = "addLabelsConcurrencyIssue_label_name_2"; + + GHPullRequest p1 = getRepository() + .createPullRequest("addLabelsConcurrencyIssue", "test/stable", "main", "## test"); + p1.getLabels(); + + GHPullRequest p2 = getRepository().getPullRequest(p1.getNumber()); + p2.addLabels(addedLabel2); + + Collection labels = p1.addLabels(addedLabel1); + + assertThat(labels.size(), equalTo(2)); + assertThat(labels, + containsInAnyOrder(hasProperty("name", equalTo(addedLabel1)), + hasProperty("name", equalTo(addedLabel2)))); + } + + /** + * Check non existent author. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void checkNonExistentAuthor() throws IOException { + // PR id is based on https://github.com/sahansera/TestRepo/pull/2 + final GHPullRequest pullRequest = getRepository().getPullRequest(2); + + assertThat(pullRequest.getUser(), is(notNullValue())); + assertThat(pullRequest.getUser().login, is("ghost")); + } + + /** + * Check non existent reviewer. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void checkNonExistentReviewer() throws IOException { + // PR id is based on https://github.com/sahansera/TestRepo/pull/1 + final GHPullRequest pullRequest = getRepository().getPullRequest(1); + final Optional review = pullRequest.listReviews().toList().stream().findFirst(); + final GHUser reviewer = review.get().getUser(); + + assertThat(pullRequest.getRequestedReviewers(), is(empty())); + assertThat(review, notNullValue()); + assertThat(reviewer, is(nullValue())); + } + + /** + * Check pull request reviewer. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void checkPullRequestReviewer() throws IOException { + // PR id is based on https://github.com/sahansera/TestRepo/pull/6 + final GHPullRequest pullRequest = getRepository().getPullRequest(6); + final Optional review = pullRequest.listReviews().toList().stream().findFirst(); + final GHUser reviewer = review.get().getUser(); + + assertThat(review, notNullValue()); + assertThat(reviewer, notNullValue()); + } + /** * Clean up. * @@ -63,27 +177,52 @@ public void cleanUp() throws Exception { } /** - * Creates the pull request. + * Close pull request. * * @throws Exception * the exception */ @Test - public void createPullRequest() throws Exception { - String name = "createPullRequest"; - GHRepository repo = getRepository(); - GHPullRequest p = repo.createPullRequest(name, "test/stable", "main", "## test"); + public void closePullRequest() throws Exception { + String name = "closePullRequest"; + GHPullRequest p = getRepository().createPullRequest(name, "test/stable", "main", "## test"); + // System.out.println(p.getUrl()); assertThat(p.getTitle(), equalTo(name)); - assertThat(p.canMaintainerModify(), is(false)); - assertThat(p.isDraft(), is(false)); + assertThat(getRepository().getPullRequest(p.getNumber()).getState(), equalTo(GHIssueState.OPEN)); + p.close(); + assertThat(getRepository().getPullRequest(p.getNumber()).getState(), equalTo(GHIssueState.CLOSED)); + } - // Check auto merge status of the pull request - final AutoMerge autoMerge = p.getAutoMerge(); - assertThat(autoMerge, is(notNullValue())); - assertThat(autoMerge.getCommitMessage(), equalTo("This is a auto merged squash commit message")); - assertThat(autoMerge.getCommitTitle(), equalTo("This is a auto merged squash commit")); - assertThat(autoMerge.getMergeMethod(), equalTo(GHPullRequest.MergeMethod.SQUASH)); - assertThat(autoMerge.getEnabledBy(), is(notNullValue())); + /** + * Comments objects in pull request review builder. + */ + @Test + public void commentsInPullRequestReviewBuilder() { + GHPullRequestReviewBuilder.DraftReviewComment draftReviewComment = new GHPullRequestReviewBuilder.DraftReviewComment( + "comment", + "path/to/file.txt", + 1); + assertThat(draftReviewComment.getBody(), equalTo("comment")); + assertThat(draftReviewComment.getPath(), equalTo("path/to/file.txt")); + assertThat(draftReviewComment.getPosition(), equalTo(1)); + + GHPullRequestReviewBuilder.SingleLineDraftReviewComment singleLineDraftReviewComment = new GHPullRequestReviewBuilder.SingleLineDraftReviewComment( + "comment", + "path/to/file.txt", + 2); + assertThat(singleLineDraftReviewComment.getBody(), equalTo("comment")); + assertThat(singleLineDraftReviewComment.getPath(), equalTo("path/to/file.txt")); + assertThat(singleLineDraftReviewComment.getLine(), equalTo(2)); + + GHPullRequestReviewBuilder.MultilineDraftReviewComment multilineDraftReviewComment = new GHPullRequestReviewBuilder.MultilineDraftReviewComment( + "comment", + "path/to/file.txt", + 1, + 2); + assertThat(multilineDraftReviewComment.getBody(), equalTo("comment")); + assertThat(multilineDraftReviewComment.getPath(), equalTo("path/to/file.txt")); + assertThat(multilineDraftReviewComment.getStartLine(), equalTo(1)); + assertThat(multilineDraftReviewComment.getLine(), equalTo(2)); } /** @@ -115,6 +254,249 @@ public void createDraftPullRequest() throws Exception { assertThat(p.isDraft(), is(true)); } + /** + * Creates the pull request. + * + * @throws Exception + * the exception + */ + @Test + public void createPullRequest() throws Exception { + String name = "createPullRequest"; + GHRepository repo = getRepository(); + GHPullRequest p = repo.createPullRequest(name, "test/stable", "main", "## test"); + assertThat(p.getTitle(), equalTo(name)); + assertThat(p.canMaintainerModify(), is(false)); + assertThat(p.isDraft(), is(false)); + + // Check auto merge status of the pull request + final AutoMerge autoMerge = p.getAutoMerge(); + assertThat(autoMerge, is(notNullValue())); + assertThat(autoMerge.getCommitMessage(), equalTo("This is a auto merged squash commit message")); + assertThat(autoMerge.getCommitTitle(), equalTo("This is a auto merged squash commit")); + assertThat(autoMerge.getMergeMethod(), equalTo(GHPullRequest.MergeMethod.SQUASH)); + assertThat(autoMerge.getEnabledBy(), is(notNullValue())); + } + + /** + * + * Test enabling auto merge for pull request + * + * @throws IOException + * the Exception + */ + @Test + public void enablePullRequestAutoMerge() throws IOException { + String authorEmail = "sa20207@naver.com"; + String clientMutationId = "github-api"; + String commitBody = "This is commit body."; + String commitTitle = "This is commit title."; + String expectedCommitHeadOid = "4888b44d7204dd05680e90159af839c8b1194b6d"; + + GHPullRequest pullRequest = gitHub.getRepository("seate/for-test").getPullRequest(9); + + pullRequest.enablePullRequestAutoMerge(authorEmail, + clientMutationId, + commitBody, + commitTitle, + expectedCommitHeadOid, + GHPullRequest.MergeMethod.MERGE); + + AutoMerge autoMerge = pullRequest.getAutoMerge(); + assertThat(autoMerge.getEnabledBy().getEmail(), is(authorEmail)); + assertThat(autoMerge.getCommitMessage(), is(commitBody)); + assertThat(autoMerge.getCommitTitle(), is(commitTitle)); + assertThat(autoMerge.getMergeMethod(), is(GHPullRequest.MergeMethod.MERGE)); + } + + /** + * Test enabling auto merge for pull request with no verified email throws GraphQL exception + * + * @throws IOException + * the io exception + */ + @Test + public void enablePullRequestAutoMergeFailure() throws IOException { + String authorEmail = "failureEmail@gmail.com"; + String clientMutationId = "github-api"; + String commitBody = "This is commit body."; + String commitTitle = "This is commit title."; + String expectedCommitHeadOid = "4888b44d7204dd05680e90159af839c8b1194b6d"; + + GHPullRequest pullRequest = gitHub.getRepository("seate/for-test").getPullRequest(9); + + try { + pullRequest.enablePullRequestAutoMerge(authorEmail, + clientMutationId, + commitBody, + commitTitle, + expectedCommitHeadOid, + null); + } catch (IOException e) { + assertThat(e.getMessage(), containsString("does not have a verified email")); + } + } + + /** + * Get list of commits from searched PR. + * + * This would result in a wrong API URL used, resulting in a GHFileNotFoundException. + * + * For more details, please have a look at the bug description in https://github.com/hub4j/github-api/issues/1778. + * + * @throws Exception + * the exception + */ + @Test + public void getListOfCommits() throws Exception { + String name = "getListOfCommits"; + GHPullRequestSearchBuilder builder = getRepository().searchPullRequests().isClosed(); + Optional firstPR = builder.list().toList().stream().findFirst(); + + try { + GHPullRequestCommitDetail detail = firstPR.get().listCommits().toArray()[0]; + assertThat(detail.getApiUrl().toString(), notNullValue()); + assertThat(detail.getSha(), equalTo("2d29c787b46ce61b98a1c13e05e21ebc21f49dbf")); + assertThat(detail.getCommentsUrl().toString(), + endsWith( + "/repos/hub4j-test-org/github-api/commits/2d29c787b46ce61b98a1c13e05e21ebc21f49dbf/comments")); + assertThat(detail.getUrl().toString(), + equalTo("https://github.com/hub4j-test-org/github-api/commit/2d29c787b46ce61b98a1c13e05e21ebc21f49dbf")); + + GHPullRequestCommitDetail.Commit commit = detail.getCommit(); + assertThat(commit, notNullValue()); + assertThat(commit.getAuthor().getEmail(), equalTo("bitwiseman@gmail.com")); + assertThat(commit.getCommitter().getEmail(), equalTo("bitwiseman@gmail.com")); + assertThat(commit.getMessage(), equalTo("Update README")); + assertThat(commit.getUrl().toString(), + endsWith("/repos/hub4j-test-org/github-api/git/commits/2d29c787b46ce61b98a1c13e05e21ebc21f49dbf")); + assertThat(commit.getComment_count(), equalTo(0)); + + GHPullRequestCommitDetail.Tree tree = commit.getTree(); + assertThat(tree, notNullValue()); + assertThat(tree.getSha(), equalTo("ce7a1ba95aba901cf08d9f8365410d290d6c23aa")); + assertThat(tree.getUrl().toString(), + endsWith("/repos/hub4j-test-org/github-api/git/trees/ce7a1ba95aba901cf08d9f8365410d290d6c23aa")); + + GHPullRequestCommitDetail.CommitPointer[] parents = detail.getParents(); + assertThat(parents, notNullValue()); + assertThat(parents.length, equalTo(1)); + assertThat(parents[0].getSha(), equalTo("3a09d2de4a9a1322a0ba2c3e2f54a919ca8fe353")); + assertThat(parents[0].getHtml_url().toString(), + equalTo("https://github.com/hub4j-test-org/github-api/commit/3a09d2de4a9a1322a0ba2c3e2f54a919ca8fe353")); + assertThat(parents[0].getUrl().toString(), + endsWith("/repos/hub4j-test-org/github-api/commits/3a09d2de4a9a1322a0ba2c3e2f54a919ca8fe353")); + + } catch (GHFileNotFoundException e) { + if (e.getMessage().contains("/issues/")) { + fail("Issued a request against the wrong path"); + } + } + } + + /** + * Gets the user test. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void getUserTest() throws IOException { + GHPullRequest p = getRepository().createPullRequest("getUserTest", "test/stable", "main", "## test"); + GHPullRequest prSingle = getRepository().getPullRequest(p.getNumber()); + assertThat(prSingle.getUser().root(), notNullValue()); + prSingle.getMergeable(); + assertThat(prSingle.getUser().root(), notNullValue()); + + List ghPullRequests = getRepository().getPullRequests(GHIssueState.OPEN); + for (GHPullRequest pr : ghPullRequests) { + assertThat(pr.getUser().root(), notNullValue()); + pr.getMergeable(); + assertThat(pr.getUser().root(), notNullValue()); + } + } + + /** + * Test marking a draft pull request as ready for review. + * + * @throws Exception + * the exception + */ + @Test + public void markReadyForReview() throws Exception { + // Create a draft PR first + GHPullRequest p = getRepository() + .createPullRequest("markReadyForReview", "test/stable", "main", "## test", false, true); + assertThat(p.isDraft(), is(true)); + + // Mark it ready for review + p.markReadyForReview(); + + // Verify it's no longer a draft + assertThat(p.isDraft(), is(false)); + } + + /** + * Marking a non-draft pull request as ready for review throws an exception. + * + * @throws Exception + * the exception + */ + @Test + public void markReadyForReviewNonDraft() throws Exception { + // Create a non-draft PR + GHPullRequest p = getRepository() + .createPullRequest("markReadyForReviewNonDraft", "test/stable", "main", "## test"); + assertThat(p.isDraft(), is(false)); + + // Attempting to mark a non-draft PR as ready should throw + try { + p.markReadyForReview(); + fail("Expected IllegalStateException"); + } catch (IllegalStateException e) { + assertThat(e.getMessage(), equalTo("Pull request is not a draft")); + } + } + + /** + * Merge commit SHA. + * + * @throws Exception + * the exception + */ + @Test + public void mergeCommitSHA() throws Exception { + String name = "mergeCommitSHA"; + GHRepository repo = getRepository(); + GHPullRequest p = repo.createPullRequest(name, "test/mergeable_branch", "main", "## test"); + int baseRequestCount = mockGitHub.getRequestCount(); + assertThat(p.getMergeableNoRefresh(), nullValue()); + assertThat("Used existing value", mockGitHub.getRequestCount() - baseRequestCount, equalTo(0)); + + // mergeability computation takes time, this should still be null immediately after creation + assertThat(p.getMergeable(), nullValue()); + assertThat("Asked for PR information", mockGitHub.getRequestCount() - baseRequestCount, equalTo(1)); + + for (int i = 2; i <= 10; i++) { + if (Boolean.TRUE.equals(p.getMergeable()) && p.getMergeCommitSha() != null) { + assertThat("Asked for PR information", mockGitHub.getRequestCount() - baseRequestCount, equalTo(i)); + + // make sure commit exists + GHCommit commit = repo.getCommit(p.getMergeCommitSha()); + assertThat(commit, notNullValue()); + + assertThat("Asked for PR information", mockGitHub.getRequestCount() - baseRequestCount, equalTo(i + 1)); + + return; + } + + // mergeability computation takes time. give it more chance + Thread.sleep(1000); + } + // hmm? + fail(); + } + /** * Pull request comment. * @@ -125,6 +507,7 @@ public void createDraftPullRequest() throws Exception { public void pullRequestComment() throws Exception { String name = "createPullRequestComment"; GHPullRequest p = getRepository().createPullRequest(name, "test/stable", "main", "## test"); + assertThat(p.getIssueUrl().toString(), endsWith("/repos/hub4j-test-org/github-api/issues/461")); List comments; comments = p.listComments().toList(); @@ -133,9 +516,8 @@ public void pullRequestComment() throws Exception { assertThat(comments, hasSize(0)); GHIssueComment firstComment = p.comment("First comment"); - Date firstCommentCreatedAt = firstComment.getCreatedAt(); - Date firstCommentCreatedAtPlus1Second = Date - .from(firstComment.getCreatedAt().toInstant().plus(1, ChronoUnit.SECONDS)); + Instant firstCommentCreatedAt = firstComment.getCreatedAt(); + Instant firstCommentCreatedAtPlus1Second = firstComment.getCreatedAt().plusSeconds(1); comments = p.listComments().toList(); assertThat(comments, hasSize(1)); @@ -158,13 +540,12 @@ public void pullRequestComment() throws Exception { Thread.sleep(2000); GHIssueComment secondComment = p.comment("Second comment"); - Date secondCommentCreatedAt = secondComment.getCreatedAt(); - Date secondCommentCreatedAtPlus1Second = Date - .from(secondComment.getCreatedAt().toInstant().plus(1, ChronoUnit.SECONDS)); + Instant secondCommentCreatedAt = secondComment.getCreatedAt(); + Instant secondCommentCreatedAtPlus1Second = secondComment.getCreatedAt().plusSeconds(1); assertThat( "There's an error in the setup of this test; please fix it." + " The second comment should be created at least one second after the first one.", - firstCommentCreatedAtPlus1Second.getTime() <= secondCommentCreatedAt.getTime()); + firstCommentCreatedAtPlus1Second.isBefore(secondCommentCreatedAt)); comments = p.listComments().toList(); assertThat(comments, hasSize(2)); @@ -182,139 +563,20 @@ public void pullRequestComment() throws Exception { assertThat(comments, hasSize(2)); assertThat(comments, contains(hasProperty("body", equalTo("First comment")), - hasProperty("body", equalTo("Second comment")))); - comments = p.queryComments().since(firstCommentCreatedAtPlus1Second).list().toList(); - assertThat(comments, hasSize(1)); - assertThat(comments, contains(hasProperty("body", equalTo("Second comment")))); - comments = p.queryComments().since(secondCommentCreatedAt).list().toList(); - assertThat(comments, hasSize(1)); - assertThat(comments, contains(hasProperty("body", equalTo("Second comment")))); - comments = p.queryComments().since(secondCommentCreatedAtPlus1Second).list().toList(); - assertThat(comments, hasSize(0)); - - // Test "since" with timestamp instead of Date - comments = p.queryComments().since(secondCommentCreatedAt.getTime()).list().toList(); - assertThat(comments, hasSize(1)); - assertThat(comments, contains(hasProperty("body", equalTo("Second comment")))); - } - - /** - * Get list of commits from searched PR. - * - * This would result in a wrong API URL used, resulting in a GHFileNotFoundException. - * - * For more details, please have a look at the bug description in https://github.com/hub4j/github-api/issues/1778. - * - * @throws Exception - * the exception - */ - @Test - public void getListOfCommits() throws Exception { - String name = "getListOfCommits"; - GHPullRequestSearchBuilder builder = getRepository().searchPullRequests().isClosed(); - Optional firstPR = builder.list().toList().stream().findFirst(); - - try { - String val = firstPR.get().listCommits().toArray()[0].getApiUrl().toString(); - assertThat(val, notNullValue()); - } catch (GHFileNotFoundException e) { - if (e.getMessage().contains("/issues/")) { - fail("Issued a request against the wrong path"); - } - } - } - - /** - * Close pull request. - * - * @throws Exception - * the exception - */ - @Test - public void closePullRequest() throws Exception { - String name = "closePullRequest"; - GHPullRequest p = getRepository().createPullRequest(name, "test/stable", "main", "## test"); - // System.out.println(p.getUrl()); - assertThat(p.getTitle(), equalTo(name)); - assertThat(getRepository().getPullRequest(p.getNumber()).getState(), equalTo(GHIssueState.OPEN)); - p.close(); - assertThat(getRepository().getPullRequest(p.getNumber()).getState(), equalTo(GHIssueState.CLOSED)); - } - - /** - * Pull request reviews. - * - * @throws Exception - * the exception - */ - @Test - public void pullRequestReviews() throws Exception { - String name = "testPullRequestReviews"; - GHPullRequest p = getRepository().createPullRequest(name, "test/stable", "main", "## test"); - - List reviews = p.listReviews().toList(); - assertThat(reviews.size(), is(0)); - - GHPullRequestReview draftReview = p.createReview() - .body("Some draft review") - .comment("Some niggle", "README.md", 1) - .singleLineComment("A single line comment", "README.md", 2) - .multiLineComment("A multiline comment", "README.md", 2, 3) - .create(); - assertThat(draftReview.getState(), is(GHPullRequestReviewState.PENDING)); - assertThat(draftReview.getBody(), is("Some draft review")); - assertThat(draftReview.getCommitId(), notNullValue()); - reviews = p.listReviews().toList(); - assertThat(reviews.size(), is(1)); - GHPullRequestReview review = reviews.get(0); - assertThat(review.getState(), is(GHPullRequestReviewState.PENDING)); - assertThat(review.getBody(), is("Some draft review")); - assertThat(review.getCommitId(), notNullValue()); - draftReview.submit("Some review comment", GHPullRequestReviewEvent.COMMENT); - List comments = review.listReviewComments().toList(); - assertThat(comments.size(), equalTo(3)); - GHPullRequestReviewComment comment = comments.get(0); - assertThat(comment.getBody(), equalTo("Some niggle")); - comment = comments.get(1); - assertThat(comment.getBody(), equalTo("A single line comment")); - assertThat(comment.getPosition(), equalTo(4)); - comment = comments.get(2); - assertThat(comment.getBody(), equalTo("A multiline comment")); - assertThat(comment.getPosition(), equalTo(5)); - draftReview = p.createReview().body("Some new review").comment("Some niggle", "README.md", 1).create(); - draftReview.delete(); - } - - /** - * Comments objects in pull request review builder. - */ - @Test - public void commentsInPullRequestReviewBuilder() { - GHPullRequestReviewBuilder.DraftReviewComment draftReviewComment = new GHPullRequestReviewBuilder.DraftReviewComment( - "comment", - "path/to/file.txt", - 1); - assertThat(draftReviewComment.getBody(), equalTo("comment")); - assertThat(draftReviewComment.getPath(), equalTo("path/to/file.txt")); - assertThat(draftReviewComment.getPosition(), equalTo(1)); - - GHPullRequestReviewBuilder.SingleLineDraftReviewComment singleLineDraftReviewComment = new GHPullRequestReviewBuilder.SingleLineDraftReviewComment( - "comment", - "path/to/file.txt", - 2); - assertThat(singleLineDraftReviewComment.getBody(), equalTo("comment")); - assertThat(singleLineDraftReviewComment.getPath(), equalTo("path/to/file.txt")); - assertThat(singleLineDraftReviewComment.getLine(), equalTo(2)); + hasProperty("body", equalTo("Second comment")))); + comments = p.queryComments().since(firstCommentCreatedAtPlus1Second).list().toList(); + assertThat(comments, hasSize(1)); + assertThat(comments, contains(hasProperty("body", equalTo("Second comment")))); + comments = p.queryComments().since(secondCommentCreatedAt).list().toList(); + assertThat(comments, hasSize(1)); + assertThat(comments, contains(hasProperty("body", equalTo("Second comment")))); + comments = p.queryComments().since(secondCommentCreatedAtPlus1Second).list().toList(); + assertThat(comments, hasSize(0)); - GHPullRequestReviewBuilder.MultilineDraftReviewComment multilineDraftReviewComment = new GHPullRequestReviewBuilder.MultilineDraftReviewComment( - "comment", - "path/to/file.txt", - 1, - 2); - assertThat(multilineDraftReviewComment.getBody(), equalTo("comment")); - assertThat(multilineDraftReviewComment.getPath(), equalTo("path/to/file.txt")); - assertThat(multilineDraftReviewComment.getStartLine(), equalTo(1)); - assertThat(multilineDraftReviewComment.getLine(), equalTo(2)); + // Test "since" with timestamp instead of Date + comments = p.queryComments().since(secondCommentCreatedAt.toEpochMilli()).list().toList(); + assertThat(comments, hasSize(1)); + assertThat(comments, contains(hasProperty("body", equalTo("Second comment")))); } /** @@ -337,12 +599,14 @@ public void pullRequestReviewComments() throws Exception { .body("A single line review comment") .path("README.md") .line(2) + .side(RIGHT) .create(); p.createReviewComment() .commitId(p.getHead().getSha()) .body("A multiline review comment") .path("README.md") .lines(2, 3) + .sides(RIGHT, RIGHT) .create(); List comments = p.listReviewComments().toList(); assertThat(comments.size(), equalTo(3)); @@ -362,7 +626,7 @@ public void pullRequestReviewComments() throws Exception { assertThat(comment.getStartSide(), equalTo(GHPullRequestReviewComment.Side.UNKNOWN)); assertThat(comment.getLine(), equalTo(1)); assertThat(comment.getOriginalLine(), equalTo(1)); - assertThat(comment.getSide(), equalTo(GHPullRequestReviewComment.Side.LEFT)); + assertThat(comment.getSide(), equalTo(LEFT)); assertThat(comment.getPullRequestUrl(), notNullValue()); assertThat(comment.getPullRequestUrl().toString(), containsString("hub4j-test-org/github-api/pulls/")); assertThat(comment.getBodyHtml(), nullValue()); @@ -375,11 +639,14 @@ public void pullRequestReviewComments() throws Exception { comment = comments.get(1); assertThat(comment.getBody(), equalTo("A single line review comment")); assertThat(comment.getLine(), equalTo(2)); + assertThat(comment.getSide(), equalTo(RIGHT)); comment = comments.get(2); assertThat(comment.getBody(), equalTo("A multiline review comment")); assertThat(comment.getStartLine(), equalTo(2)); assertThat(comment.getLine(), equalTo(3)); + assertThat(comment.getStartSide(), equalTo(RIGHT)); + assertThat(comment.getSide(), equalTo(RIGHT)); comment.createReaction(ReactionContent.EYES); GHReaction toBeRemoved = comment.createReaction(ReactionContent.CONFUSED); @@ -412,306 +679,97 @@ public void pullRequestReviewComments() throws Exception { GHReaction reaction = comment.createReaction(ReactionContent.CONFUSED); assertThat(reaction.getContent(), equalTo(ReactionContent.CONFUSED)); - reactions = comment.listReactions().toList(); - assertThat(reactions.size(), equalTo(8)); - - comment.deleteReaction(reaction); - - reactions = comment.listReactions().toList(); - assertThat(reactions.size(), equalTo(7)); - - GHPullRequestReviewComment reply = comment.reply("This is a reply."); - assertThat(reply.getInReplyToId(), equalTo(comment.getId())); - comments = p.listReviewComments().toList(); - - assertThat(comments.size(), equalTo(4)); - - comment.update("Updated review comment"); - comments = p.listReviewComments().toList(); - comment = comments.get(2); - assertThat(comment.getBody(), equalTo("Updated review comment")); - - comment.delete(); - comments = p.listReviewComments().toList(); - // Reply is still present after delete of original comment, but no longer has replyToId - assertThat(comments.size(), equalTo(3)); - assertThat(comments.get(2).getId(), equalTo(reply.getId())); - assertThat(comments.get(2).getInReplyToId(), equalTo(-1L)); - } finally { - p.close(); - } - } - - /** - * Test pull request review requests. - * - * @throws Exception - * the exception - */ - @Test - public void testPullRequestReviewRequests() throws Exception { - String name = "testPullRequestReviewRequests"; - GHPullRequest p = getRepository().createPullRequest(name, "test/stable", "main", "## test"); - // System.out.println(p.getUrl()); - assertThat(p.getRequestedReviewers(), is(empty())); - - GHUser kohsuke2 = gitHub.getUser("kohsuke2"); - p.requestReviewers(Collections.singletonList(kohsuke2)); - p.refresh(); - assertThat(p.getRequestedReviewers(), is(not(empty()))); - } - - /** - * Test pull request team review requests. - * - * @throws Exception - * the exception - */ - @Test - public void testPullRequestTeamReviewRequests() throws Exception { - String name = "testPullRequestTeamReviewRequests"; - GHPullRequest p = getRepository().createPullRequest(name, "test/stable", "main", "## test"); - // System.out.println(p.getUrl()); - assertThat(p.getRequestedReviewers(), is(empty())); - - GHOrganization testOrg = gitHub.getOrganization("hub4j-test-org"); - GHTeam testTeam = testOrg.getTeamBySlug("dummy-team"); - - p.requestTeamReviewers(Collections.singletonList(testTeam)); - - int baseRequestCount = mockGitHub.getRequestCount(); - p.refresh(); - assertThat("We should not eagerly load organizations for teams", - mockGitHub.getRequestCount() - baseRequestCount, - equalTo(1)); - assertThat(p.getRequestedTeams().size(), equalTo(1)); - assertThat("We should not eagerly load organizations for teams", - mockGitHub.getRequestCount() - baseRequestCount, - equalTo(1)); - assertThat("Org should be queried for automatically if asked for", - p.getRequestedTeams().get(0).getOrganization(), - notNullValue()); - assertThat("Request count should show lazy load occurred", - mockGitHub.getRequestCount() - baseRequestCount, - equalTo(2)); - } - - /** - * Merge commit SHA. - * - * @throws Exception - * the exception - */ - @Test - public void mergeCommitSHA() throws Exception { - String name = "mergeCommitSHA"; - GHRepository repo = getRepository(); - GHPullRequest p = repo.createPullRequest(name, "test/mergeable_branch", "main", "## test"); - int baseRequestCount = mockGitHub.getRequestCount(); - assertThat(p.getMergeableNoRefresh(), nullValue()); - assertThat("Used existing value", mockGitHub.getRequestCount() - baseRequestCount, equalTo(0)); - - // mergeability computation takes time, this should still be null immediately after creation - assertThat(p.getMergeable(), nullValue()); - assertThat("Asked for PR information", mockGitHub.getRequestCount() - baseRequestCount, equalTo(1)); - - for (int i = 2; i <= 10; i++) { - if (Boolean.TRUE.equals(p.getMergeable()) && p.getMergeCommitSha() != null) { - assertThat("Asked for PR information", mockGitHub.getRequestCount() - baseRequestCount, equalTo(i)); - - // make sure commit exists - GHCommit commit = repo.getCommit(p.getMergeCommitSha()); - assertThat(commit, notNullValue()); - - assertThat("Asked for PR information", mockGitHub.getRequestCount() - baseRequestCount, equalTo(i + 1)); - - return; - } - - // mergeability computation takes time. give it more chance - Thread.sleep(1000); - } - // hmm? - fail(); - } - - /** - * Sets the base branch. - * - * @throws Exception - * the exception - */ - @Test - public void setBaseBranch() throws Exception { - String prName = "testSetBaseBranch"; - String originalBaseBranch = "main"; - String newBaseBranch = "gh-pages"; - - GHPullRequest pullRequest = getRepository().createPullRequest(prName, "test/stable", "main", "## test"); - - assertThat("Pull request base branch is supposed to be " + originalBaseBranch, - pullRequest.getBase().getRef(), - equalTo(originalBaseBranch)); - - GHPullRequest responsePullRequest = pullRequest.setBaseBranch(newBaseBranch); - - assertThat("Pull request base branch is supposed to be " + newBaseBranch, - responsePullRequest.getBase().getRef(), - equalTo(newBaseBranch)); - } - - /** - * Sets the base branch non existing. - * - * @throws Exception - * the exception - */ - @Test - public void setBaseBranchNonExisting() throws Exception { - String prName = "testSetBaseBranchNonExisting"; - String originalBaseBranch = "main"; - String newBaseBranch = "non-existing"; - - GHPullRequest pullRequest = getRepository().createPullRequest(prName, "test/stable", "main", "## test"); - - assertThat("Pull request base branch is supposed to be " + originalBaseBranch, - pullRequest.getBase().getRef(), - equalTo(originalBaseBranch)); - - try { - pullRequest.setBaseBranch(newBaseBranch); - } catch (HttpException e) { - assertThat(e, instanceOf(HttpException.class)); - assertThat(e.toString(), containsString("Proposed base branch 'non-existing' was not found")); - } - - pullRequest.close(); - } - - /** - * Update outdated branches unexpected head. - * - * @throws Exception - * the exception - */ - @Test - public void updateOutdatedBranchesUnexpectedHead() throws Exception { - String prName = "testUpdateOutdatedBranches"; - String outdatedRefName = "refs/heads/outdated"; - GHRepository repository = gitHub.getOrganization("hub4j-test-org").getRepository("updateOutdatedBranches"); - - GHRef outdatedRef = repository.getRef(outdatedRefName); - outdatedRef.updateTo("6440189369f9f33b2366556a94dbc26f2cfdd969", true); - - GHPullRequest outdatedPullRequest = repository.createPullRequest(prName, "outdated", "main", "## test"); - - do { - Thread.sleep(5000); - outdatedPullRequest.refresh(); - } while (outdatedPullRequest.getMergeableState().equalsIgnoreCase("unknown")); - - assertThat("Pull request is supposed to be not up to date", - outdatedPullRequest.getMergeableState(), - equalTo("behind")); - - outdatedRef.updateTo("f567328eb81270487864963b7d7446953353f2b5", true); - - try { - outdatedPullRequest.updateBranch(); - } catch (HttpException e) { - assertThat(e, instanceOf(HttpException.class)); - assertThat(e.toString(), containsString("expected head sha didn’t match current head ref.")); - } - - outdatedPullRequest.close(); - } - - /** - * Update outdated branches. - * - * @throws Exception - * the exception - */ - @Test - public void updateOutdatedBranches() throws Exception { - String prName = "testUpdateOutdatedBranches"; - String outdatedRefName = "refs/heads/outdated"; - GHRepository repository = gitHub.getOrganization("hub4j-test-org").getRepository("updateOutdatedBranches"); - - repository.getRef(outdatedRefName).updateTo("6440189369f9f33b2366556a94dbc26f2cfdd969", true); - - GHPullRequest outdatedPullRequest = repository.createPullRequest(prName, "outdated", "main", "## test"); - - do { - Thread.sleep(5000); - outdatedPullRequest.refresh(); - } while (outdatedPullRequest.getMergeableState().equalsIgnoreCase("unknown")); - - assertThat("Pull request is supposed to be not up to date", - outdatedPullRequest.getMergeableState(), - equalTo("behind")); + reactions = comment.listReactions().toList(); + assertThat(reactions.size(), equalTo(8)); - outdatedPullRequest.updateBranch(); - outdatedPullRequest.refresh(); + comment.deleteReaction(reaction); - assertThat("Pull request is supposed to be up to date", outdatedPullRequest.getMergeableState(), not("behind")); + reactions = comment.listReactions().toList(); + assertThat(reactions.size(), equalTo(7)); - outdatedPullRequest.close(); - } + GHPullRequestReviewComment reply = comment.reply("This is a reply."); + assertThat(reply.getInReplyToId(), equalTo(comment.getId())); + comments = p.listReviewComments().toList(); - /** - * Squash merge. - * - * @throws Exception - * the exception - */ - @Test - public void squashMerge() throws Exception { - String name = "squashMerge"; - String branchName = "test/" + name; - GHRef mainRef = getRepository().getRef("heads/main"); - GHRef branchRef = getRepository().createRef("refs/heads/" + branchName, mainRef.getObject().getSha()); + assertThat(comments.size(), equalTo(4)); - getRepository().createContent().content(name).path(name).message(name).branch(branchName).commit(); - Thread.sleep(1000); - GHPullRequest p = getRepository().createPullRequest(name, branchName, "main", "## test squash"); - Thread.sleep(1000); - p.merge("squash merge", null, GHPullRequest.MergeMethod.SQUASH); + comment.update("Updated review comment"); + comments = p.listReviewComments().toList(); + comment = comments.get(2); + assertThat(comment.getBody(), equalTo("Updated review comment")); + + comment.delete(); + comments = p.listReviewComments().toList(); + // Reply is still present after delete of original comment, but no longer has replyToId + assertThat(comments.size(), equalTo(3)); + assertThat(comments.get(2).getId(), equalTo(reply.getId())); + assertThat(comments.get(2).getInReplyToId(), equalTo(-1L)); + } finally { + p.close(); + } } /** - * Update content squash merge. + * Pull request reviews. * * @throws Exception * the exception */ @Test - public void updateContentSquashMerge() throws Exception { - String name = "updateContentSquashMerge"; - String branchName = "test/" + name; + public void pullRequestReviews() throws Exception { + String name = "testPullRequestReviews"; + GHPullRequest p = getRepository().createPullRequest(name, "test/stable", "main", "## test"); - GHRef mainRef = getRepository().getRef("heads/main"); - GHRef branchRef = getRepository().createRef("refs/heads/" + branchName, mainRef.getObject().getSha()); + List reviews = p.listReviews().toList(); + assertThat(reviews.size(), is(0)); - GHContentUpdateResponse response = getRepository().createContent() - .content(name) - .path(name) - .branch(branchName) - .message(name) - .commit(); + GHPullRequestReview draftReview = p.createReview() + .body("Some draft review") + .comment("Some niggle", "README.md", 1) + .singleLineComment("A single line comment", "README.md", 2) + .multiLineComment("A multiline comment", "README.md", 2, 3) + .create(); + assertThat(draftReview.getState(), is(GHPullRequestReviewState.PENDING)); + assertThat(draftReview.getBody(), is("Some draft review")); + assertThat(draftReview.getCommitId(), notNullValue()); + reviews = p.listReviews().toList(); + assertThat(reviews.size(), is(1)); + GHPullRequestReview review = reviews.get(0); + assertThat(review.getState(), is(GHPullRequestReviewState.PENDING)); + assertThat(review.getBody(), is("Some draft review")); + assertThat(review.getCommitId(), notNullValue()); + draftReview.submit("Some review comment", GHPullRequestReviewEvent.COMMENT); + List comments = review.listReviewComments().toList(); + assertThat(comments.size(), equalTo(3)); + GHPullRequestReview.ReviewComment comment = comments.get(0); + assertThat(comment.getBody(), equalTo("Some niggle")); + assertThat(comment.getPath(), equalTo("README.md")); + assertThat(comment.getCommitId(), equalTo("07374fe73aff1c2024a8d4114b32406c7a8e89b7")); + assertThat(comment.getOriginalCommitId(), equalTo("07374fe73aff1c2024a8d4114b32406c7a8e89b7")); + assertThat(comment.getDiffHunk(), notNullValue()); + assertThat(comment.getAuthorAssociation(), equalTo(GHCommentAuthorAssociation.MEMBER)); + assertThat(comment.getOriginalPosition(), equalTo(1)); + assertThat(comment.getHtmlUrl(), notNullValue()); + assertThat(comment.getPullRequestReviewId(), equalTo(2121304234L)); + assertThat(comment.getPullRequestUrl(), notNullValue()); + assertThat(comment.getReactions(), notNullValue()); + comment = comments.get(1); + assertThat(comment.getBody(), equalTo("A single line comment")); + assertThat(comment.getPosition(), equalTo(4)); + comment = comments.get(2); + assertThat(comment.getBody(), equalTo("A multiline comment")); + assertThat(comment.getPosition(), equalTo(5)); - Thread.sleep(1000); + // Verify that readPullRequestReviewComment() fetches the full comment with line data + GHPullRequestReviewComment fullComment = comments.get(0).readPullRequestReviewComment(); + assertThat(fullComment.getBody(), equalTo("Some niggle")); + assertThat(fullComment.getLine(), equalTo(1)); + assertThat(fullComment.getParent().getNumber(), equalTo(p.getNumber())); + fullComment.refresh(); + assertThat(fullComment.getLine(), equalTo(1)); - getRepository().createContent() - .content(name + name) - .path(name) - .branch(branchName) - .message(name) - .sha(response.getContent().getSha()) - .commit(); - GHPullRequest p = getRepository().createPullRequest(name, branchName, "main", "## test squash"); - Thread.sleep(1000); - p.merge("squash merge", null, GHPullRequest.MergeMethod.SQUASH); + draftReview = p.createReview().body("Some new review").comment("Some niggle", "README.md", 1).create(); + draftReview.delete(); } /** @@ -732,6 +790,7 @@ public void queryPullRequestsQualifiedHead() throws Exception { .state(GHIssueState.OPEN) .head("hub4j-test-org:test/stable") .base("main") + .pageSize(5) .list() .toList(); assertThat(prs, notNullValue()); @@ -765,87 +824,61 @@ public void queryPullRequestsUnqualifiedHead() throws Exception { } /** - * Sets the labels. + * Create/Delete reaction for pull requests. * * @throws Exception * the exception */ @Test - // Requires push access to the test repo to pass - public void setLabels() throws Exception { - GHPullRequest p = getRepository().createPullRequest("setLabels", "test/stable", "main", "## test"); - String label = "setLabels_label_name"; - p.setLabels(label); + public void reactions() throws Exception { + String name = "createPullRequest"; + GHRepository repo = getRepository(); + GHPullRequest p = repo.createPullRequest(name, "test/stable", "main", "## test"); - Collection labels = getRepository().getPullRequest(p.getNumber()).getLabels(); - assertThat(labels.size(), equalTo(1)); - GHLabel savedLabel = labels.iterator().next(); - assertThat(savedLabel.getName(), equalTo(label)); - assertThat(savedLabel.getId(), notNullValue()); - assertThat(savedLabel.getNodeId(), notNullValue()); - assertThat(savedLabel.isDefault(), is(false)); + assertThat(p.listReactions().toList(), hasSize(0)); + GHReaction reaction = p.createReaction(ReactionContent.CONFUSED); + assertThat(p.listReactions().toList(), hasSize(1)); + + p.deleteReaction(reaction); + assertThat(p.listReactions().toList(), hasSize(0)); } /** - * Adds the labels. + * Test refreshing a PR coming from the search results. * * @throws Exception * the exception */ @Test - // Requires push access to the test repo to pass - public void addLabels() throws Exception { - GHPullRequest p = getRepository().createPullRequest("addLabels", "test/stable", "main", "## test"); - String addedLabel1 = "addLabels_label_name_1"; - String addedLabel2 = "addLabels_label_name_2"; - String addedLabel3 = "addLabels_label_name_3"; - - List resultingLabels = p.addLabels(addedLabel1); - assertThat(resultingLabels.size(), equalTo(1)); - GHLabel ghLabel = resultingLabels.get(0); - assertThat(ghLabel.getName(), equalTo(addedLabel1)); + public void refreshFromSearchResults() throws Exception { + // To re-record, uncomment the Thread.sleep() calls below + snapshotNotAllowed(); - int requestCount = mockGitHub.getRequestCount(); - resultingLabels = p.addLabels(addedLabel2, addedLabel3); - // multiple labels can be added with one api call - assertThat(mockGitHub.getRequestCount(), equalTo(requestCount + 1)); + String prName = "refreshFromSearchResults"; + GHRepository repository = getRepository(); - assertThat(resultingLabels.size(), equalTo(3)); - assertThat(resultingLabels, - containsInAnyOrder(hasProperty("name", equalTo(addedLabel1)), - hasProperty("name", equalTo(addedLabel2)), - hasProperty("name", equalTo(addedLabel3)))); + repository.createPullRequest(prName, "test/stable", "main", "## test"); - // Adding a label which is already present does not throw an error - resultingLabels = p.addLabels(ghLabel); - assertThat(resultingLabels.size(), equalTo(3)); - } + // we need to wait a bit for the pull request to be indexed by GitHub + // Thread.sleep(2000); - /** - * Adds the labels concurrency issue. - * - * @throws Exception - * the exception - */ - @Test - // Requires push access to the test repo to pass - public void addLabelsConcurrencyIssue() throws Exception { - String addedLabel1 = "addLabelsConcurrencyIssue_label_name_1"; - String addedLabel2 = "addLabelsConcurrencyIssue_label_name_2"; + GHPullRequest pullRequestFromSearchResults = repository.searchPullRequests() + .isOpen() + .titleLike(prName) + .list() + .toList() + .get(0); - GHPullRequest p1 = getRepository() - .createPullRequest("addLabelsConcurrencyIssue", "test/stable", "main", "## test"); - p1.getLabels(); + pullRequestFromSearchResults.getMergeableState(); - GHPullRequest p2 = getRepository().getPullRequest(p1.getNumber()); - p2.addLabels(addedLabel2); + // wait a bit for the mergeable state to get populated + // Thread.sleep(5000); - Collection labels = p1.addLabels(addedLabel1); + assertThat("Pull request is supposed to have been refreshed and have a mergeable state", + pullRequestFromSearchResults.getMergeableState(), + equalTo("clean")); - assertThat(labels.size(), equalTo(2)); - assertThat(labels, - containsInAnyOrder(hasProperty("name", equalTo(addedLabel1)), - hasProperty("name", equalTo(addedLabel2)))); + pullRequestFromSearchResults.close(); } /** @@ -872,168 +905,295 @@ public void removeLabels() throws Exception { // each label deleted is a separate api call assertThat(mockGitHub.getRequestCount(), equalTo(requestCount + 2)); - assertThat(resultingLabels.size(), equalTo(1)); - assertThat(resultingLabels.get(0).getName(), equalTo(label1)); + assertThat(resultingLabels.size(), equalTo(1)); + assertThat(resultingLabels.get(0).getName(), equalTo(label1)); + + // Removing some labels that are not present does not throw + // This is consistent with earlier behavior and with addLabels() + p.removeLabels(ghLabel3); + + // Calling removeLabel() on label that is not present will throw + try { + p.removeLabel(label3); + fail("Expected GHFileNotFoundException"); + } catch (GHFileNotFoundException e) { + assertThat(e.getMessage(), containsString("Label does not exist")); + } + } + + /** + * Sets the assignee. + * + * @throws Exception + * the exception + */ + @Test + // Requires push access to the test repo to pass + public void setAssignee() throws Exception { + GHPullRequest p = getRepository().createPullRequest("setAssignee", "test/stable", "main", "## test"); + GHMyself user = gitHub.getMyself(); + p.assignTo(user); + + assertThat(getRepository().getPullRequest(p.getNumber()).getAssignee(), equalTo(user)); + } + + /** + * Sets the base branch. + * + * @throws Exception + * the exception + */ + @Test + public void setBaseBranch() throws Exception { + String prName = "testSetBaseBranch"; + String originalBaseBranch = "main"; + String newBaseBranch = "gh-pages"; + + GHPullRequest pullRequest = getRepository().createPullRequest(prName, "test/stable", "main", "## test"); + + assertThat("Pull request base branch is supposed to be " + originalBaseBranch, + pullRequest.getBase().getRef(), + equalTo(originalBaseBranch)); + + GHPullRequest responsePullRequest = pullRequest.setBaseBranch(newBaseBranch); + + assertThat("Pull request base branch is supposed to be " + newBaseBranch, + responsePullRequest.getBase().getRef(), + equalTo(newBaseBranch)); + } + + /** + * Sets the base branch non existing. + * + * @throws Exception + * the exception + */ + @Test + public void setBaseBranchNonExisting() throws Exception { + String prName = "testSetBaseBranchNonExisting"; + String originalBaseBranch = "main"; + String newBaseBranch = "non-existing"; + + GHPullRequest pullRequest = getRepository().createPullRequest(prName, "test/stable", "main", "## test"); - // Removing some labels that are not present does not throw - // This is consistent with earlier behavior and with addLabels() - p.removeLabels(ghLabel3); + assertThat("Pull request base branch is supposed to be " + originalBaseBranch, + pullRequest.getBase().getRef(), + equalTo(originalBaseBranch)); - // Calling removeLabel() on label that is not present will throw try { - p.removeLabel(label3); - fail("Expected GHFileNotFoundException"); - } catch (GHFileNotFoundException e) { - assertThat(e.getMessage(), containsString("Label does not exist")); + pullRequest.setBaseBranch(newBaseBranch); + } catch (HttpException e) { + assertThat(e, instanceOf(HttpException.class)); + assertThat(e.toString(), containsString("Proposed base branch 'non-existing' was not found")); } + + pullRequest.close(); } /** - * Sets the assignee. + * Sets the labels. * * @throws Exception * the exception */ @Test // Requires push access to the test repo to pass - public void setAssignee() throws Exception { - GHPullRequest p = getRepository().createPullRequest("setAssignee", "test/stable", "main", "## test"); - GHMyself user = gitHub.getMyself(); - p.assignTo(user); + public void setLabels() throws Exception { + GHPullRequest p = getRepository().createPullRequest("setLabels", "test/stable", "main", "## test"); + String label = "setLabels_label_name"; + p.setLabels(label); - assertThat(getRepository().getPullRequest(p.getNumber()).getAssignee(), equalTo(user)); + Collection labels = getRepository().getPullRequest(p.getNumber()).getLabels(); + assertThat(labels.size(), equalTo(1)); + GHLabel savedLabel = labels.iterator().next(); + assertThat(savedLabel.getName(), equalTo(label)); + assertThat(savedLabel.getId(), notNullValue()); + assertThat(savedLabel.getNodeId(), notNullValue()); + assertThat(savedLabel.isDefault(), is(false)); } /** - * Gets the user test. + * Squash merge. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void getUserTest() throws IOException { - GHPullRequest p = getRepository().createPullRequest("getUserTest", "test/stable", "main", "## test"); - GHPullRequest prSingle = getRepository().getPullRequest(p.getNumber()); - assertThat(prSingle.getUser().root(), notNullValue()); - prSingle.getMergeable(); - assertThat(prSingle.getUser().root(), notNullValue()); + public void squashMerge() throws Exception { + String name = "squashMerge"; + String branchName = "test/" + name; + GHRef mainRef = getRepository().getRef("heads/main"); + GHRef branchRef = getRepository().createRef("refs/heads/" + branchName, mainRef.getObject().getSha()); - PagedIterable ghPullRequests = getRepository().queryPullRequests() - .state(GHIssueState.OPEN) - .list(); - for (GHPullRequest pr : ghPullRequests) { - assertThat(pr.getUser().root(), notNullValue()); - pr.getMergeable(); - assertThat(pr.getUser().root(), notNullValue()); - } + getRepository().createContent().content(name).path(name).message(name).branch(branchName).commit(); + Thread.sleep(1000); + GHPullRequest p = getRepository().createPullRequest(name, branchName, "main", "## test squash"); + Thread.sleep(1000); + p.merge("squash merge", null, GHPullRequest.MergeMethod.SQUASH); } /** - * Check non existent reviewer. + * Test pull request review requests. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void checkNonExistentReviewer() throws IOException { - // PR id is based on https://github.com/sahansera/TestRepo/pull/1 - final GHPullRequest pullRequest = getRepository().getPullRequest(1); - final Optional review = pullRequest.listReviews().toList().stream().findFirst(); - final GHUser reviewer = review.get().getUser(); + public void testPullRequestReviewRequests() throws Exception { + String name = "testPullRequestReviewRequests"; + GHPullRequest p = getRepository().createPullRequest(name, "test/stable", "main", "## test"); + // System.out.println(p.getUrl()); + assertThat(p.getRequestedReviewers(), is(empty())); - assertThat(pullRequest.getRequestedReviewers(), is(empty())); - assertThat(review, notNullValue()); - assertThat(reviewer, is(nullValue())); + GHUser kohsuke2 = gitHub.getUser("kohsuke2"); + p.requestReviewers(Collections.singletonList(kohsuke2)); + p.refresh(); + assertThat(p.getRequestedReviewers(), is(not(empty()))); } /** - * Check non existent author. + * Test pull request team review requests. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void checkNonExistentAuthor() throws IOException { - // PR id is based on https://github.com/sahansera/TestRepo/pull/2 - final GHPullRequest pullRequest = getRepository().getPullRequest(2); + public void testPullRequestTeamReviewRequests() throws Exception { + String name = "testPullRequestTeamReviewRequests"; + GHPullRequest p = getRepository().createPullRequest(name, "test/stable", "main", "## test"); + // System.out.println(p.getUrl()); + assertThat(p.getRequestedReviewers(), is(empty())); - assertThat(pullRequest.getUser(), is(notNullValue())); - assertThat(pullRequest.getUser().login, is("ghost")); + GHOrganization testOrg = gitHub.getOrganization("hub4j-test-org"); + GHTeam testTeam = testOrg.getTeamBySlug("dummy-team"); + + p.requestTeamReviewers(Collections.singletonList(testTeam)); + + int baseRequestCount = mockGitHub.getRequestCount(); + p.refresh(); + assertThat("We should not eagerly load organizations for teams", + mockGitHub.getRequestCount() - baseRequestCount, + equalTo(1)); + assertThat(p.getRequestedTeams().size(), equalTo(1)); + assertThat("We should not eagerly load organizations for teams", + mockGitHub.getRequestCount() - baseRequestCount, + equalTo(1)); + assertThat("Org should be queried for automatically if asked for", + p.getRequestedTeams().get(0).getOrganization(), + notNullValue()); + assertThat("Request count should show lazy load occurred", + mockGitHub.getRequestCount() - baseRequestCount, + equalTo(2)); } /** - * Check pull request reviewer. + * Update content squash merge. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void checkPullRequestReviewer() throws IOException { - // PR id is based on https://github.com/sahansera/TestRepo/pull/6 - final GHPullRequest pullRequest = getRepository().getPullRequest(6); - final Optional review = pullRequest.listReviews().toList().stream().findFirst(); - final GHUser reviewer = review.get().getUser(); + public void updateContentSquashMerge() throws Exception { + String name = "updateContentSquashMerge"; + String branchName = "test/" + name; - assertThat(review, notNullValue()); - assertThat(reviewer, notNullValue()); + GHRef mainRef = getRepository().getRef("heads/main"); + GHRef branchRef = getRepository().createRef("refs/heads/" + branchName, mainRef.getObject().getSha()); + + GHContentUpdateResponse response = getRepository().createContent() + .content(name) + .path(name) + .branch(branchName) + .message(name) + .commit(); + + Thread.sleep(1000); + + getRepository().createContent() + .content(name + name) + .path(name) + .branch(branchName) + .message(name) + .sha(response.getContent().getSha()) + .commit(); + GHPullRequest p = getRepository().createPullRequest(name, branchName, "main", "## test squash"); + Thread.sleep(1000); + p.merge("squash merge", null, GHPullRequest.MergeMethod.SQUASH); } /** - * Create/Delete reaction for pull requests. + * Update outdated branches. * * @throws Exception * the exception */ @Test - public void reactions() throws Exception { - String name = "createPullRequest"; - GHRepository repo = getRepository(); - GHPullRequest p = repo.createPullRequest(name, "test/stable", "main", "## test"); + public void updateOutdatedBranches() throws Exception { + String prName = "testUpdateOutdatedBranches"; + String outdatedRefName = "refs/heads/outdated"; + GHRepository repository = gitHub.getOrganization("hub4j-test-org").getRepository("updateOutdatedBranches"); - assertThat(p.listReactions().toList(), hasSize(0)); - GHReaction reaction = p.createReaction(ReactionContent.CONFUSED); - assertThat(p.listReactions().toList(), hasSize(1)); + repository.getRef(outdatedRefName).updateTo("6440189369f9f33b2366556a94dbc26f2cfdd969", true); - p.deleteReaction(reaction); - assertThat(p.listReactions().toList(), hasSize(0)); + GHPullRequest outdatedPullRequest = repository.createPullRequest(prName, "outdated", "main", "## test"); + + do { + Thread.sleep(5000); + outdatedPullRequest.refresh(); + } while (outdatedPullRequest.getMergeableState().equalsIgnoreCase("unknown")); + + assertThat("Pull request is supposed to be not up to date", + outdatedPullRequest.getMergeableState(), + equalTo("behind")); + + outdatedPullRequest.updateBranch(); + outdatedPullRequest.refresh(); + + assertThat("Pull request is supposed to be up to date", outdatedPullRequest.getMergeableState(), not("behind")); + + outdatedPullRequest.close(); } /** - * Test refreshing a PR coming from the search results. + * Update outdated branches unexpected head. * * @throws Exception * the exception */ @Test - public void refreshFromSearchResults() throws Exception { - // To re-record, uncomment the Thread.sleep() calls below - snapshotNotAllowed(); + public void updateOutdatedBranchesUnexpectedHead() throws Exception { + String prName = "testUpdateOutdatedBranches"; + String outdatedRefName = "refs/heads/outdated"; + GHRepository repository = gitHub.getOrganization("hub4j-test-org").getRepository("updateOutdatedBranches"); - String prName = "refreshFromSearchResults"; - GHRepository repository = getRepository(); + GHRef outdatedRef = repository.getRef(outdatedRefName); + outdatedRef.updateTo("6440189369f9f33b2366556a94dbc26f2cfdd969", true); - repository.createPullRequest(prName, "test/stable", "main", "## test"); + GHPullRequest outdatedPullRequest = repository.createPullRequest(prName, "outdated", "main", "## test"); - // we need to wait a bit for the pull request to be indexed by GitHub - // Thread.sleep(2000); + do { + Thread.sleep(5000); + outdatedPullRequest.refresh(); + } while (outdatedPullRequest.getMergeableState().equalsIgnoreCase("unknown")); - GHPullRequest pullRequestFromSearchResults = repository.searchPullRequests() - .isOpen() - .titleLike(prName) - .list() - .toList() - .get(0); + assertThat("Pull request is supposed to be not up to date", + outdatedPullRequest.getMergeableState(), + equalTo("behind")); - pullRequestFromSearchResults.getMergeableState(); + outdatedRef.updateTo("f567328eb81270487864963b7d7446953353f2b5", true); - // wait a bit for the mergeable state to get populated - // Thread.sleep(5000); + try { + outdatedPullRequest.updateBranch(); + } catch (HttpException e) { + assertThat(e, instanceOf(HttpException.class)); + assertThat(e.toString(), containsString("expected head sha didn’t match current head ref.")); + } - assertThat("Pull request is supposed to have been refreshed and have a mergeable state", - pullRequestFromSearchResults.getMergeableState(), - equalTo("clean")); + outdatedPullRequest.close(); + } - pullRequestFromSearchResults.close(); + private GHRepository getRepository(GitHub gitHub) throws IOException { + return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); } /** @@ -1046,8 +1206,4 @@ public void refreshFromSearchResults() throws Exception { protected GHRepository getRepository() throws IOException { return getRepository(gitHub); } - - private GHRepository getRepository(GitHub gitHub) throws IOException { - return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); - } } diff --git a/src/test/java/org/kohsuke/github/GHRateLimitTest.java b/src/test/java/org/kohsuke/github/GHRateLimitTest.java index 5ec59ebb50..66fcc21df1 100644 --- a/src/test/java/org/kohsuke/github/GHRateLimitTest.java +++ b/src/test/java/org/kohsuke/github/GHRateLimitTest.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.time.Duration; +import java.time.Instant; import java.util.Date; import java.util.HashMap; @@ -38,12 +39,16 @@ */ public class GHRateLimitTest extends AbstractGitHubWireMockTest { - /** The rate limit. */ - GHRateLimit rateLimit = null; + private static GHRepository getRepository(GitHub gitHub) throws IOException { + return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); + } /** The previous limit. */ GHRateLimit previousLimit = null; + /** The rate limit. */ + GHRateLimit rateLimit = null; + /** * Instantiates a new GH rate limit test. */ @@ -51,202 +56,6 @@ public GHRateLimitTest() { useDefaultGitHub = false; } - /** - * Gets the wire mock options. - * - * @return the wire mock options - */ - @Override - protected WireMockConfiguration getWireMockOptions() { - return super.getWireMockOptions().extensions(templating.newResponseTransformer()); - } - - /** - * Test git hub rate limit. - * - * @throws Exception - * the exception - */ - @Test - public void testGitHubRateLimit() throws Exception { - // Customized response that templates the date to keep things working - snapshotNotAllowed(); - GHRateLimit.UnknownLimitRecord.reset(); - - assertThat(mockGitHub.getRequestCount(), equalTo(0)); - - // 4897 is just the what the limit was when the snapshot was taken - previousLimit = GHRateLimit.fromRecord( - new GHRateLimit.Record(5000, - 4897, - (templating.testStartDate.getTime() + Duration.ofHours(1).toMillis()) / 1000L), - RateLimitTarget.CORE); - - // ------------------------------------------------------------- - // /user gets response with rate limit information - gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()).build(); - gitHub.getMyself(); - - assertThat(mockGitHub.getRequestCount(), equalTo(1)); - - // Since we already had rate limit info these don't request again - rateLimit = gitHub.lastRateLimit(); - verifyRateLimitValues(previousLimit, previousLimit.getRemaining()); - previousLimit = rateLimit; - - GHRateLimit headerRateLimit = rateLimit; - - // Give this a moment - Thread.sleep(1500); - - // ratelimit() uses cached rate limit if available and not expired - assertThat(gitHub.rateLimit(), sameInstance(headerRateLimit)); - - assertThat(mockGitHub.getRequestCount(), equalTo(1)); - - // Give this a moment - Thread.sleep(1500); - - // Always requests new info - rateLimit = gitHub.getRateLimit(); - assertThat(mockGitHub.getRequestCount(), equalTo(2)); - - // Because remaining and reset date are unchanged in core, the header should be unchanged as well - // But the overall instance has changed because of filling in of unknown data. - assertThat(gitHub.lastRateLimit(), not(sameInstance(headerRateLimit))); - // Identical Records should be preserved even when GHRateLimit is merged - assertThat(gitHub.lastRateLimit().getCore(), sameInstance(headerRateLimit.getCore())); - assertThat(gitHub.lastRateLimit().getSearch(), not(sameInstance(headerRateLimit.getSearch()))); - headerRateLimit = gitHub.lastRateLimit(); - - // rate limit request is free, remaining is unchanged - verifyRateLimitValues(previousLimit, previousLimit.getRemaining()); - previousLimit = rateLimit; - - // Give this a moment - Thread.sleep(1500); - - // Always requests new info - rateLimit = gitHub.getRateLimit(); - assertThat(mockGitHub.getRequestCount(), equalTo(3)); - - // Because remaining and reset date are unchanged, the header should be unchanged as well - assertThat(gitHub.lastRateLimit(), sameInstance(headerRateLimit)); - - // rate limit request is free, remaining is unchanged - verifyRateLimitValues(previousLimit, previousLimit.getRemaining()); - previousLimit = rateLimit; - - gitHub.getOrganization(GITHUB_API_TEST_ORG); - assertThat(mockGitHub.getRequestCount(), equalTo(4)); - - // Because remaining has changed the header should be different - assertThat(gitHub.lastRateLimit(), not(sameInstance(headerRateLimit))); - assertThat(gitHub.lastRateLimit(), not(equalTo(headerRateLimit))); - rateLimit = gitHub.lastRateLimit(); - - // Org costs limit to query - verifyRateLimitValues(previousLimit, previousLimit.getRemaining() - 1); - - previousLimit = rateLimit; - headerRateLimit = rateLimit; - - // ratelimit() should prefer headerRateLimit when it is most recent and not expired - assertThat(gitHub.rateLimit(), sameInstance(headerRateLimit)); - - assertThat(mockGitHub.getRequestCount(), equalTo(4)); - - // AT THIS POINT WE SIMULATE A RATE LIMIT RESET - - // Give this a moment - Thread.sleep(2000); - - // Always requests new info - rateLimit = gitHub.getRateLimit(); - assertThat(mockGitHub.getRequestCount(), equalTo(5)); - - // rate limit request is free, remaining is unchanged date is later - verifyRateLimitValues(previousLimit, previousLimit.getRemaining(), true); - previousLimit = rateLimit; - - // When getRateLimit() succeeds, cached rate limit updates as usual as well (if needed) - assertThat(gitHub.rateLimit(), sameInstance(rateLimit)); - - // Verify different record instances can be compared - assertThat(gitHub.rateLimit().getCore(), equalTo(rateLimit.getCore())); - - // Verify different instances can be compared - // TODO: This is not work currently because the header rate limit has unknowns for records other than core. - // assertThat(gitHub.rateLimit(), equalTo(rateLimit)); - - assertThat(gitHub.rateLimit(), not(sameInstance(headerRateLimit))); - assertThat(gitHub.rateLimit(), sameInstance(gitHub.lastRateLimit())); - headerRateLimit = gitHub.lastRateLimit(); - - assertThat(mockGitHub.getRequestCount(), equalTo(5)); - - // Verify the requesting a search url updates the search rate limit - assertThat(gitHub.lastRateLimit().getSearch().getRemaining(), equalTo(30)); - - HashMap searchResult = (HashMap) gitHub.createRequest() - .rateLimit(RateLimitTarget.SEARCH) - .setRawUrlPath(mockGitHub.apiServer().baseUrl() - + "/search/repositories?q=tetris+language%3Aassembly&sort=stars&order=desc") - .fetch(HashMap.class); - - assertThat(searchResult.get("total_count"), equalTo(1918)); - - assertThat(mockGitHub.getRequestCount(), equalTo(6)); - - assertThat(gitHub.lastRateLimit(), not(sameInstance(headerRateLimit))); - assertThat(gitHub.lastRateLimit().getCore(), sameInstance(headerRateLimit.getCore())); - assertThat(gitHub.lastRateLimit().getSearch(), not(sameInstance(headerRateLimit.getSearch()))); - assertThat(gitHub.lastRateLimit().getSearch().getRemaining(), equalTo(29)); - - PagedSearchIterable searchResult2 = gitHub.searchRepositories() - .q("tetris") - .language("assembly") - .sort(GHRepositorySearchBuilder.Sort.STARS) - .order(GHDirection.DESC) - .list(); - - assertThat(searchResult2.getTotalCount(), equalTo(1918)); - - assertThat(mockGitHub.getRequestCount(), equalTo(7)); - - assertThat(gitHub.lastRateLimit(), not(sameInstance(headerRateLimit))); - assertThat(gitHub.lastRateLimit().getCore(), sameInstance(headerRateLimit.getCore())); - assertThat(gitHub.lastRateLimit().getSearch(), not(sameInstance(headerRateLimit.getSearch()))); - assertThat(gitHub.lastRateLimit().getSearch().getRemaining(), equalTo(28)); - } - - private void verifyRateLimitValues(GHRateLimit previousLimit, int remaining) { - verifyRateLimitValues(previousLimit, remaining, false); - } - - private void verifyRateLimitValues(GHRateLimit previousLimit, int remaining, boolean changedResetDate) { - // Basic checks of values - assertThat(rateLimit, notNullValue()); - assertThat(rateLimit.getLimit(), equalTo(previousLimit.getLimit())); - assertThat(rateLimit.getRemaining(), equalTo(remaining)); - - // Check that the reset date of the current limit is not older than the previous one - long diffMillis = rateLimit.getResetDate().getTime() - previousLimit.getResetDate().getTime(); - - assertThat(diffMillis, greaterThanOrEqualTo(0L)); - if (changedResetDate) { - assertThat(diffMillis, greaterThan(1000L)); - } else { - assertThat(diffMillis, lessThanOrEqualTo(1000L)); - } - - // Additional checks for record values - assertThat(rateLimit.getCore().getLimit(), equalTo(rateLimit.getLimit())); - assertThat(rateLimit.getCore().getRemaining(), equalTo(rateLimit.getRemaining())); - assertThat(rateLimit.getCore().getResetEpochSeconds(), equalTo(rateLimit.getResetEpochSeconds())); - assertThat(rateLimit.getCore().getResetDate(), equalTo(rateLimit.getResetDate())); - } - /** * Test git hub enterprise does not have rate limit. * @@ -262,7 +71,7 @@ public void testGitHubEnterpriseDoesNotHaveRateLimit() throws Exception { assertThat(mockGitHub.getRequestCount(), equalTo(0)); GHRateLimit rateLimit = null; - Date lastReset = new Date(System.currentTimeMillis() / 1000L); + Instant lastReset = Instant.ofEpochMilli(System.currentTimeMillis() / 1000L); // Give this a moment Thread.sleep(1500); @@ -279,8 +88,8 @@ public void testGitHubEnterpriseDoesNotHaveRateLimit() throws Exception { assertThat(rateLimit.getCore(), instanceOf(GHRateLimit.UnknownLimitRecord.class)); assertThat(rateLimit.getLimit(), equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit)); assertThat(rateLimit.getRemaining(), equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining)); - assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(1)); - lastReset = rateLimit.getResetDate(); + assertThat(rateLimit.getResetDate().compareTo(Date.from(lastReset)), equalTo(1)); + lastReset = rateLimit.getCore().getResetInstant(); assertThat(mockGitHub.getRequestCount(), equalTo(1)); @@ -306,8 +115,8 @@ public void testGitHubEnterpriseDoesNotHaveRateLimit() throws Exception { assertThat(rateLimit.getLimit(), equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit)); assertThat(rateLimit.getRemaining(), equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining)); // Same unknown instance is reused for a while - assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(0)); - lastReset = rateLimit.getResetDate(); + assertThat(rateLimit.getResetDate().compareTo(Date.from(lastReset)), equalTo(0)); + lastReset = rateLimit.getCore().getResetInstant(); assertThat(mockGitHub.getRequestCount(), equalTo(3)); @@ -326,7 +135,7 @@ public void testGitHubEnterpriseDoesNotHaveRateLimit() throws Exception { assertThat(rateLimit.getLimit(), equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit)); assertThat(rateLimit.getRemaining(), equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining)); // When not expired, unknowns do not replace each other so last reset remains unchanged - assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(0)); + assertThat(rateLimit.getResetDate().compareTo(Date.from(lastReset)), equalTo(0)); // Give this a moment Thread.sleep(1500); @@ -348,8 +157,8 @@ public void testGitHubEnterpriseDoesNotHaveRateLimit() throws Exception { assertThat(rateLimit, notNullValue()); assertThat(rateLimit.getLimit(), equalTo(5000)); assertThat(rateLimit.getRemaining(), equalTo(4978)); - assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(1)); - lastReset = rateLimit.getResetDate(); + assertThat(rateLimit.getResetDate().compareTo(Date.from(lastReset)), equalTo(1)); + lastReset = rateLimit.getCore().getResetInstant(); // When getting only header updates, the unknowns are also expired assertThat(rateLimit.getSearch().isExpired(), is(true)); @@ -383,7 +192,7 @@ public void testGitHubEnterpriseDoesNotHaveRateLimit() throws Exception { // 11 requests since previous api call // This verifies that header rate limit info is recorded even for /rate_limit endpoint and 404 response assertThat(rateLimit.getRemaining(), equalTo(4967)); - assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(0)); + assertThat(rateLimit.getResetDate().compareTo(Date.from(lastReset)), equalTo(0)); // getRateLimit() uses headerRateLimit if /rate_limit returns a 404 // and headerRateLimit is available and not expired @@ -434,37 +243,162 @@ public void testGitHubEnterpriseDoesNotHaveRateLimit() throws Exception { } /** - * Test git hub rate limit with bad data. + * Test git hub rate limit. * * @throws Exception * the exception */ @Test - public void testGitHubRateLimitWithBadData() throws Exception { + public void testGitHubRateLimit() throws Exception { + // Customized response that templates the date to keep things working snapshotNotAllowed(); + GHRateLimit.UnknownLimitRecord.reset(); + + assertThat(mockGitHub.getRequestCount(), equalTo(0)); + + // 4897 is just the what the limit was when the snapshot was taken + previousLimit = GHRateLimit.fromRecord( + new GHRateLimit.Record(5000, + 4897, + (templating.testStartDate.getTime() + Duration.ofHours(1).toMillis()) / 1000L), + RateLimitTarget.CORE); + + // ------------------------------------------------------------- + // /user gets response with rate limit information gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()).build(); gitHub.getMyself(); - try { - gitHub.getRateLimit(); - fail("Invalid rate limit missing some records should throw"); - } catch (Exception e) { - assertThat(e, instanceOf(HttpException.class)); - assertThat(e.getCause(), instanceOf(ValueInstantiationException.class)); - assertThat(e.getCause().getMessage(), - containsString( - "Cannot construct instance of `org.kohsuke.github.GHRateLimit`, problem: `java.lang.NullPointerException`")); - } - try { - gitHub.getRateLimit(); - fail("Invalid rate limit record missing a value should throw"); - } catch (Exception e) { - assertThat(e, instanceOf(HttpException.class)); - assertThat(e.getCause(), instanceOf(MismatchedInputException.class)); - assertThat(e.getCause().getMessage(), - containsString("Missing required creator property 'reset' (index 2)")); - } + assertThat(mockGitHub.getRequestCount(), equalTo(1)); + + // Since we already had rate limit info these don't request again + rateLimit = gitHub.lastRateLimit(); + verifyRateLimitValues(previousLimit, previousLimit.getRemaining()); + previousLimit = rateLimit; + + GHRateLimit headerRateLimit = rateLimit; + + // Give this a moment + Thread.sleep(1500); + + // ratelimit() uses cached rate limit if available and not expired + assertThat(gitHub.rateLimit(), sameInstance(headerRateLimit)); + + assertThat(mockGitHub.getRequestCount(), equalTo(1)); + + // Give this a moment + Thread.sleep(1500); + + // Always requests new info + rateLimit = gitHub.getRateLimit(); + assertThat(mockGitHub.getRequestCount(), equalTo(2)); + + // Because remaining and reset date are unchanged in core, the header should be unchanged as well + // But the overall instance has changed because of filling in of unknown data. + assertThat(gitHub.lastRateLimit(), not(sameInstance(headerRateLimit))); + // Identical Records should be preserved even when GHRateLimit is merged + assertThat(gitHub.lastRateLimit().getCore(), sameInstance(headerRateLimit.getCore())); + assertThat(gitHub.lastRateLimit().getSearch(), not(sameInstance(headerRateLimit.getSearch()))); + headerRateLimit = gitHub.lastRateLimit(); + + // rate limit request is free, remaining is unchanged + verifyRateLimitValues(previousLimit, previousLimit.getRemaining()); + previousLimit = rateLimit; + // Give this a moment + Thread.sleep(1500); + + // Always requests new info + rateLimit = gitHub.getRateLimit(); + assertThat(mockGitHub.getRequestCount(), equalTo(3)); + + // Because remaining and reset date are unchanged, the header should be unchanged as well + assertThat(gitHub.lastRateLimit(), sameInstance(headerRateLimit)); + + // rate limit request is free, remaining is unchanged + verifyRateLimitValues(previousLimit, previousLimit.getRemaining()); + previousLimit = rateLimit; + + gitHub.getOrganization(GITHUB_API_TEST_ORG); + assertThat(mockGitHub.getRequestCount(), equalTo(4)); + + // Because remaining has changed the header should be different + assertThat(gitHub.lastRateLimit(), not(sameInstance(headerRateLimit))); + assertThat(gitHub.lastRateLimit(), not(equalTo(headerRateLimit))); + rateLimit = gitHub.lastRateLimit(); + + // Org costs limit to query + verifyRateLimitValues(previousLimit, previousLimit.getRemaining() - 1); + + previousLimit = rateLimit; + headerRateLimit = rateLimit; + + // ratelimit() should prefer headerRateLimit when it is most recent and not expired + assertThat(gitHub.rateLimit(), sameInstance(headerRateLimit)); + + assertThat(mockGitHub.getRequestCount(), equalTo(4)); + + // AT THIS POINT WE SIMULATE A RATE LIMIT RESET + + // Give this a moment + Thread.sleep(2000); + + // Always requests new info + rateLimit = gitHub.getRateLimit(); + assertThat(mockGitHub.getRequestCount(), equalTo(5)); + + // rate limit request is free, remaining is unchanged date is later + verifyRateLimitValues(previousLimit, previousLimit.getRemaining(), true); + previousLimit = rateLimit; + + // When getRateLimit() succeeds, cached rate limit updates as usual as well (if needed) + assertThat(gitHub.rateLimit(), sameInstance(rateLimit)); + + // Verify different record instances can be compared + assertThat(gitHub.rateLimit().getCore(), equalTo(rateLimit.getCore())); + + // Verify different instances can be compared + // TODO: This is not work currently because the header rate limit has unknowns for records other than core. + // assertThat(gitHub.rateLimit(), equalTo(rateLimit)); + + assertThat(gitHub.rateLimit(), not(sameInstance(headerRateLimit))); + assertThat(gitHub.rateLimit(), sameInstance(gitHub.lastRateLimit())); + headerRateLimit = gitHub.lastRateLimit(); + + assertThat(mockGitHub.getRequestCount(), equalTo(5)); + + // Verify the requesting a search url updates the search rate limit + assertThat(gitHub.lastRateLimit().getSearch().getRemaining(), equalTo(30)); + + HashMap searchResult = gitHub.createRequest() + .rateLimit(RateLimitTarget.SEARCH) + .setRawUrlPath(mockGitHub.apiServer().baseUrl() + + "/search/repositories?q=tetris+language%3Aassembly&sort=stars&order=desc") + .fetch(HashMap.class); + + assertThat(searchResult.get("total_count"), equalTo(1918)); + + assertThat(mockGitHub.getRequestCount(), equalTo(6)); + + assertThat(gitHub.lastRateLimit(), not(sameInstance(headerRateLimit))); + assertThat(gitHub.lastRateLimit().getCore(), sameInstance(headerRateLimit.getCore())); + assertThat(gitHub.lastRateLimit().getSearch(), not(sameInstance(headerRateLimit.getSearch()))); + assertThat(gitHub.lastRateLimit().getSearch().getRemaining(), equalTo(29)); + + PagedSearchIterable searchResult2 = gitHub.searchRepositories() + .q("tetris") + .language("assembly") + .sort(GHRepositorySearchBuilder.Sort.STARS) + .order(GHDirection.DESC) + .list(); + + assertThat(searchResult2.getTotalCount(), equalTo(1918)); + + assertThat(mockGitHub.getRequestCount(), equalTo(7)); + + assertThat(gitHub.lastRateLimit(), not(sameInstance(headerRateLimit))); + assertThat(gitHub.lastRateLimit().getCore(), sameInstance(headerRateLimit.getCore())); + assertThat(gitHub.lastRateLimit().getSearch(), not(sameInstance(headerRateLimit.getSearch()))); + assertThat(gitHub.lastRateLimit().getSearch().getRemaining(), equalTo(28)); } /** @@ -490,6 +424,40 @@ public void testGitHubRateLimitExpirationServerFiveMinutesBehind() throws Except executeExpirationTest(); } + /** + * Test git hub rate limit with bad data. + * + * @throws Exception + * the exception + */ + @Test + public void testGitHubRateLimitWithBadData() throws Exception { + snapshotNotAllowed(); + gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()).build(); + gitHub.getMyself(); + try { + gitHub.getRateLimit(); + fail("Invalid rate limit missing some records should throw"); + } catch (Exception e) { + assertThat(e, instanceOf(HttpException.class)); + assertThat(e.getCause(), instanceOf(ValueInstantiationException.class)); + assertThat(e.getCause().getMessage(), + containsString( + "Cannot construct instance of `org.kohsuke.github.GHRateLimit`, problem: `java.lang.NullPointerException`")); + } + + try { + gitHub.getRateLimit(); + fail("Invalid rate limit record missing a value should throw"); + } catch (Exception e) { + assertThat(e, instanceOf(HttpException.class)); + assertThat(e.getCause(), instanceOf(MismatchedInputException.class)); + assertThat(e.getCause().getMessage(), + containsString("Missing required creator property 'reset' (index 2)")); + } + + } + private void executeExpirationTest() throws Exception { // Customized response that templates the date to keep things working snapshotNotAllowed(); @@ -587,8 +555,42 @@ private void executeExpirationTest() throws Exception { assertThat(mockGitHub.getRequestCount(), equalTo(3)); } - private static GHRepository getRepository(GitHub gitHub) throws IOException { - return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); + private void verifyRateLimitValues(GHRateLimit previousLimit, int remaining) { + verifyRateLimitValues(previousLimit, remaining, false); + } + + private void verifyRateLimitValues(GHRateLimit previousLimit, int remaining, boolean changedResetDate) { + // Basic checks of values + assertThat(rateLimit, notNullValue()); + assertThat(rateLimit.getLimit(), equalTo(previousLimit.getLimit())); + assertThat(rateLimit.getRemaining(), equalTo(remaining)); + + // Check that the reset date of the current limit is not older than the previous one + long diffMillis = rateLimit.getCore().getResetInstant().toEpochMilli() + - previousLimit.getCore().getResetInstant().toEpochMilli(); + + assertThat(diffMillis, greaterThanOrEqualTo(0L)); + if (changedResetDate) { + assertThat(diffMillis, greaterThan(1000L)); + } else { + assertThat(diffMillis, lessThanOrEqualTo(1000L)); + } + + // Additional checks for record values + assertThat(rateLimit.getCore().getLimit(), equalTo(rateLimit.getLimit())); + assertThat(rateLimit.getCore().getRemaining(), equalTo(rateLimit.getRemaining())); + assertThat(rateLimit.getCore().getResetEpochSeconds(), equalTo(rateLimit.getResetEpochSeconds())); + assertThat(rateLimit.getCore().getResetDate(), equalTo(rateLimit.getResetDate())); + } + + /** + * Gets the wire mock options. + * + * @return the wire mock options + */ + @Override + protected WireMockConfiguration getWireMockOptions() { + return super.getWireMockOptions().extensions(templating.newResponseTransformer()); } } diff --git a/src/test/java/org/kohsuke/github/GHReleaseTest.java b/src/test/java/org/kohsuke/github/GHReleaseTest.java index 7d907c4335..1a4b5a79d4 100644 --- a/src/test/java/org/kohsuke/github/GHReleaseTest.java +++ b/src/test/java/org/kohsuke/github/GHReleaseTest.java @@ -3,6 +3,8 @@ import org.junit.Test; import org.kohsuke.github.GHReleaseBuilder.MakeLatest; +import java.util.Date; + import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThrows; @@ -19,49 +21,28 @@ public GHReleaseTest() { } /** - * Test create simple release. + * Test create double release fails. * * @throws Exception * the exception */ @Test - public void testCreateSimpleRelease() throws Exception { + public void testCreateDoubleReleaseFails() throws Exception { GHRepository repo = gitHub.getRepository("hub4j-test-org/testCreateRelease"); String tagName = mockGitHub.getMethodName(); - GHRelease release = repo.createRelease(tagName).categoryName("announcements").prerelease(false).create(); - try { - GHRelease releaseCheck = repo.getRelease(release.getId()); - assertThat(releaseCheck, notNullValue()); - assertThat(releaseCheck.getTagName(), is(tagName)); - assertThat(releaseCheck.isPrerelease(), is(false)); - assertThat(releaseCheck.getDiscussionUrl(), notNullValue()); - } finally { - release.delete(); - assertThat(repo.getRelease(release.getId()), nullValue()); - } - } - - /** - * Test create simple release without discussion. - * - * @throws Exception - * the exception - */ - @Test - public void testCreateSimpleReleaseWithoutDiscussion() throws Exception { - GHRepository repo = gitHub.getRepository("hub4j-test-org/testCreateRelease"); - - String tagName = mockGitHub.getMethodName(); GHRelease release = repo.createRelease(tagName).create(); try { GHRelease releaseCheck = repo.getRelease(release.getId()); - assertThat(releaseCheck, notNullValue()); - assertThat(releaseCheck.getTagName(), is(tagName)); - assertThat(releaseCheck.getDiscussionUrl(), nullValue()); + + HttpException httpException = assertThrows(HttpException.class, () -> { + repo.createRelease(tagName).create(); + }); + + assertThat(httpException.getResponseCode(), is(422)); } finally { release.delete(); assertThat(repo.getRelease(release.getId()), nullValue()); @@ -69,28 +50,24 @@ public void testCreateSimpleReleaseWithoutDiscussion() throws Exception { } /** - * Test create double release fails. + * Tests creation of the release with `generate_release_notes` parameter on. * * @throws Exception - * the exception + * if any failure has happened. */ @Test - public void testCreateDoubleReleaseFails() throws Exception { + public void testCreateReleaseWithNotes() throws Exception { GHRepository repo = gitHub.getRepository("hub4j-test-org/testCreateRelease"); String tagName = mockGitHub.getMethodName(); - - GHRelease release = repo.createRelease(tagName).create(); - + GHRelease release = new GHReleaseBuilder(repo, tagName).generateReleaseNotes(true).create(); try { GHRelease releaseCheck = repo.getRelease(release.getId()); - assertThat(releaseCheck, notNullValue()); - - HttpException httpException = assertThrows(HttpException.class, () -> { - repo.createRelease(tagName).create(); - }); - assertThat(httpException.getResponseCode(), is(422)); + assertThat(releaseCheck, notNullValue()); + assertThat(releaseCheck.getTagName(), is(tagName)); + assertThat(releaseCheck.isPrerelease(), is(false)); + assertThat(releaseCheck.getDiscussionUrl(), notNullValue()); } finally { release.delete(); assertThat(repo.getRelease(release.getId()), nullValue()); @@ -120,31 +97,56 @@ public void testCreateReleaseWithUnknownCategoryFails() throws Exception { } /** - * Test update release. + * Test create simple release. * * @throws Exception * the exception */ @Test - public void testUpdateRelease() throws Exception { + public void testCreateSimpleRelease() throws Exception { GHRepository repo = gitHub.getRepository("hub4j-test-org/testCreateRelease"); String tagName = mockGitHub.getMethodName(); - GHRelease release = repo.createRelease(tagName).prerelease(true).create(); + GHRelease release = repo.createRelease(tagName).categoryName("announcements").prerelease(false).create(); try { GHRelease releaseCheck = repo.getRelease(release.getId()); - GHRelease updateCheck = releaseCheck.update().categoryName("announcements").prerelease(false).update(); assertThat(releaseCheck, notNullValue()); assertThat(releaseCheck.getTagName(), is(tagName)); - assertThat(releaseCheck.isPrerelease(), is(true)); - assertThat(releaseCheck.getDiscussionUrl(), nullValue()); + assertThat(releaseCheck.isPrerelease(), is(false)); + assertThat(releaseCheck.isDraft(), is(false)); + assertThat(releaseCheck.getAssetsUrl(), endsWith("/assets")); + assertThat(releaseCheck.getDiscussionUrl(), notNullValue()); + assertThat(releaseCheck.getCreatedAt(), equalTo(GitHubClient.parseInstant("2021-06-02T21:59:14Z"))); + assertThat(releaseCheck.getPublished_at(), + equalTo(Date.from(GitHubClient.parseInstant("2021-06-11T06:56:52Z")))); + assertThat(releaseCheck.getPublishedAt(), equalTo(GitHubClient.parseInstant("2021-06-11T06:56:52Z"))); - assertThat(updateCheck, notNullValue()); - assertThat(updateCheck.getTagName(), is(tagName)); - assertThat(updateCheck.isPrerelease(), is(false)); - assertThat(updateCheck.getDiscussionUrl(), notNullValue()); + } finally { + release.delete(); + assertThat(repo.getRelease(release.getId()), nullValue()); + } + } + + /** + * Test create simple release without discussion. + * + * @throws Exception + * the exception + */ + @Test + public void testCreateSimpleReleaseWithoutDiscussion() throws Exception { + GHRepository repo = gitHub.getRepository("hub4j-test-org/testCreateRelease"); + + String tagName = mockGitHub.getMethodName(); + GHRelease release = repo.createRelease(tagName).create(); + try { + GHRelease releaseCheck = repo.getRelease(release.getId()); + + assertThat(releaseCheck, notNullValue()); + assertThat(releaseCheck.getTagName(), is(tagName)); + assertThat(releaseCheck.getDiscussionUrl(), nullValue()); } finally { release.delete(); assertThat(repo.getRelease(release.getId()), nullValue()); @@ -206,24 +208,31 @@ public void testMakeLatestRelease() throws Exception { } /** - * Tests creation of the release with `generate_release_notes` parameter on. + * Test update release. * * @throws Exception - * if any failure has happened. + * the exception */ @Test - public void testCreateReleaseWithNotes() throws Exception { + public void testUpdateRelease() throws Exception { GHRepository repo = gitHub.getRepository("hub4j-test-org/testCreateRelease"); String tagName = mockGitHub.getMethodName(); - GHRelease release = new GHReleaseBuilder(repo, tagName).generateReleaseNotes(true).create(); + GHRelease release = repo.createRelease(tagName).prerelease(true).create(); try { GHRelease releaseCheck = repo.getRelease(release.getId()); + GHRelease updateCheck = releaseCheck.update().categoryName("announcements").prerelease(false).update(); assertThat(releaseCheck, notNullValue()); assertThat(releaseCheck.getTagName(), is(tagName)); - assertThat(releaseCheck.isPrerelease(), is(false)); - assertThat(releaseCheck.getDiscussionUrl(), notNullValue()); + assertThat(releaseCheck.isPrerelease(), is(true)); + assertThat(releaseCheck.getDiscussionUrl(), nullValue()); + + assertThat(updateCheck, notNullValue()); + assertThat(updateCheck.getTagName(), is(tagName)); + assertThat(updateCheck.isPrerelease(), is(false)); + assertThat(updateCheck.getDiscussionUrl(), notNullValue()); + } finally { release.delete(); assertThat(repo.getRelease(release.getId()), nullValue()); diff --git a/src/test/java/org/kohsuke/github/GHRepositoryForkBuilderTest.java b/src/test/java/org/kohsuke/github/GHRepositoryForkBuilderTest.java index c045ef811e..d07d1eb124 100644 --- a/src/test/java/org/kohsuke/github/GHRepositoryForkBuilderTest.java +++ b/src/test/java/org/kohsuke/github/GHRepositoryForkBuilderTest.java @@ -18,10 +18,49 @@ * The Class GHRepositoryForkBuilderTest. */ public class GHRepositoryForkBuilderTest extends AbstractGitHubWireMockTest { - private GHRepository repo; + /** + * The type Test fork builder. + */ + class TestForkBuilder extends GHRepositoryForkBuilder { + /** + * The Last sleep millis. + */ + int lastSleepMillis = 0; + /** + * The Sleep count. + */ + int sleepCount = 0; + + /** + * Instantiates a new Test fork builder. + * + * @param repo + * the repo + */ + TestForkBuilder(GHRepository repo) { + super(repo); + } + + @Override + void sleep(int millis) throws IOException { + sleepCount++; + lastSleepMillis = millis; + try { + if (mockGitHub.isUseProxy()) { + Thread.sleep(millis); + } else { + Thread.sleep(1); + } + } catch (InterruptedException e) { + throw (IOException) new InterruptedIOException().initCause(e); + } + } + } private static final String TARGET_ORG = "nts-api-test-org"; private int originalInterval; + private GHRepository repo; + /** * Instantiates a new Gh repository fork builder test. */ @@ -63,73 +102,6 @@ public void tearDown() { GHRepositoryForkBuilder.FORK_RETRY_INTERVAL = originalInterval; } - /** - * The type Test fork builder. - */ - class TestForkBuilder extends GHRepositoryForkBuilder { - /** - * The Sleep count. - */ - int sleepCount = 0; - /** - * The Last sleep millis. - */ - int lastSleepMillis = 0; - - /** - * Instantiates a new Test fork builder. - * - * @param repo - * the repo - */ - TestForkBuilder(GHRepository repo) { - super(repo); - } - - @Override - void sleep(int millis) throws IOException { - sleepCount++; - lastSleepMillis = millis; - try { - if (mockGitHub.isUseProxy()) { - Thread.sleep(millis); - } else { - Thread.sleep(1); - } - } catch (InterruptedException e) { - throw (IOException) new InterruptedIOException().initCause(e); - } - } - } - - private TestForkBuilder createBuilder() { - return new TestForkBuilder(repo); - } - - private void verifyBasicForkProperties(GHRepository original, GHRepository forked, String expectedName) - throws IOException { - GHRepository updatedFork = forked; - - await().atMost(Duration.ofSeconds(30)) - .pollInterval(Duration.ofSeconds(3)) - .until(() -> gitHub.getRepository(forked.getFullName()).isFork()); - - assertThat(updatedFork, notNullValue()); - assertThat(updatedFork.getName(), equalTo(expectedName)); - assertThat(updatedFork.isFork(), is(true)); - assertThat(updatedFork.getParent().getFullName(), equalTo(original.getFullName())); - } - - private void verifyBranches(GHRepository forked, boolean defaultBranchOnly) throws IOException { - Map branches = forked.getBranches(); - if (defaultBranchOnly) { - assertThat(branches.size(), equalTo(1)); - } else { - assertThat(branches.size(), greaterThan(1)); - } - assertThat(branches.containsKey(forked.getDefaultBranch()), is(true)); - } - /** * Test fork. * @@ -148,19 +120,19 @@ public void testFork() throws Exception { } /** - * Test fork to org. + * Test fork changed name. * * @throws Exception * the exception */ @Test - public void testForkToOrg() throws Exception { - GHOrganization targetOrg = gitHub.getOrganization(TARGET_ORG); - // equivalent to the deprecated forkTo() method + public void testForkChangedName() throws Exception { + String newRepoName = "test-fork-with-new-name"; TestForkBuilder builder = createBuilder(); - GHRepository forkedRepo = builder.organization(targetOrg).create(); + GHRepository forkedRepo = builder.name(newRepoName).create(); - verifyBasicForkProperties(repo, forkedRepo, repo.getName()); + assertThat(forkedRepo.getName(), equalTo(newRepoName)); + verifyBasicForkProperties(repo, forkedRepo, newRepoName); verifyBranches(forkedRepo, false); forkedRepo.delete(); @@ -184,24 +156,44 @@ public void testForkDefaultBranchOnly() throws Exception { } /** - * Test fork changed name. + * Test fork to org. * * @throws Exception * the exception */ @Test - public void testForkChangedName() throws Exception { - String newRepoName = "test-fork-with-new-name"; + public void testForkToOrg() throws Exception { + GHOrganization targetOrg = gitHub.getOrganization(TARGET_ORG); + // equivalent to the deprecated forkTo() method TestForkBuilder builder = createBuilder(); - GHRepository forkedRepo = builder.name(newRepoName).create(); + GHRepository forkedRepo = builder.organization(targetOrg).create(); - assertThat(forkedRepo.getName(), equalTo(newRepoName)); - verifyBasicForkProperties(repo, forkedRepo, newRepoName); + verifyBasicForkProperties(repo, forkedRepo, repo.getName()); verifyBranches(forkedRepo, false); forkedRepo.delete(); } + /** + * Test sleep. + * + * @throws Exception + * the exception + */ + @Test + public void testSleep() throws Exception { + GHRepositoryForkBuilder builder = new GHRepositoryForkBuilder(repo); + Thread.currentThread().interrupt(); + + try { + builder.sleep(100); + fail("Expected InterruptedIOException"); + } catch (InterruptedIOException e) { + assertThat(e, instanceOf(InterruptedIOException.class)); + assertThat(e.getCause(), instanceOf(InterruptedException.class)); + } + } + /** * Test timeout message and sleep count. */ @@ -254,24 +246,32 @@ public void testTimeoutOrgMessage() throws Exception { } } - /** - * Test sleep. - * - * @throws Exception - * the exception - */ - @Test - public void testSleep() throws Exception { - GHRepositoryForkBuilder builder = new GHRepositoryForkBuilder(repo); - Thread.currentThread().interrupt(); + private TestForkBuilder createBuilder() { + return new TestForkBuilder(repo); + } - try { - builder.sleep(100); - fail("Expected InterruptedIOException"); - } catch (InterruptedIOException e) { - assertThat(e, instanceOf(InterruptedIOException.class)); - assertThat(e.getCause(), instanceOf(InterruptedException.class)); + private void verifyBasicForkProperties(GHRepository original, GHRepository forked, String expectedName) + throws IOException { + GHRepository updatedFork = forked; + + await().atMost(Duration.ofSeconds(30)) + .pollInterval(Duration.ofSeconds(3)) + .until(() -> gitHub.getRepository(forked.getFullName()).isFork()); + + assertThat(updatedFork, notNullValue()); + assertThat(updatedFork.getName(), equalTo(expectedName)); + assertThat(updatedFork.isFork(), is(true)); + assertThat(updatedFork.getParent().getFullName(), equalTo(original.getFullName())); + } + + private void verifyBranches(GHRepository forked, boolean defaultBranchOnly) throws IOException { + Map branches = forked.getBranches(); + if (defaultBranchOnly) { + assertThat(branches.size(), equalTo(1)); + } else { + assertThat(branches.size(), greaterThan(1)); } + assertThat(branches.containsKey(forked.getDefaultBranch()), is(true)); } } diff --git a/src/test/java/org/kohsuke/github/GHRepositoryRuleTest.java b/src/test/java/org/kohsuke/github/GHRepositoryRuleTest.java index 084e41e159..56b5c9f68d 100644 --- a/src/test/java/org/kohsuke/github/GHRepositoryRuleTest.java +++ b/src/test/java/org/kohsuke/github/GHRepositoryRuleTest.java @@ -16,11 +16,12 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThrows; /** * Test class for GHRepositoryRule. */ -public class GHRepositoryRuleTest { +public class GHRepositoryRuleTest extends AbstractGitHubWireMockTest { /** * Create default GHRepositoryRuleTest instance @@ -29,59 +30,65 @@ public GHRepositoryRuleTest() { } /** - * Test to cover the constructor of the Parameters class. + * Tests to cover AlertsThreshold enum. */ @Test - public void testParameters() { - assertThat(Parameters.REQUIRED_DEPLOYMENT_ENVIRONMENTS.getType(), is(notNullValue())); - assertThat(Parameters.REQUIRED_STATUS_CHECKS.getType(), is(notNullValue())); - assertThat(Parameters.OPERATOR.getType(), is(notNullValue())); - assertThat(Parameters.WORKFLOWS.getType(), is(notNullValue())); - assertThat(Parameters.CODE_SCANNING_TOOLS.getType(), is(notNullValue())); - assertThat(new StringParameter("any").getType(), is(notNullValue())); + public void testAlertsThreshold() { + assertThat(AlertsThreshold.ERRORS, is(notNullValue())); } /** - * Tests to cover StatusCheckConfiguration class. + * Tests to cover CodeScanningTool class. */ @Test - public void testStatusCheckConfiguration() { - StatusCheckConfiguration statusCheckConfiguration = new StatusCheckConfiguration(); - statusCheckConfiguration = new StatusCheckConfiguration(); - assertThat(statusCheckConfiguration.getContext(), is(nullValue())); - assertThat(statusCheckConfiguration.getIntegrationId(), is(nullValue())); + public void testCodeScanningTool() { + CodeScanningTool codeScanningTool = new CodeScanningTool(); + codeScanningTool = new CodeScanningTool(); + assertThat(codeScanningTool.getAlertsThreshold(), is(nullValue())); + assertThat(codeScanningTool.getSecurityAlertsThreshold(), is(nullValue())); + assertThat(codeScanningTool.getTool(), is(nullValue())); } /** - * Tests to cover WorkflowFileReference class. + * Tests to cover Operator enum. */ @Test - public void testWorkflowFileReference() { - WorkflowFileReference workflowFileReference = new WorkflowFileReference(); - assertThat(workflowFileReference.getPath(), is(nullValue())); - assertThat(workflowFileReference.getRef(), is(nullValue())); - assertThat(workflowFileReference.getRepositoryId(), is(equalTo(0L))); - assertThat(workflowFileReference.getSha(), is(nullValue())); + public void testOperator() { + assertThat(Operator.ENDS_WITH, is(notNullValue())); } /** - * Tests to cover CodeScanningTool class. + * Tests that apply on null JsonNode returns null. + * + * @throws Exception + * if something goes wrong. */ @Test - public void testCodeScanningTool() { - CodeScanningTool codeScanningTool = new CodeScanningTool(); - codeScanningTool = new CodeScanningTool(); - assertThat(codeScanningTool.getAlertsThreshold(), is(nullValue())); - assertThat(codeScanningTool.getSecurityAlertsThreshold(), is(nullValue())); - assertThat(codeScanningTool.getTool(), is(nullValue())); + public void testParameterReturnsNullOnNullArg() throws Exception { + Parameter parameter = new StringParameter("any"); + assertThat(parameter.apply(null, null), is(nullValue())); } /** - * Tests to cover AlertsThreshold enum. + * Test to cover the constructor of the Parameters class. + * + * @throws Exception + * if something goes wrong. */ @Test - public void testAlertsThreshold() { - assertThat(AlertsThreshold.ERRORS, is(notNullValue())); + public void testParameters() throws Exception { + assertThat(Parameters.REQUIRED_DEPLOYMENT_ENVIRONMENTS, is(notNullValue())); + assertThat(Parameters.REQUIRED_STATUS_CHECKS, is(notNullValue())); + assertThat(Parameters.OPERATOR, is(notNullValue())); + assertThat(Parameters.WORKFLOWS, is(notNullValue())); + assertThat(Parameters.CODE_SCANNING_TOOLS, is(notNullValue())); + assertThat(Parameters.CODE_SCANNING_TOOLS.apply("[]", this.gitHub), is(notNullValue())); + assertThat(new StringParameter("any"), is(notNullValue())); + + assertThrows(GHException.class, () -> new GHRepositoryRule.ListParameter("") { + }); + assertThrows(GHException.class, () -> new GHRepositoryRule.Parameter("") { + }); } /** @@ -93,22 +100,25 @@ public void testSecurityAlertsThreshold() { } /** - * Tests to cover Operator enum. + * Tests to cover StatusCheckConfiguration class. */ @Test - public void testOperator() { - assertThat(Operator.ENDS_WITH, is(notNullValue())); + public void testStatusCheckConfiguration() { + StatusCheckConfiguration statusCheckConfiguration = new StatusCheckConfiguration(); + statusCheckConfiguration = new StatusCheckConfiguration(); + assertThat(statusCheckConfiguration.getContext(), is(nullValue())); + assertThat(statusCheckConfiguration.getIntegrationId(), is(nullValue())); } /** - * Tests that apply on null JsonNode returns null. - * - * @throws Exception - * if something goes wrong. + * Tests to cover WorkflowFileReference class. */ @Test - public void testParameterReturnsNullOnNullArg() throws Exception { - Parameter parameter = new StringParameter("any"); - assertThat(parameter.apply(null, null), is(nullValue())); + public void testWorkflowFileReference() { + WorkflowFileReference workflowFileReference = new WorkflowFileReference(); + assertThat(workflowFileReference.getPath(), is(nullValue())); + assertThat(workflowFileReference.getRef(), is(nullValue())); + assertThat(workflowFileReference.getRepositoryId(), is(equalTo(0L))); + assertThat(workflowFileReference.getSha(), is(nullValue())); } } diff --git a/src/test/java/org/kohsuke/github/GHRepositoryStatisticsTest.java b/src/test/java/org/kohsuke/github/GHRepositoryStatisticsTest.java index b7e32ce0a8..08a9524214 100644 --- a/src/test/java/org/kohsuke/github/GHRepositoryStatisticsTest.java +++ b/src/test/java/org/kohsuke/github/GHRepositoryStatisticsTest.java @@ -14,12 +14,6 @@ */ public class GHRepositoryStatisticsTest extends AbstractGitHubWireMockTest { - /** - * Create default GHRepositoryStatisticsTest instance - */ - public GHRepositoryStatisticsTest() { - } - /** The max iterations. */ public static int MAX_ITERATIONS = 3; @@ -27,7 +21,13 @@ public GHRepositoryStatisticsTest() { public static int SLEEP_INTERVAL = 5000; /** - * Test contributor stats. + * Create default GHRepositoryStatisticsTest instance + */ + public GHRepositoryStatisticsTest() { + } + + /** + * Test code frequency. * * @throws IOException * Signals that an I/O exception has occurred. @@ -35,10 +35,19 @@ public GHRepositoryStatisticsTest() { * the interrupted exception */ @Test - public void testContributorStats() throws IOException, InterruptedException { + @SuppressWarnings("SleepWhileInLoop") + public void testCodeFrequency() throws IOException, InterruptedException { // get the statistics - PagedIterable stats = getRepository().getStatistics() - .getContributorStats(); + List stats = null; + + for (int i = 0; i < MAX_ITERATIONS; i += 1) { + stats = getRepository().getStatistics().getCodeFrequency(); + if (stats == null) { + Thread.sleep(SLEEP_INTERVAL); + } else { + break; + } + } // check that the statistics were eventually retrieved if (stats == null) { @@ -47,40 +56,19 @@ public void testContributorStats() throws IOException, InterruptedException { } // check the statistics are accurate - List list = stats.toList(); - assertThat(list.size(), equalTo(99)); - - // find a particular developer - // TODO: Add an accessor method for this instead of having use a loop. - boolean developerFound = false; - final String authorLogin = "kohsuke"; - for (GHRepositoryStatistics.ContributorStats statsForAuthor : list) { - if (authorLogin.equals(statsForAuthor.getAuthor().getLogin())) { - assertThat(statsForAuthor.getTotal(), equalTo(715)); - assertThat(statsForAuthor.toString(), equalTo("kohsuke made 715 contributions over 494 weeks")); - - List weeks = statsForAuthor.getWeeks(); - assertThat(weeks.size(), equalTo(494)); - - try { - // check a particular week - // TODO: Maybe add a convenience method to get the week - // containing a certain date (Java.Util.Date). - GHRepositoryStatistics.ContributorStats.Week week = statsForAuthor.getWeek(1541289600); - assertThat(week.getNumberOfAdditions(), equalTo(63)); - assertThat(week.getNumberOfDeletions(), equalTo(56)); - assertThat(week.getNumberOfCommits(), equalTo(5)); - assertThat(week.toString(), - equalTo("Week starting 1541289600 - Additions: 63, Deletions: 56, Commits: 5")); - } catch (NoSuchElementException e) { - fail("Did not find week 1546128000"); - } - developerFound = true; + // TODO: Perhaps return this as a map with the timestamp as the key? + // Either that or wrap in an object with accessor methods. + Boolean foundWeek = false; + for (GHRepositoryStatistics.CodeFrequency item : stats) { + if (item.getWeekTimestamp() == 1535241600) { + assertThat(item.getAdditions(), equalTo(185L)); + assertThat(item.getDeletions(), equalTo(-243L)); + assertThat(item.toString(), equalTo("Week starting 1535241600 has 185 additions and 243 deletions")); + foundWeek = true; break; } } - - assertThat("Did not find author " + authorLogin, developerFound); + assertThat("Could not find week starting 1535241600", foundWeek); } /** @@ -137,7 +125,7 @@ public void testCommitActivity() throws IOException, InterruptedException { } /** - * Test code frequency. + * Test contributor stats. * * @throws IOException * Signals that an I/O exception has occurred. @@ -145,19 +133,10 @@ public void testCommitActivity() throws IOException, InterruptedException { * the interrupted exception */ @Test - @SuppressWarnings("SleepWhileInLoop") - public void testCodeFrequency() throws IOException, InterruptedException { + public void testContributorStats() throws IOException, InterruptedException { // get the statistics - List stats = null; - - for (int i = 0; i < MAX_ITERATIONS; i += 1) { - stats = getRepository().getStatistics().getCodeFrequency(); - if (stats == null) { - Thread.sleep(SLEEP_INTERVAL); - } else { - break; - } - } + PagedIterable stats = getRepository().getStatistics() + .getContributorStats(); // check that the statistics were eventually retrieved if (stats == null) { @@ -166,19 +145,40 @@ public void testCodeFrequency() throws IOException, InterruptedException { } // check the statistics are accurate - // TODO: Perhaps return this as a map with the timestamp as the key? - // Either that or wrap in an object with accessor methods. - Boolean foundWeek = false; - for (GHRepositoryStatistics.CodeFrequency item : stats) { - if (item.getWeekTimestamp() == 1535241600) { - assertThat(item.getAdditions(), equalTo(185L)); - assertThat(item.getDeletions(), equalTo(-243L)); - assertThat(item.toString(), equalTo("Week starting 1535241600 has 185 additions and 243 deletions")); - foundWeek = true; + List list = stats.toList(); + assertThat(list.size(), equalTo(99)); + + // find a particular developer + // TODO: Add an accessor method for this instead of having use a loop. + boolean developerFound = false; + final String authorLogin = "kohsuke"; + for (GHRepositoryStatistics.ContributorStats statsForAuthor : list) { + if (authorLogin.equals(statsForAuthor.getAuthor().getLogin())) { + assertThat(statsForAuthor.getTotal(), equalTo(715)); + assertThat(statsForAuthor.toString(), equalTo("kohsuke made 715 contributions over 494 weeks")); + + List weeks = statsForAuthor.getWeeks(); + assertThat(weeks.size(), equalTo(494)); + + try { + // check a particular week + // TODO: Maybe add a convenience method to get the week + // containing a certain date (Java.Util.Date). + GHRepositoryStatistics.ContributorStats.Week week = statsForAuthor.getWeek(1541289600); + assertThat(week.getNumberOfAdditions(), equalTo(63)); + assertThat(week.getNumberOfDeletions(), equalTo(56)); + assertThat(week.getNumberOfCommits(), equalTo(5)); + assertThat(week.toString(), + equalTo("Week starting 1541289600 - Additions: 63, Deletions: 56, Commits: 5")); + } catch (NoSuchElementException e) { + fail("Did not find week 1546128000"); + } + developerFound = true; break; } } - assertThat("Could not find week starting 1535241600", foundWeek); + + assertThat("Did not find author " + authorLogin, developerFound); } /** @@ -263,6 +263,10 @@ public void testPunchCard() throws IOException, InterruptedException { assertThat("Hour 10 for Day 2 not found.", hourFound); } + private GHRepository getRepository(GitHub gitHub) throws IOException { + return gitHub.getOrganization(GITHUB_API_TEST_ORG).getRepository("github-api"); + } + /** * Gets the repository. * @@ -273,8 +277,4 @@ public void testPunchCard() throws IOException, InterruptedException { protected GHRepository getRepository() throws IOException { return getRepository(gitHub); } - - private GHRepository getRepository(GitHub gitHub) throws IOException { - return gitHub.getOrganization(GITHUB_API_TEST_ORG).getRepository("github-api"); - } } diff --git a/src/test/java/org/kohsuke/github/GHRepositoryTest.java b/src/test/java/org/kohsuke/github/GHRepositoryTest.java index 0d1f6e4590..ae8fba9ba7 100644 --- a/src/test/java/org/kohsuke/github/GHRepositoryTest.java +++ b/src/test/java/org/kohsuke/github/GHRepositoryTest.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.time.Instant; import java.time.LocalDate; import java.util.*; import java.util.stream.Collectors; @@ -40,105 +41,78 @@ public GHRepositoryTest() { } /** - * Gets the repository. - * - * @return the repository - * @throws IOException - * Signals that an I/O exception has occurred. + * Latest repository exist. */ - protected GHRepository getRepository() throws IOException { - return getRepository(gitHub); - } - - private GHRepository getRepository(GitHub gitHub) throws IOException { - return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); + @Test + public void LatestRepositoryExist() { + try { + // add the repository that have latest release + GHRelease release = gitHub.getRepository("kamontat/CheckIDNumber").getLatestRelease(); + assertThat(release.getTagName(), equalTo("v3.0")); + } catch (IOException e) { + e.printStackTrace(); + fail(); + } } /** - * Test sync of fork - * - * @throws IOException - * Signals that an I/O exception has occurred. + * Latest repository not exist. */ @Test - public void sync() throws IOException { - GHRepository r = getRepository(); - GHBranchSync sync = r.sync("main"); - assertThat(sync.getOwner().getFullName(), equalTo("hub4j-test-org/github-api")); - assertThat(sync.getMessage(), equalTo("Successfully fetched and fast-forwarded from upstream github-api:main")); - assertThat(sync.getMergeType(), equalTo("fast-forward")); - assertThat(sync.getBaseBranch(), equalTo("github-api:main")); + public void LatestRepositoryNotExist() { + try { + // add the repository that `NOT` have latest release + GHRelease release = gitHub.getRepository("kamontat/Java8Example").getLatestRelease(); + assertThat(release, nullValue()); + } catch (IOException e) { + e.printStackTrace(); + fail(); + } } /** - * Test sync of repository not a fork + * Adds the collaborators. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ - @Test(expected = HttpException.class) - public void syncNoFork() throws IOException { - GHRepository r = getRepository(); - GHBranchSync sync = r.sync("main"); - fail("Should have thrown an exception"); + @Test + public void addCollaborators() throws Exception { + GHRepository repo = getRepository(); + GHUser user = getUser(); + List users = new ArrayList<>(); - } + users.add(user); + users.add(gitHub.getUser("jimmysombrero2")); + repo.addCollaborators(users, RepositoryRole.from(GHOrganization.Permission.PUSH)); - /** - * Test zipball. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Test - public void testZipball() throws IOException { - getTempRepository().readZip((InputStream inputstream) -> { - return new ByteArrayInputStream(IOUtils.toByteArray(inputstream)); - }, null); - } + GHPersonSet collabs = repo.getCollaborators(); + GHUser colabUser = collabs.byLogin("jimmysombrero"); + assertThat(colabUser.getAvatarUrl(), equalTo("https://avatars3.githubusercontent.com/u/12157727?v=4")); + assertThat(colabUser.getHtmlUrl().toString(), equalTo("https://github.com/jimmysombrero")); + assertThat(colabUser.getLocation(), nullValue()); - /** - * Test tarball. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Test - public void testTarball() throws IOException { - getTempRepository().readTar((InputStream inputstream) -> { - return new ByteArrayInputStream(IOUtils.toByteArray(inputstream)); - }, null); + assertThat(user.getName(), equalTo(colabUser.getName())); } /** - * Test getters. + * Adds the collaborators repo perm. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void testGetters() throws IOException { - GHRepository r = getTempRepository(); - - assertThat(r.hasAdminAccess(), is(true)); - assertThat(r.hasDownloads(), is(true)); - assertThat(r.hasIssues(), is(true)); - assertThat(r.hasPages(), is(false)); - assertThat(r.hasProjects(), is(true)); - assertThat(r.hasPullAccess(), is(true)); - assertThat(r.hasPushAccess(), is(true)); - assertThat(r.hasWiki(), is(true)); + public void addCollaboratorsRepoPerm() throws Exception { + GHRepository repo = getRepository(); + GHUser user = getUser(); - assertThat(r.isAllowMergeCommit(), is(true)); - assertThat(r.isAllowRebaseMerge(), is(true)); - assertThat(r.isAllowSquashMerge(), is(true)); - assertThat(r.isAllowForking(), is(false)); + RepositoryRole role = RepositoryRole.from(GHOrganization.Permission.PULL); + repo.addCollaborators(role, user); - String httpTransport = "https://github.com/hub4j-test-org/temp-testGetters.git"; - assertThat(r.getHttpTransportUrl(), equalTo(httpTransport)); + GHPersonSet collabs = repo.getCollaborators(); + GHUser colabUser = collabs.byLogin("jgangemi"); - assertThat(r.getName(), equalTo("temp-testGetters")); - assertThat(r.getFullName(), equalTo("hub4j-test-org/temp-testGetters")); + assertThat(user.getName(), equalTo(colabUser.getName())); } /** @@ -164,65 +138,88 @@ public void archive() throws Exception { } /** - * Checks if is disabled. + * Test demoing the issue with a user having the maintain permission on a repository. * - * @throws Exception + * Test checking the permission fallback mechanism in case the Github API changes. The test was recorded at a time a + * new permission was added by mistake. If a re-recording it is needed, you'll like have to manually edit the + * generated mocks to get a non existing permission See + * https://github.com/hub4j/github-api/issues/1671#issuecomment-1577515662 for the details. + * + * @throws IOException * the exception */ @Test - public void isDisabled() throws Exception { - GHRepository r = getRepository(); - - assertThat(r.isDisabled(), is(false)); + public void cannotRetrievePermissionMaintainUser() throws IOException { + GHRepository r = gitHub.getRepository("hub4j-test-org/maintain-permission-issue"); + GHPermissionType permission = r.getPermission("alecharp"); + assertThat(permission.toString(), is("UNKNOWN")); } /** - * Checks if is disabled true. + * Check stargazers count. * * @throws Exception * the exception */ @Test - public void isDisabledTrue() throws Exception { - GHRepository r = getRepository(); - - assertThat(r.isDisabled(), is(true)); + public void checkStargazersCount() throws Exception { + snapshotNotAllowed(); + GHRepository repo = getTempRepository(); + int stargazersCount = repo.getStargazersCount(); + assertThat(stargazersCount, equalTo(10)); } /** - * Gets the branch URL encoded. + * Check watchers count. * * @throws Exception * the exception */ @Test - public void getBranch_URLEncoded() throws Exception { - GHRepository repo = getRepository(); - GHBranch branch = repo.getBranch("test/#UrlEncode"); - assertThat(branch.getName(), is("test/#UrlEncode")); + public void checkWatchersCount() throws Exception { + snapshotNotAllowed(); + GHRepository repo = getTempRepository(); + int watchersCount = repo.getWatchersCount(); + assertThat(watchersCount, equalTo(10)); } /** - * Creates the signed commit verify error. + * Creates the dispatch event with client payload. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void createSignedCommitVerifyError() throws IOException { - GHRepository repository = getRepository(); - - GHTree ghTree = new GHTreeBuilder(repository).textEntry("a", "", false).create(); + public void createDispatchEventWithClientPayload() throws Exception { + GHRepository repository = getTempRepository(); + Map clientPayload = new HashMap<>(); + clientPayload.put("name", "joe.doe"); + clientPayload.put("list", new ArrayList<>()); + repository.dispatch("test", clientPayload); + } - GHVerification verification = repository.createCommit() - .message("test signing") - .withSignature("-----BEGIN PGP SIGNATURE-----\ninvalid\n-----END PGP SIGNATURE-----") - .tree(ghTree.getSha()) - .create() - .getCommitShortInfo() - .getVerification(); + /** + * Creates the dispatch event without client payload. + * + * @throws Exception + * the exception + */ + @Test + public void createDispatchEventWithoutClientPayload() throws Exception { + GHRepository repository = getTempRepository(); + repository.dispatch("test", null); + } - assertThat(verification.getReason(), equalTo(GPGVERIFY_ERROR)); + /** + * Creates the secret. + * + * @throws Exception + * the exception + */ + @Test + public void createSecret() throws Exception { + GHRepository repo = getTempRepository(); + repo.createSecret("secret", "encrypted", "public"); } /** @@ -249,22 +246,26 @@ public void createSignedCommitUnknownSignatureType() throws IOException { } /** - * List stargazers. + * Creates the signed commit verify error. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void listStargazers() throws IOException { + public void createSignedCommitVerifyError() throws IOException { GHRepository repository = getRepository(); - assertThat(repository.listStargazers().toList(), is(empty())); - repository = gitHub.getOrganization("hub4j").getRepository("github-api"); - Iterable stargazers = repository.listStargazers2(); - GHStargazer stargazer = stargazers.iterator().next(); - assertThat(stargazer.getStarredAt(), equalTo(new Date(1271650383000L))); - assertThat(stargazer.getUser().getLogin(), equalTo("nielswind")); - assertThat(stargazer.getRepository(), sameInstance(repository)); + GHTree ghTree = new GHTreeBuilder(repository).textEntry("a", "", false).create(); + + GHVerification verification = repository.createCommit() + .message("test signing") + .withSignature("-----BEGIN PGP SIGNATURE-----\ninvalid\n-----END PGP SIGNATURE-----") + .tree(ghTree.getSha()) + .create() + .getCommitShortInfo() + .getVerification(); + + assertThat(verification.getReason(), equalTo(GPGVERIFY_ERROR)); } /** @@ -295,241 +296,207 @@ public void getBranchNonExistentBut200Status() throws Exception { } /** - * Subscription. + * Gets the branch URL encoded. * * @throws Exception * the exception */ @Test - public void subscription() throws Exception { - GHRepository r = getRepository(); - assertThat(r.getSubscription(), nullValue()); - GHSubscription s = r.subscribe(true, false); - try { - - assertThat(r, equalTo(s.getRepository())); - assertThat(s.isIgnored(), equalTo(false)); - assertThat(s.isSubscribed(), equalTo(true)); - assertThat(s.getRepositoryUrl().toString(), containsString("/repos/hub4j-test-org/github-api")); - assertThat(s.getUrl().toString(), containsString("/repos/hub4j-test-org/github-api/subscription")); - - assertThat(s.getReason(), nullValue()); - assertThat(s.getCreatedAt(), equalTo(new Date(1611377286000L))); - } finally { - s.delete(); - } - - assertThat(r.getSubscription(), nullValue()); + public void getBranch_URLEncoded() throws Exception { + GHRepository repo = getRepository(); + GHBranch branch = repo.getBranch("test/#UrlEncode"); + assertThat(branch.getName(), is("test/#UrlEncode")); } /** - * Test set public. + * Gets the check runs. * * @throws Exception * the exception */ @Test - public void testSetPublic() throws Exception { - kohsuke(); - GHUser myself = gitHub.getMyself(); - String repoName = "test-repo-public"; - GHRepository repo = gitHub.createRepository(repoName).private_(false).create(); - try { - assertThat(repo.isPrivate(), is(false)); - repo.setPrivate(true); - assertThat(myself.getRepository(repoName).isPrivate(), is(true)); - repo.setPrivate(false); - assertThat(myself.getRepository(repoName).isPrivate(), is(false)); - } finally { - repo.delete(); + public void getCheckRuns() throws Exception { + final int expectedCount = 8; + // Use github-api repository as it has checks set up + PagedIterable checkRuns = gitHub.getOrganization("hub4j") + .getRepository("github-api") + .getCheckRuns("78b9ff49d47daaa158eb373c4e2e040f739df8b9"); + // Check if the paging works correctly + assertThat(checkRuns.withPageSize(2).iterator().nextPage(), hasSize(2)); + + // Check if the checkruns are all succeeded and if we got all of them + int checkRunsCount = 0; + for (GHCheckRun checkRun : checkRuns) { + assertThat(checkRun.getConclusion(), equalTo(Conclusion.SUCCESS)); + checkRunsCount++; + } + assertThat(checkRunsCount, equalTo(expectedCount)); + + // Check that we can call update on the results + for (GHCheckRun checkRun : checkRuns) { + checkRun.update(); } } /** - * Tests the creation of repositories with alternating visibilities for orgs. + * Filter out the checks from a reference * * @throws Exception * the exception */ @Test - public void testCreateVisibilityForOrganization() throws Exception { - GHOrganization organization = gitHub.getOrganization(GITHUB_API_TEST_ORG); + public void getCheckRunsWithParams() throws Exception { + final int expectedCount = 1; + // Use github-api repository as it has checks set up + final Map params = new HashMap<>(1); + params.put("check_name", "build-only (Java 17)"); + PagedIterable checkRuns = gitHub.getOrganization("hub4j") + .getRepository("github-api") + .getCheckRuns("54d60fbb53b4efa19f3081417bfb6a1de30c55e4", params); - // can not test for internal, as test org is not assigned to an enterprise - for (Visibility visibility : Sets.newHashSet(Visibility.PUBLIC, Visibility.PRIVATE)) { - String repoName = String.format("test-repo-visibility-%s", visibility.toString()); - GHRepository repository = organization.createRepository(repoName).visibility(visibility).create(); - try { - assertThat(repository.getVisibility(), is(visibility)); - assertThat(organization.getRepository(repoName).getVisibility(), is(visibility)); - } finally { - repository.delete(); - } + // Check if the checkruns are all succeeded and if we got all of them + int checkRunsCount = 0; + for (GHCheckRun checkRun : checkRuns) { + assertThat(checkRun.getConclusion(), equalTo(Conclusion.SUCCESS)); + checkRunsCount++; } + assertThat(checkRunsCount, equalTo(expectedCount)); } /** - * Tests the creation of repositories with alternating visibilities for users. + * Gets the collaborators. * * @throws Exception * the exception */ @Test - public void testCreateVisibilityForUser() throws Exception { - - GHUser myself = gitHub.getMyself(); - - // can not test for internal, as test org is not assigned to an enterprise - for (Visibility visibility : Sets.newHashSet(Visibility.PUBLIC, Visibility.PRIVATE)) { - String repoName = String.format("test-repo-visibility-%s", visibility.toString()); - boolean isPrivate = visibility.equals(Visibility.PRIVATE); - GHRepository repository = gitHub.createRepository(repoName) - .private_(isPrivate) - .visibility(visibility) - .create(); - try { - assertThat(repository.getVisibility(), is(visibility)); - assertThat(myself.getRepository(repoName).getVisibility(), is(visibility)); - } finally { - repository.delete(); - } - } + public void getCollaborators() throws Exception { + GHRepository repo = getRepository(gitHub); + GHPersonSet collaborators = repo.getCollaborators(); + assertThat(collaborators.size(), greaterThan(0)); } /** - * Test update repository. + * Gets the commits between over 250. * * @throws Exception * the exception */ @Test - public void testUpdateRepository() throws Exception { - String homepage = "https://github-api.kohsuke.org/apidocs/index.html"; - String description = "A test repository for update testing via the github-api project"; - - GHRepository repo = getTempRepository(); - GHRepository.Updater builder = repo.update(); - - // one merge option is always required - GHRepository updated = builder.allowRebaseMerge(false) - .allowSquashMerge(false) - .deleteBranchOnMerge(true) - .allowForking(true) - .description(description) - .downloads(false) - .downloads(false) - .homepage(homepage) - .issues(false) - .private_(true) - .projects(false) - .wiki(false) - .done(); + public void getCommitsBetweenOver250() throws Exception { + GHRepository repository = getRepository(); + int startingCount = mockGitHub.getRequestCount(); + GHCompare compare = repository.getCompare("4261c42949915816a9f246eb14c3dfd21a637bc2", + "94ff089e60064bfa43e374baeb10846f7ce82f40"); + int actualCount = 0; + for (GHCompare.Commit item : compare.getCommits()) { + assertThat(item, notNullValue()); + actualCount++; + } + assertThat(compare.getTotalCommits(), is(283)); + assertThat(actualCount, is(250)); + assertThat(mockGitHub.getRequestCount(), equalTo(startingCount + 1)); - assertThat(updated.isAllowMergeCommit(), is(true)); - assertThat(updated.isAllowRebaseMerge(), is(false)); - assertThat(updated.isAllowSquashMerge(), is(false)); - assertThat(updated.isDeleteBranchOnMerge(), is(true)); - assertThat(updated.isAllowForking(), is(true)); - assertThat(updated.isPrivate(), is(true)); - assertThat(updated.hasDownloads(), is(false)); - assertThat(updated.hasIssues(), is(false)); - assertThat(updated.hasProjects(), is(false)); - assertThat(updated.hasWiki(), is(false)); + // Additional GHCompare checks + assertThat(compare.getAheadBy(), equalTo(283)); + assertThat(compare.getBehindBy(), equalTo(0)); + assertThat(compare.getStatus(), equalTo(GHCompare.Status.ahead)); + assertThat(compare.getDiffUrl().toString(), + endsWith( + "compare/4261c42949915816a9f246eb14c3dfd21a637bc2...94ff089e60064bfa43e374baeb10846f7ce82f40.diff")); + assertThat(compare.getHtmlUrl().toString(), + endsWith( + "compare/4261c42949915816a9f246eb14c3dfd21a637bc2...94ff089e60064bfa43e374baeb10846f7ce82f40")); + assertThat(compare.getPatchUrl().toString(), + endsWith( + "compare/4261c42949915816a9f246eb14c3dfd21a637bc2...94ff089e60064bfa43e374baeb10846f7ce82f40.patch")); + assertThat(compare.getPermalinkUrl().toString(), + endsWith("compare/hub4j-test-org:4261c42...hub4j-test-org:94ff089")); + assertThat(compare.getUrl().toString(), + endsWith( + "compare/4261c42949915816a9f246eb14c3dfd21a637bc2...94ff089e60064bfa43e374baeb10846f7ce82f40")); - assertThat(updated.getHomepage(), equalTo(homepage)); - assertThat(updated.getDescription(), equalTo(description)); + assertThat(compare.getBaseCommit().getSHA1(), equalTo("4261c42949915816a9f246eb14c3dfd21a637bc2")); - // test the other merge option and making the repo public again - GHRepository redux = updated.update().allowMergeCommit(false).allowRebaseMerge(true).private_(false).done(); + assertThat(compare.getMergeBaseCommit().getSHA1(), equalTo("4261c42949915816a9f246eb14c3dfd21a637bc2")); + // it appears this field is not present in the returned JSON. Strange. + assertThat(compare.getMergeBaseCommit().getCommit().getSha(), nullValue()); + assertThat(compare.getMergeBaseCommit().getCommit().getUrl(), + endsWith("/commits/4261c42949915816a9f246eb14c3dfd21a637bc2")); + assertThat(compare.getMergeBaseCommit().getCommit().getMessage(), + endsWith("[maven-release-plugin] prepare release github-api-1.123")); + assertThat(compare.getMergeBaseCommit().getCommit().getAuthor().getName(), equalTo("Liam Newman")); + assertThat(compare.getMergeBaseCommit().getCommit().getCommitter().getName(), equalTo("Liam Newman")); - assertThat(redux.isAllowMergeCommit(), is(false)); - assertThat(redux.isAllowRebaseMerge(), is(true)); - assertThat(redux.isPrivate(), is(false)); + assertThat(compare.getMergeBaseCommit().getCommit().getTree().getSha(), + equalTo("5da98090976978c93aba0bdfa550e05675543f99")); + assertThat(compare.getMergeBaseCommit().getCommit().getTree().getUrl(), + endsWith("/git/trees/5da98090976978c93aba0bdfa550e05675543f99")); - String updatedDescription = "updated using set()"; - redux = redux.set().description(updatedDescription); + assertThat(compare.getFiles().length, equalTo(300)); + assertThat(compare.getFiles()[0].getFileName(), equalTo(".github/PULL_REQUEST_TEMPLATE.md")); + assertThat(compare.getFiles()[0].getLinesAdded(), equalTo(8)); + assertThat(compare.getFiles()[0].getLinesChanged(), equalTo(15)); + assertThat(compare.getFiles()[0].getLinesDeleted(), equalTo(7)); + assertThat(compare.getFiles()[0].getFileName(), equalTo(".github/PULL_REQUEST_TEMPLATE.md")); + assertThat(compare.getFiles()[0].getPatch(), startsWith("@@ -1,15 +1,16 @@")); + assertThat(compare.getFiles()[0].getPreviousFilename(), nullValue()); + assertThat(compare.getFiles()[0].getStatus(), equalTo("modified")); + assertThat(compare.getFiles()[0].getSha(), equalTo("e4234f5f6f39899282a6ef1edff343ae1269222e")); - assertThat(redux.getDescription(), equalTo(updatedDescription)); + assertThat(compare.getFiles()[0].getBlobUrl().toString(), + endsWith("/blob/94ff089e60064bfa43e374baeb10846f7ce82f40/.github/PULL_REQUEST_TEMPLATE.md")); + assertThat(compare.getFiles()[0].getRawUrl().toString(), + endsWith("/raw/94ff089e60064bfa43e374baeb10846f7ce82f40/.github/PULL_REQUEST_TEMPLATE.md")); } /** - * Test get repository with visibility. + * Gets the commits between paged. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void testGetRepositoryWithVisibility() throws IOException { - snapshotNotAllowed(); - final String repoName = "test-repo-visibility"; - final GHRepository repo = getTempRepository(repoName); - assertThat(repo.getVisibility(), equalTo(Visibility.PUBLIC)); - - repo.setVisibility(Visibility.INTERNAL); - assertThat(gitHub.getRepository(repo.getOwnerName() + "/" + repo.getName()).getVisibility(), - equalTo(Visibility.INTERNAL)); - - repo.setVisibility(Visibility.PRIVATE); - assertThat(gitHub.getRepository(repo.getOwnerName() + "/" + repo.getName()).getVisibility(), - equalTo(Visibility.PRIVATE)); - - repo.setVisibility(Visibility.PUBLIC); - assertThat(gitHub.getRepository(repo.getOwnerName() + "/" + repo.getName()).getVisibility(), - equalTo(Visibility.PUBLIC)); - - // deliberately bogus response in snapshot - assertThat(gitHub.getRepository(repo.getOwnerName() + "/" + repo.getName()).getVisibility(), - equalTo(Visibility.UNKNOWN)); + public void getCommitsBetweenPaged() throws Exception { + GHRepository repository = getRepository(); + int startingCount = mockGitHub.getRequestCount(); + repository.setCompareUsePaginatedCommits(true); + GHCompare compare = repository.getCompare("4261c42949915816a9f246eb14c3dfd21a637bc2", + "94ff089e60064bfa43e374baeb10846f7ce82f40"); + int actualCount = 0; + for (GHCompare.Commit item : compare.getCommits()) { + assertThat(item, notNullValue()); + actualCount++; + } + assertThat(compare.getTotalCommits(), is(283)); + assertThat(actualCount, is(283)); + assertThat(mockGitHub.getRequestCount(), equalTo(startingCount + 4)); } /** - * List contributors. + * Gets the delete branch on merge. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void listContributors() throws IOException { - GHRepository r = gitHub.getOrganization("hub4j").getRepository("github-api"); - int i = 0; - boolean kohsuke = false; - - for (GHRepository.Contributor c : r.listContributors()) { - if (c.getLogin().equals("kohsuke")) { - assertThat(c.getContributions(), greaterThan(0)); - kohsuke = true; - } - if (i++ > 5) { - break; - } - } - - assertThat(kohsuke, is(true)); + public void getDeleteBranchOnMerge() throws IOException { + GHRepository r = getRepository(); + assertThat(r.isDeleteBranchOnMerge(), notNullValue()); } /** - * List contributors. + * Gets the last commit status. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void listContributorsAnon() throws IOException { - GHRepository r = gitHub.getOrganization("hub4j").getRepository("github-api"); - int i = 0; - boolean kohsuke = false; - - for (GHRepository.Contributor c : r.listContributors(true)) { - if (c.getType().equals("Anonymous")) { - assertThat(c.getContributions(), is(3)); - kohsuke = true; - } - if (++i > 1) { - break; - } - } - - assertThat(kohsuke, is(true)); + public void getLastCommitStatus() throws Exception { + GHCommitStatus status = getRepository().getLastCommitStatus("8051615eff597f4e49f4f47625e6fc2b49f26bfc"); + assertThat(status.getId(), equalTo(9027542286L)); + assertThat(status.getState(), equalTo(GHCommitState.SUCCESS)); + assertThat(status.getContext(), equalTo("ci/circleci: build")); } /** @@ -567,145 +534,143 @@ public void getPermission() throws Exception { } /** - * Checks for permission. + * Gets the post commit hooks. * * @throws Exception * the exception */ @Test - public void hasPermission() throws Exception { - kohsuke(); - GHRepository publicRepository = gitHub.getRepository("hub4j-test-org/test-permission"); - assertThat(publicRepository.hasPermission("kohsuke", GHPermissionType.ADMIN), equalTo(true)); - assertThat(publicRepository.hasPermission("kohsuke", GHPermissionType.WRITE), equalTo(true)); - assertThat(publicRepository.hasPermission("kohsuke", GHPermissionType.READ), equalTo(true)); - assertThat(publicRepository.hasPermission("kohsuke", GHPermissionType.NONE), equalTo(false)); - - assertThat(publicRepository.hasPermission("dude", GHPermissionType.ADMIN), equalTo(false)); - assertThat(publicRepository.hasPermission("dude", GHPermissionType.WRITE), equalTo(false)); - assertThat(publicRepository.hasPermission("dude", GHPermissionType.READ), equalTo(true)); - assertThat(publicRepository.hasPermission("dude", GHPermissionType.NONE), equalTo(false)); - - // also check the GHUser method - GHUser kohsuke = gitHub.getUser("kohsuke"); - assertThat(publicRepository.hasPermission(kohsuke, GHPermissionType.ADMIN), equalTo(true)); - assertThat(publicRepository.hasPermission(kohsuke, GHPermissionType.WRITE), equalTo(true)); - assertThat(publicRepository.hasPermission(kohsuke, GHPermissionType.READ), equalTo(true)); - assertThat(publicRepository.hasPermission(kohsuke, GHPermissionType.NONE), equalTo(false)); - - // check NONE on a private project - GHRepository privateRepository = gitHub.getRepository("hub4j-test-org/test-permission-private"); - assertThat(privateRepository.hasPermission("dude", GHPermissionType.ADMIN), equalTo(false)); - assertThat(privateRepository.hasPermission("dude", GHPermissionType.WRITE), equalTo(false)); - assertThat(privateRepository.hasPermission("dude", GHPermissionType.READ), equalTo(false)); - assertThat(privateRepository.hasPermission("dude", GHPermissionType.NONE), equalTo(true)); + public void getPostCommitHooks() throws Exception { + GHRepository repo = getRepository(gitHub); + Set postcommitHooks = setupPostCommitHooks(repo); + assertThat(postcommitHooks, is(empty())); } /** - * Latest repository exist. + * Gets the public key. + * + * @throws Exception + * the exception */ @Test - public void LatestRepositoryExist() { - try { - // add the repository that have latest release - GHRelease release = gitHub.getRepository("kamontat/CheckIDNumber").getLatestRelease(); - assertThat(release.getTagName(), equalTo("v3.0")); - } catch (IOException e) { - e.printStackTrace(); - fail(); - } + public void getPublicKey() throws Exception { + GHRepository repo = getTempRepository(); + GHRepositoryPublicKey publicKey = repo.getPublicKey(); + assertThat(publicKey, notNullValue()); + assertThat(publicKey.getKey(), equalTo("test-key")); + assertThat(publicKey.getKeyId(), equalTo("key-id")); } /** - * Adds the collaborators. + * Gets the ref. * * @throws Exception * the exception */ @Test - public void addCollaborators() throws Exception { + public void getRef() throws Exception { GHRepository repo = getRepository(); - GHUser user = getUser(); - List users = new ArrayList<>(); - users.add(user); - users.add(gitHub.getUser("jimmysombrero2")); - repo.addCollaborators(users, RepositoryRole.from(GHOrganization.Permission.PUSH)); + GHRef ghRef; - GHPersonSet collabs = repo.getCollaborators(); - GHUser colabUser = collabs.byLogin("jimmysombrero"); + // handle refs/* + ghRef = repo.getRef("heads/gh-pages"); + GHRef ghRefWithPrefix = repo.getRef("refs/heads/gh-pages"); - assertThat(user.getName(), equalTo(colabUser.getName())); + assertThat(ghRef, notNullValue()); + assertThat(ghRef.getRef(), equalTo("refs/heads/gh-pages")); + assertThat(ghRefWithPrefix.getRef(), equalTo(ghRef.getRef())); + assertThat(ghRefWithPrefix.getObject().getType(), equalTo("commit")); + assertThat(ghRefWithPrefix.getObject().getUrl().toString(), + containsString("/repos/hub4j-test-org/github-api/git/commits/")); + + // git/refs/heads/gh-pages + ghRef = repo.getRef("heads/gh-pages"); + assertThat(ghRef, notNullValue()); + assertThat(ghRef.getRef(), equalTo("refs/heads/gh-pages")); + + // git/refs/heads/gh + try { + ghRef = repo.getRef("heads/gh"); + fail(); + } catch (Exception e) { + assertThat(e, instanceOf(GHFileNotFoundException.class)); + assertThat(e.getMessage(), + containsString( + "{\"message\":\"Not Found\",\"documentation_url\":\"https://developer.github.com/v3/git/refs/#get-a-reference\"}")); + } + + // git/refs/headz + try { + ghRef = repo.getRef("headz"); + fail(); + } catch (Exception e) { + assertThat(e, instanceOf(GHFileNotFoundException.class)); + assertThat(e.getMessage(), + containsString( + "{\"message\":\"Not Found\",\"documentation_url\":\"https://developer.github.com/v3/git/refs/#get-a-reference\"}")); + } } /** - * Adds the collaborators repo perm. + * Gets the refs. * * @throws Exception * the exception */ @Test - public void addCollaboratorsRepoPerm() throws Exception { - GHRepository repo = getRepository(); - GHUser user = getUser(); - - RepositoryRole role = RepositoryRole.from(GHOrganization.Permission.PULL); - repo.addCollaborators(role, user); - - GHPersonSet collabs = repo.getCollaborators(); - GHUser colabUser = collabs.byLogin("jgangemi"); - - assertThat(user.getName(), equalTo(colabUser.getName())); + public void getRefs() throws Exception { + GHRepository repo = getTempRepository(); + GHRef[] refs = repo.getRefs(); + assertThat(refs, notNullValue()); + assertThat(refs.length, equalTo(1)); + assertThat(refs[0].getRef(), equalTo("refs/heads/main")); } /** - * Latest repository not exist. + * Gets the refs empty tags. + * + * @throws Exception + * the exception */ @Test - public void LatestRepositoryNotExist() { + public void getRefsEmptyTags() throws Exception { + GHRepository repo = getTempRepository(); try { - // add the repository that `NOT` have latest release - GHRelease release = gitHub.getRepository("kamontat/Java8Example").getLatestRelease(); - assertThat(release, nullValue()); - } catch (IOException e) { - e.printStackTrace(); + repo.getRefs("tags"); fail(); + } catch (Exception e) { + assertThat(e, instanceOf(GHFileNotFoundException.class)); + assertThat(e.getMessage(), + containsString( + "{\"message\":\"Not Found\",\"documentation_url\":\"https://developer.github.com/v3/git/refs/#get-a-reference\"}")); } } /** - * List releases. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Test - public void listReleases() throws IOException { - PagedIterable releases = gitHub.getOrganization("github").getRepository("hub").listReleases(); - assertThat(releases, is(not(emptyIterable()))); - } - - /** - * Gets the release exists. + * Gets the refs heads. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void getReleaseExists() throws IOException { - GHRelease release = gitHub.getOrganization("github").getRepository("hub").getRelease(6839710); - assertThat(release.getTagName(), equalTo("v2.3.0-pre10")); + public void getRefsHeads() throws Exception { + GHRepository repo = getTempRepository(); + GHRef[] refs = repo.getRefs("heads"); + assertThat(refs, notNullValue()); + assertThat(refs.length, equalTo(1)); + assertThat(refs[0].getRef(), equalTo("refs/heads/main")); } /** - * Gets the release does not exist. + * Gets the release by tag name does not exist. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void getReleaseDoesNotExist() throws IOException { - GHRelease release = gitHub.getOrganization("github").getRepository("hub").getRelease(Long.MAX_VALUE); + public void getReleaseByTagNameDoesNotExist() throws IOException { + GHRelease release = getRepository().getReleaseByTagName("foo-bar-baz"); assertThat(release, nullValue()); } @@ -723,96 +688,54 @@ public void getReleaseByTagNameExists() throws IOException { } /** - * Gets the release by tag name does not exist. + * Gets the release does not exist. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void getReleaseByTagNameDoesNotExist() throws IOException { - GHRelease release = getRepository().getReleaseByTagName("foo-bar-baz"); + public void getReleaseDoesNotExist() throws IOException { + GHRelease release = gitHub.getOrganization("github").getRepository("hub").getRelease(Long.MAX_VALUE); assertThat(release, nullValue()); } /** - * List languages. + * Gets the release exists. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void listLanguages() throws IOException { - GHRepository r = gitHub.getRepository("hub4j/github-api"); - String mainLanguage = r.getLanguage(); - assertThat(mainLanguage, equalTo("Java")); - Map languages = r.listLanguages(); - assertThat(languages.containsKey(mainLanguage), is(true)); - assertThat(languages.get("Java"), greaterThan(100000L)); + public void getReleaseExists() throws IOException { + GHRelease release = gitHub.getOrganization("github").getRepository("hub").getRelease(6839710); + assertThat(release.getTagName(), equalTo("v2.3.0-pre10")); } /** - * List commit comments no comments. - * - * @throws IOException - * Signals that an I/O exception has occurred. + * Gh repository search builder fork default reset forks search terms. */ @Test - public void listCommitCommentsNoComments() throws IOException { - List commitComments = getRepository() - .listCommitComments("c413fc1e3057332b93850ea48202627d29a37de5") - .toList(); + public void ghRepositorySearchBuilderForkDefaultResetForksSearchTerms() { + GHRepositorySearchBuilder ghRepositorySearchBuilder = new GHRepositorySearchBuilder(gitHub); - assertThat("Commit has no comments", commitComments.isEmpty()); + ghRepositorySearchBuilder = ghRepositorySearchBuilder.fork(GHFork.PARENT_AND_FORKS); + assertThat(ghRepositorySearchBuilder.terms.stream().filter(item -> item.contains("fork:true")).count(), is(1L)); + assertThat(ghRepositorySearchBuilder.terms.stream().filter(item -> item.contains("fork:")).count(), is(1L)); - commitComments = getRepository().getCommit("c413fc1e3057332b93850ea48202627d29a37de5").listComments().toList(); + ghRepositorySearchBuilder = ghRepositorySearchBuilder.fork(GHFork.FORKS_ONLY); + assertThat(ghRepositorySearchBuilder.terms.stream().filter(item -> item.contains("fork:only")).count(), is(1L)); + assertThat(ghRepositorySearchBuilder.terms.stream().filter(item -> item.contains("fork:")).count(), is(2L)); - assertThat("Commit has no comments", commitComments.isEmpty()); + ghRepositorySearchBuilder = ghRepositorySearchBuilder.fork(GHFork.PARENT_ONLY); + assertThat(ghRepositorySearchBuilder.terms.stream().filter(item -> item.contains("fork:")).count(), is(0L)); } /** - * Search all public and forked repos. - * - * @throws IOException - * Signals that an I/O exception has occurred. + * Gh repository search builder ignores unknown visibility. */ @Test - public void searchAllPublicAndForkedRepos() throws IOException { - PagedSearchIterable list = gitHub.searchRepositories() - .user("t0m4uk1991") - .visibility(GHRepository.Visibility.PUBLIC) - .fork(GHFork.PARENT_AND_FORKS) - .list(); - List u = list.toList(); - assertThat(u.size(), is(14)); - assertThat(u.stream().filter(item -> item.getName().equals("github-api")).count(), is(1L)); - assertThat(u.stream().filter(item -> item.getName().equals("Complete-Python-3-Bootcamp")).count(), is(1L)); - } - - /** - * Search for public forked only repos. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Test - public void searchForPublicForkedOnlyRepos() throws IOException { - PagedSearchIterable list = gitHub.searchRepositories() - .user("t0m4uk1991") - .visibility(GHRepository.Visibility.PUBLIC) - .fork(GHFork.FORKS_ONLY) - .list(); - List u = list.toList(); - assertThat(u.size(), is(2)); - assertThat(u.get(0).getName(), is("github-api")); - assertThat(u.get(1).getName(), is("Complete-Python-3-Bootcamp")); - } - - /** - * Gh repository search builder ignores unknown visibility. - */ - @Test - public void ghRepositorySearchBuilderIgnoresUnknownVisibility() { - GHRepositorySearchBuilder ghRepositorySearchBuilder; + public void ghRepositorySearchBuilderIgnoresUnknownVisibility() { + GHRepositorySearchBuilder ghRepositorySearchBuilder; GHException exception = assertThrows(GHException.class, () -> new GHRepositorySearchBuilder(gitHub).visibility(Visibility.UNKNOWN)); @@ -830,386 +753,289 @@ public void ghRepositorySearchBuilderIgnoresUnknownVisibility() { } /** - * Gh repository search builder fork default reset forks search terms. + * Checks for permission. + * + * @throws Exception + * the exception */ @Test - public void ghRepositorySearchBuilderForkDefaultResetForksSearchTerms() { - GHRepositorySearchBuilder ghRepositorySearchBuilder = new GHRepositorySearchBuilder(gitHub); + public void hasPermission() throws Exception { + kohsuke(); + GHRepository publicRepository = gitHub.getRepository("hub4j-test-org/test-permission"); + assertThat(publicRepository.hasPermission("kohsuke", GHPermissionType.ADMIN), equalTo(true)); + assertThat(publicRepository.hasPermission("kohsuke", GHPermissionType.WRITE), equalTo(true)); + assertThat(publicRepository.hasPermission("kohsuke", GHPermissionType.READ), equalTo(true)); + assertThat(publicRepository.hasPermission("kohsuke", GHPermissionType.NONE), equalTo(false)); - ghRepositorySearchBuilder = ghRepositorySearchBuilder.fork(GHFork.PARENT_AND_FORKS); - assertThat(ghRepositorySearchBuilder.terms.stream().filter(item -> item.contains("fork:true")).count(), is(1L)); - assertThat(ghRepositorySearchBuilder.terms.stream().filter(item -> item.contains("fork:")).count(), is(1L)); + assertThat(publicRepository.hasPermission("dude", GHPermissionType.ADMIN), equalTo(false)); + assertThat(publicRepository.hasPermission("dude", GHPermissionType.WRITE), equalTo(false)); + assertThat(publicRepository.hasPermission("dude", GHPermissionType.READ), equalTo(true)); + assertThat(publicRepository.hasPermission("dude", GHPermissionType.NONE), equalTo(false)); - ghRepositorySearchBuilder = ghRepositorySearchBuilder.fork(GHFork.FORKS_ONLY); - assertThat(ghRepositorySearchBuilder.terms.stream().filter(item -> item.contains("fork:only")).count(), is(1L)); - assertThat(ghRepositorySearchBuilder.terms.stream().filter(item -> item.contains("fork:")).count(), is(2L)); + // also check the GHUser method + GHUser kohsuke = gitHub.getUser("kohsuke"); + assertThat(publicRepository.hasPermission(kohsuke, GHPermissionType.ADMIN), equalTo(true)); + assertThat(publicRepository.hasPermission(kohsuke, GHPermissionType.WRITE), equalTo(true)); + assertThat(publicRepository.hasPermission(kohsuke, GHPermissionType.READ), equalTo(true)); + assertThat(publicRepository.hasPermission(kohsuke, GHPermissionType.NONE), equalTo(false)); - ghRepositorySearchBuilder = ghRepositorySearchBuilder.fork(GHFork.PARENT_ONLY); - assertThat(ghRepositorySearchBuilder.terms.stream().filter(item -> item.contains("fork:")).count(), is(0L)); + // check NONE on a private project + GHRepository privateRepository = gitHub.getRepository("hub4j-test-org/test-permission-private"); + assertThat(privateRepository.hasPermission("dude", GHPermissionType.ADMIN), equalTo(false)); + assertThat(privateRepository.hasPermission("dude", GHPermissionType.WRITE), equalTo(false)); + assertThat(privateRepository.hasPermission("dude", GHPermissionType.READ), equalTo(false)); + assertThat(privateRepository.hasPermission("dude", GHPermissionType.NONE), equalTo(true)); } /** - * List commit comments some comments. + * Checks if is disabled. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void listCommitCommentsSomeComments() throws IOException { - List commitComments = getRepository() - .listCommitComments("499d91f9f846b0087b2a20cf3648b49dc9c2eeef") - .toList(); - - assertThat("Two comments present", commitComments.size(), equalTo(2)); - assertThat("Comment text found", - commitComments.stream().map(GHCommitComment::getBody).collect(Collectors.toList()), - containsInAnyOrder("comment 1", "comment 2")); - - commitComments = getRepository().getCommit("499d91f9f846b0087b2a20cf3648b49dc9c2eeef").listComments().toList(); + public void isDisabled() throws Exception { + GHRepository r = getRepository(); - assertThat("Two comments present", commitComments.size(), equalTo(2)); - assertThat("Comment text found", - commitComments.stream().map(GHCommitComment::getBody).collect(Collectors.toList()), - containsInAnyOrder("comment 1", "comment 2")); + assertThat(r.isDisabled(), is(false)); } /** - * List empty contributors. + * Checks if is disabled true. * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Test // Issue #261 - public void listEmptyContributors() throws IOException { - assertThat("This list should be empty, but should return a valid empty iterable.", - gitHub.getRepository(GITHUB_API_TEST_ORG + "/empty").listContributors(), - is(emptyIterable())); - } - - /** - * Search repositories. + * @throws Exception + * the exception */ @Test - public void searchRepositories() { - PagedSearchIterable r = gitHub.searchRepositories() - .q("tetris") - .language("assembly") - .sort(GHRepositorySearchBuilder.Sort.STARS) - .list(); - GHRepository u = r.iterator().next(); - // System.out.println(u.getName()); - assertThat(u.getId(), notNullValue()); - assertThat(u.getLanguage(), equalTo("Assembly")); - assertThat(r.getTotalCount(), greaterThan(0)); - } + public void isDisabledTrue() throws Exception { + GHRepository r = getRepository(); - /** - * Search org for repositories. - */ - @Test - public void searchOrgForRepositories() { - PagedSearchIterable r = gitHub.searchRepositories().org("hub4j-test-org").list(); - GHRepository u = r.iterator().next(); - assertThat(u.getOwnerName(), equalTo("hub4j-test-org")); - assertThat(r.getTotalCount(), greaterThan(0)); + assertThat(r.isDisabled(), is(true)); } /** - * Test issue 162. + * List collaborators. * * @throws Exception * the exception */ - @Test // issue #162 - public void testIssue162() throws Exception { - GHRepository r = gitHub.getRepository("hub4j/github-api"); - List contents = r.getDirectoryContent("", "gh-pages"); - for (GHContent content : contents) { - if (content.isFile()) { - String content1 = content.getContent(); - String content2 = r.getFileContent(content.getPath(), "gh-pages").getContent(); - // System.out.println(content.getPath()); - assertThat(content2, equalTo(content1)); - } - } + @Test + public void listCollaborators() throws Exception { + GHRepository repo = getRepository(); + List collaborators = repo.listCollaborators().toList(); + assertThat(collaborators.size(), greaterThan(10)); } /** - * Mark down. + * List collaborators filtered. * * @throws Exception * the exception */ @Test - public void markDown() throws Exception { - assertThat(IOUtils.toString(gitHub.renderMarkdown("**Testæ—ĨæœŦčĒž**")).trim(), - equalTo("

Testæ—ĨæœŦčĒž

")); - - String actual = IOUtils.toString( - gitHub.getRepository("hub4j/github-api").renderMarkdown("@kohsuke to fix issue #1", MarkdownMode.GFM)); - // System.out.println(actual); - assertThat(actual, containsString("href=\"https://github.com/kohsuke\"")); - assertThat(actual, containsString("href=\"https://github.com/hub4j/github-api/pull/1\"")); - assertThat(actual, containsString("class=\"user-mention\"")); - assertThat(actual, containsString("class=\"issue-link ")); - assertThat(actual, containsString("to fix issue")); + public void listCollaboratorsFiltered() throws Exception { + GHRepository repo = getRepository(); + List allCollaborators = repo.listCollaborators().toList(); + List filteredCollaborators = repo.listCollaborators(GHRepository.CollaboratorAffiliation.OUTSIDE) + .toList(); + assertThat(filteredCollaborators.size(), lessThan(allCollaborators.size())); } /** - * Sets the merge options. + * List commit comments no comments. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void setMergeOptions() throws IOException { - // String repoName = "hub4j-test-org/test-mergeoptions"; - GHRepository r = getTempRepository(); - - // at least one merge option must be selected - // flip all the values at least once - r.allowSquashMerge(true); - - r.allowMergeCommit(false); - r.allowRebaseMerge(false); + public void listCommitCommentsNoComments() throws IOException { + List commitComments = getRepository() + .listCommitComments("c413fc1e3057332b93850ea48202627d29a37de5") + .toList(); - r = gitHub.getRepository(r.getFullName()); - assertThat(r.isAllowMergeCommit(), is(false)); - assertThat(r.isAllowRebaseMerge(), is(false)); - assertThat(r.isAllowSquashMerge(), is(true)); + assertThat("Commit has no comments", commitComments.isEmpty()); - // flip the last value - r.allowMergeCommit(true); - r.allowRebaseMerge(true); - r.allowSquashMerge(false); + commitComments = getRepository().getCommit("c413fc1e3057332b93850ea48202627d29a37de5").listComments().toList(); - r = gitHub.getRepository(r.getFullName()); - assertThat(r.isAllowMergeCommit(), is(true)); - assertThat(r.isAllowRebaseMerge(), is(true)); - assertThat(r.isAllowSquashMerge(), is(false)); + assertThat("Commit has no comments", commitComments.isEmpty()); } /** - * Gets the delete branch on merge. + * List commit comments some comments. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void getDeleteBranchOnMerge() throws IOException { - GHRepository r = getRepository(); - assertThat(r.isDeleteBranchOnMerge(), notNullValue()); + public void listCommitCommentsSomeComments() throws IOException { + List commitComments = getRepository() + .listCommitComments("499d91f9f846b0087b2a20cf3648b49dc9c2eeef") + .toList(); + + assertThat("Two comments present", commitComments.size(), equalTo(2)); + assertThat("Comment text found", + commitComments.stream().map(GHCommitComment::getBody).collect(Collectors.toList()), + containsInAnyOrder("comment 1", "comment 2")); + + commitComments = getRepository().getCommit("499d91f9f846b0087b2a20cf3648b49dc9c2eeef").listComments().toList(); + + assertThat("Two comments present", commitComments.size(), equalTo(2)); + assertThat("Comment text found", + commitComments.stream().map(GHCommitComment::getBody).collect(Collectors.toList()), + containsInAnyOrder("comment 1", "comment 2")); } /** - * Sets the delete branch on merge. + * List commits between. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ @Test - public void setDeleteBranchOnMerge() throws IOException { - GHRepository r = getRepository(); - - // enable auto delete - r.deleteBranchOnMerge(true); - - r = gitHub.getRepository(r.getFullName()); - assertThat(r.isDeleteBranchOnMerge(), is(true)); - - // flip the last value - r.deleteBranchOnMerge(false); - - r = gitHub.getRepository(r.getFullName()); - assertThat(r.isDeleteBranchOnMerge(), is(false)); + public void listCommitsBetween() throws Exception { + GHRepository repository = getRepository(); + int startingCount = mockGitHub.getRequestCount(); + GHCompare compare = repository.getCompare("e46a9f3f2ac55db96de3c5c4706f2813b3a96465", + "8051615eff597f4e49f4f47625e6fc2b49f26bfc"); + int actualCount = 0; + for (GHCompare.Commit item : compare.listCommits().withPageSize(5)) { + assertThat(item, notNullValue()); + actualCount++; + } + assertThat(compare.getTotalCommits(), is(9)); + assertThat(actualCount, is(9)); + assertThat(mockGitHub.getRequestCount(), equalTo(startingCount + 1)); } /** - * Test set topics. + * List commits between paginated. * * @throws Exception * the exception */ @Test - public void testSetTopics() throws Exception { - GHRepository repo = getRepository(gitHub); - - List topics = new ArrayList<>(); - - topics.add("java"); - topics.add("api-test-dummy"); - repo.setTopics(topics); - assertThat("Topics retain input order (are not sort when stored)", - repo.listTopics(), - contains("java", "api-test-dummy")); - - topics = new ArrayList<>(); - topics.add("ordered-state"); - topics.add("api-test-dummy"); - topics.add("java"); - repo.setTopics(topics); - assertThat("Topics behave as a set and retain order from previous calls", - repo.listTopics(), - contains("java", "api-test-dummy", "ordered-state")); - - topics = new ArrayList<>(); - topics.add("ordered-state"); - topics.add("api-test-dummy"); - repo.setTopics(topics); - assertThat("Topics retain order even when some are removed", - repo.listTopics(), - contains("api-test-dummy", "ordered-state")); - - topics = new ArrayList<>(); - repo.setTopics(topics); - assertThat("Topics can be set to empty", repo.listTopics(), is(empty())); + public void listCommitsBetweenPaginated() throws Exception { + GHRepository repository = getRepository(); + int startingCount = mockGitHub.getRequestCount(); + repository.setCompareUsePaginatedCommits(true); + GHCompare compare = repository.getCompare("e46a9f3f2ac55db96de3c5c4706f2813b3a96465", + "8051615eff597f4e49f4f47625e6fc2b49f26bfc"); + int actualCount = 0; + for (GHCompare.Commit item : compare.listCommits().withPageSize(5)) { + assertThat(item, notNullValue()); + actualCount++; + } + assertThat(compare.getTotalCommits(), is(9)); + assertThat(actualCount, is(9)); + assertThat(mockGitHub.getRequestCount(), equalTo(startingCount + 3)); } /** - * Gets the collaborators. + * List contributors. * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ @Test - public void getCollaborators() throws Exception { - GHRepository repo = getRepository(gitHub); - GHPersonSet collaborators = repo.getCollaborators(); - assertThat(collaborators.size(), greaterThan(0)); + public void listContributors() throws IOException { + GHRepository r = gitHub.getOrganization("hub4j").getRepository("github-api"); + int i = 0; + boolean kohsuke = false; + + for (GHRepository.Contributor c : r.listContributors()) { + if (c.getLogin().equals("kohsuke")) { + assertThat(c.getContributions(), greaterThan(0)); + kohsuke = true; + } + if (i++ > 5) { + break; + } + } + + assertThat(kohsuke, is(true)); } /** - * Gets the post commit hooks. + * List contributors. * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ @Test - public void getPostCommitHooks() throws Exception { - GHRepository repo = getRepository(gitHub); - Set postcommitHooks = setupPostCommitHooks(repo); - assertThat(postcommitHooks, is(empty())); - } - - @SuppressFBWarnings(value = "DMI_COLLECTION_OF_URLS", - justification = "It causes a performance degradation, but we have already exposed it to the API") - private Set setupPostCommitHooks(final GHRepository repo) { - return new AbstractSet() { - private List getPostCommitHooks() { - try { - List r = new ArrayList<>(); - for (GHHook h : repo.getHooks()) { - if (h.getName().equals("web")) { - r.add(new URL(h.getConfig().get("url"))); - } - } - return r; - } catch (IOException e) { - throw new GHException("Failed to retrieve post-commit hooks", e); - } - } - - @Override - public Iterator iterator() { - return getPostCommitHooks().iterator(); - } + public void listContributorsAnon() throws IOException { + GHRepository r = gitHub.getOrganization("hub4j").getRepository("github-api"); + int i = 0; + boolean kohsuke = false; - @Override - public int size() { - return getPostCommitHooks().size(); + for (GHRepository.Contributor c : r.listContributors(true)) { + if (c.getType().equals("Anonymous")) { + assertThat(c.getContributions(), is(3)); + kohsuke = true; } - - @Override - public boolean add(URL url) { - try { - repo.createWebHook(url); - return true; - } catch (IOException e) { - throw new GHException("Failed to update post-commit hooks", e); - } + if (++i > 1) { + break; } + } - @Override - public boolean remove(Object url) { - try { - String _url = ((URL) url).toExternalForm(); - for (GHHook h : repo.getHooks()) { - if (h.getName().equals("web") && h.getConfig().get("url").equals(_url)) { - h.delete(); - return true; - } - } - return false; - } catch (IOException e) { - throw new GHException("Failed to update post-commit hooks", e); - } - } - }; + assertThat(kohsuke, is(true)); } /** - * Gets the refs. + * List empty contributors. * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ - @Test - public void getRefs() throws Exception { - GHRepository repo = getTempRepository(); - GHRef[] refs = repo.getRefs(); - assertThat(refs, notNullValue()); - assertThat(refs.length, equalTo(1)); - assertThat(refs[0].getRef(), equalTo("refs/heads/main")); + @Test // Issue #261 + public void listEmptyContributors() throws IOException { + assertThat("This list should be empty, but should return a valid empty iterable.", + gitHub.getRepository(GITHUB_API_TEST_ORG + "/empty").listContributors(), + is(emptyIterable())); } /** - * Gets the public key. + * List languages. * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ @Test - public void getPublicKey() throws Exception { - GHRepository repo = getTempRepository(); - GHRepositoryPublicKey publicKey = repo.getPublicKey(); - assertThat(publicKey, notNullValue()); - assertThat(publicKey.getKey(), equalTo("test-key")); - assertThat(publicKey.getKeyId(), equalTo("key-id")); + public void listLanguages() throws IOException { + GHRepository r = gitHub.getRepository("hub4j/github-api"); + String mainLanguage = r.getLanguage(); + assertThat(mainLanguage, equalTo("Java")); + Map languages = r.listLanguages(); + assertThat(languages.containsKey(mainLanguage), is(true)); + assertThat(languages.get("Java"), greaterThan(100000L)); } /** - * Gets the refs heads. + * List matching refs. * * @throws Exception * the exception */ @Test - public void getRefsHeads() throws Exception { - GHRepository repo = getTempRepository(); - GHRef[] refs = repo.getRefs("heads"); + public void listMatchingRefs() throws Exception { + GHRepository repo = getRepository(); + List refs; + + // Test listing refs matching a prefix + refs = repo.listMatchingRefs("heads").toList(); assertThat(refs, notNullValue()); - assertThat(refs.length, equalTo(1)); - assertThat(refs[0].getRef(), equalTo("refs/heads/main")); - } + assertThat(refs.size(), greaterThan(3)); + assertThat(refs.get(0).getRef(), equalTo("refs/heads/changes")); - /** - * Gets the refs empty tags. - * - * @throws Exception - * the exception - */ - @Test - public void getRefsEmptyTags() throws Exception { - GHRepository repo = getTempRepository(); - try { - repo.getRefs("tags"); - fail(); - } catch (Exception e) { - assertThat(e, instanceOf(GHFileNotFoundException.class)); - assertThat(e.getMessage(), - containsString( - "{\"message\":\"Not Found\",\"documentation_url\":\"https://developer.github.com/v3/git/refs/#get-a-reference\"}")); - } + // Test with refs/ prefix + List refsWithPrefix = repo.listMatchingRefs("refs/heads").toList(); + assertThat(refsWithPrefix.size(), equalTo(refs.size())); + assertThat(refsWithPrefix.get(0).getRef(), equalTo(refs.get(0).getRef())); + + // Test with a more specific prefix + refs = repo.listMatchingRefs("heads/gh").toList(); + assertThat(refs, notNullValue()); + assertThat(refs.size(), equalTo(1)); + assertThat(refs.get(0).getRef(), equalTo("refs/heads/gh-pages")); } /** @@ -1264,53 +1090,17 @@ public void listRefs() throws Exception { } /** - * Gets the ref. - * - * @throws Exception - * the exception + * List refs empty tags. */ @Test - public void getRef() throws Exception { - GHRepository repo = getRepository(); - - GHRef ghRef; - - // handle refs/* - ghRef = repo.getRef("heads/gh-pages"); - GHRef ghRefWithPrefix = repo.getRef("refs/heads/gh-pages"); - - assertThat(ghRef, notNullValue()); - assertThat(ghRef.getRef(), equalTo("refs/heads/gh-pages")); - assertThat(ghRefWithPrefix.getRef(), equalTo(ghRef.getRef())); - assertThat(ghRefWithPrefix.getObject().getType(), equalTo("commit")); - assertThat(ghRefWithPrefix.getObject().getUrl().toString(), - containsString("/repos/hub4j-test-org/github-api/git/commits/")); - - // git/refs/heads/gh-pages - ghRef = repo.getRef("heads/gh-pages"); - assertThat(ghRef, notNullValue()); - assertThat(ghRef.getRef(), equalTo("refs/heads/gh-pages")); - - // git/refs/heads/gh - try { - ghRef = repo.getRef("heads/gh"); - fail(); - } catch (Exception e) { - assertThat(e, instanceOf(GHFileNotFoundException.class)); - assertThat(e.getMessage(), - containsString( - "{\"message\":\"Not Found\",\"documentation_url\":\"https://developer.github.com/v3/git/refs/#get-a-reference\"}")); - } - - // git/refs/headz + public void listRefsEmptyTags() { try { - ghRef = repo.getRef("headz"); + GHRepository repo = getTempRepository(); + repo.listRefs("tags").toList(); fail(); } catch (Exception e) { assertThat(e, instanceOf(GHFileNotFoundException.class)); - assertThat(e.getMessage(), - containsString( - "{\"message\":\"Not Found\",\"documentation_url\":\"https://developer.github.com/v3/git/refs/#get-a-reference\"}")); + assertThat(e.getMessage(), containsString("/repos/hub4j-test-org/temp-listRefsEmptyTags/git/refs/tags")); } } @@ -1330,32 +1120,34 @@ public void listRefsHeads() throws Exception { } /** - * List refs empty tags. + * List releases. + * + * @throws IOException + * Signals that an I/O exception has occurred. */ @Test - public void listRefsEmptyTags() { - try { - GHRepository repo = getTempRepository(); - repo.listRefs("tags").toList(); - fail(); - } catch (Exception e) { - assertThat(e, instanceOf(GHFileNotFoundException.class)); - assertThat(e.getMessage(), containsString("/repos/hub4j-test-org/temp-listRefsEmptyTags/git/refs/tags")); - } + public void listReleases() throws IOException { + PagedIterable releases = gitHub.getOrganization("github").getRepository("hub").listReleases(); + assertThat(releases, is(not(emptyIterable()))); } /** - * List tags empty. + * List stargazers. * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ @Test - public void listTagsEmpty() throws Exception { - GHRepository repo = getTempRepository(); - List refs = repo.listTags().toList(); - assertThat(refs, notNullValue()); - assertThat(refs, is(empty())); + public void listStargazers() throws IOException { + GHRepository repository = getRepository(); + assertThat(repository.listStargazers().toList(), is(empty())); + + repository = gitHub.getOrganization("hub4j").getRepository("github-api"); + Iterable stargazers = repository.listStargazers2(); + GHStargazer stargazer = stargazers.iterator().next(); + assertThat(stargazer.getStarredAt(), equalTo(Instant.ofEpochMilli(1271650383000L))); + assertThat(stargazer.getUser().getLogin(), equalTo("nielswind")); + assertThat(stargazer.getRepository(), sameInstance(repository)); } /** @@ -1373,402 +1165,483 @@ public void listTags() throws Exception { } /** - * Check watchers count. + * List tags empty. * * @throws Exception * the exception */ @Test - public void checkWatchersCount() throws Exception { - snapshotNotAllowed(); + public void listTagsEmpty() throws Exception { GHRepository repo = getTempRepository(); - int watchersCount = repo.getWatchersCount(); - assertThat(watchersCount, equalTo(10)); + List refs = repo.listTags().toList(); + assertThat(refs, notNullValue()); + assertThat(refs, is(empty())); } /** - * Check stargazers count. + * Mark down. * * @throws Exception * the exception */ @Test - public void checkStargazersCount() throws Exception { - snapshotNotAllowed(); - GHRepository repo = getTempRepository(); - int stargazersCount = repo.getStargazersCount(); - assertThat(stargazersCount, equalTo(10)); + public void markDown() throws Exception { + assertThat(IOUtils.toString(gitHub.renderMarkdown("**Testæ—ĨæœŦčĒž**")).trim(), + equalTo("

Testæ—ĨæœŦčĒž

")); + + String actual = IOUtils.toString( + gitHub.getRepository("hub4j/github-api").renderMarkdown("@kohsuke to fix issue #1", MarkdownMode.GFM)); + // System.out.println(actual); + assertThat(actual, containsString("href=\"https://github.com/kohsuke\"")); + assertThat(actual, containsString("href=\"https://github.com/hub4j/github-api/pull/1\"")); + assertThat(actual, containsString("class=\"user-mention\"")); + assertThat(actual, containsString("class=\"issue-link ")); + assertThat(actual, containsString("to fix issue")); } /** - * List collaborators. + * Search all public and forked repos. * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ @Test - public void listCollaborators() throws Exception { - GHRepository repo = getRepository(); - List collaborators = repo.listCollaborators().toList(); - assertThat(collaborators.size(), greaterThan(10)); + public void searchAllPublicAndForkedRepos() throws IOException { + PagedSearchIterable list = gitHub.searchRepositories() + .user("t0m4uk1991") + .visibility(GHRepository.Visibility.PUBLIC) + .fork(GHFork.PARENT_AND_FORKS) + .list(); + List u = list.toList(); + assertThat(u.size(), is(14)); + assertThat(u.stream().filter(item -> item.getName().equals("github-api")).count(), is(1L)); + assertThat(u.stream().filter(item -> item.getName().equals("Complete-Python-3-Bootcamp")).count(), is(1L)); } /** - * List collaborators filtered. + * Search for public forked only repos. * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ @Test - public void listCollaboratorsFiltered() throws Exception { - GHRepository repo = getRepository(); - List allCollaborators = repo.listCollaborators().toList(); - List filteredCollaborators = repo.listCollaborators(GHRepository.CollaboratorAffiliation.OUTSIDE) - .toList(); - assertThat(filteredCollaborators.size(), lessThan(allCollaborators.size())); + public void searchForPublicForkedOnlyRepos() throws IOException { + PagedSearchIterable list = gitHub.searchRepositories() + .user("t0m4uk1991") + .visibility(GHRepository.Visibility.PUBLIC) + .fork(GHFork.FORKS_ONLY) + .list(); + List u = list.toList(); + assertThat(u.size(), is(2)); + assertThat(u.get(0).getName(), is("github-api")); + assertThat(u.get(1).getName(), is("Complete-Python-3-Bootcamp")); } /** - * User is collaborator. - * - * @throws Exception - * the exception + * Search org for repositories. */ @Test - public void userIsCollaborator() throws Exception { - GHRepository repo = getRepository(); - GHUser collaborator = repo.listCollaborators().toList().get(0); - assertThat(repo.isCollaborator(collaborator), is(true)); + public void searchOrgForRepositories() { + PagedSearchIterable r = gitHub.searchRepositories().org("hub4j-test-org").list(); + GHRepository u = r.iterator().next(); + assertThat(u.getOwnerName(), equalTo("hub4j-test-org")); + assertThat(r.getTotalCount(), greaterThan(0)); } /** - * Gets the check runs. - * - * @throws Exception - * the exception + * Search repositories. */ @Test - public void getCheckRuns() throws Exception { - final int expectedCount = 8; - // Use github-api repository as it has checks set up - PagedIterable checkRuns = gitHub.getOrganization("hub4j") - .getRepository("github-api") - .getCheckRuns("78b9ff49d47daaa158eb373c4e2e040f739df8b9"); - // Check if the paging works correctly - assertThat(checkRuns.withPageSize(2).iterator().nextPage(), hasSize(2)); - - // Check if the checkruns are all succeeded and if we got all of them - int checkRunsCount = 0; - for (GHCheckRun checkRun : checkRuns) { - assertThat(checkRun.getConclusion(), equalTo(Conclusion.SUCCESS)); - checkRunsCount++; - } - assertThat(checkRunsCount, equalTo(expectedCount)); - - // Check that we can call update on the results - for (GHCheckRun checkRun : checkRuns) { - checkRun.update(); - } + public void searchRepositories() { + PagedSearchIterable r = gitHub.searchRepositories() + .q("tetris") + .language("assembly") + .sort(GHRepositorySearchBuilder.Sort.STARS) + .list(); + GHRepository u = r.iterator().next(); + // System.out.println(u.getName()); + assertThat(u.getId(), notNullValue()); + assertThat(u.getLanguage(), equalTo("Assembly")); + assertThat(r.getTotalCount(), greaterThan(0)); } /** - * Filter out the checks from a reference + * Sets the delete branch on merge. * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ @Test - public void getCheckRunsWithParams() throws Exception { - final int expectedCount = 1; - // Use github-api repository as it has checks set up - final Map params = new HashMap<>(1); - params.put("check_name", "build-only (Java 17)"); - PagedIterable checkRuns = gitHub.getOrganization("hub4j") - .getRepository("github-api") - .getCheckRuns("54d60fbb53b4efa19f3081417bfb6a1de30c55e4", params); + public void setDeleteBranchOnMerge() throws IOException { + GHRepository r = getRepository(); - // Check if the checkruns are all succeeded and if we got all of them - int checkRunsCount = 0; - for (GHCheckRun checkRun : checkRuns) { - assertThat(checkRun.getConclusion(), equalTo(Conclusion.SUCCESS)); - checkRunsCount++; - } - assertThat(checkRunsCount, equalTo(expectedCount)); + // enable auto delete + r.deleteBranchOnMerge(true); + + r = gitHub.getRepository(r.getFullName()); + assertThat(r.isDeleteBranchOnMerge(), is(true)); + + // flip the last value + r.deleteBranchOnMerge(false); + + r = gitHub.getRepository(r.getFullName()); + assertThat(r.isDeleteBranchOnMerge(), is(false)); } /** - * Gets the last commit status. + * Sets the merge options. * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ @Test - public void getLastCommitStatus() throws Exception { - GHCommitStatus status = getRepository().getLastCommitStatus("8051615eff597f4e49f4f47625e6fc2b49f26bfc"); - assertThat(status.getId(), equalTo(9027542286L)); - assertThat(status.getState(), equalTo(GHCommitState.SUCCESS)); - assertThat(status.getContext(), equalTo("ci/circleci: build")); + public void setMergeOptions() throws IOException { + // String repoName = "hub4j-test-org/test-mergeoptions"; + GHRepository r = getTempRepository(); + + // at least one merge option must be selected + // flip all the values at least once + r.allowSquashMerge(true); + + r.allowMergeCommit(false); + r.allowRebaseMerge(false); + + r = gitHub.getRepository(r.getFullName()); + assertThat(r.isAllowMergeCommit(), is(false)); + assertThat(r.isAllowRebaseMerge(), is(false)); + assertThat(r.isAllowSquashMerge(), is(true)); + + // flip the last value + r.allowMergeCommit(true); + r.allowRebaseMerge(true); + r.allowSquashMerge(false); + + r = gitHub.getRepository(r.getFullName()); + assertThat(r.isAllowMergeCommit(), is(true)); + assertThat(r.isAllowRebaseMerge(), is(true)); + assertThat(r.isAllowSquashMerge(), is(false)); } /** - * List commits between. + * Test to check star method by verifying stargarzer count. * * @throws Exception * the exception */ @Test - public void listCommitsBetween() throws Exception { + public void starTest() throws Exception { + String owner = "hub4j-test-org"; GHRepository repository = getRepository(); - int startingCount = mockGitHub.getRequestCount(); - GHCompare compare = repository.getCompare("e46a9f3f2ac55db96de3c5c4706f2813b3a96465", - "8051615eff597f4e49f4f47625e6fc2b49f26bfc"); - int actualCount = 0; - for (GHCompare.Commit item : compare.listCommits().withPageSize(5)) { - assertThat(item, notNullValue()); - actualCount++; - } - assertThat(compare.getTotalCommits(), is(9)); - assertThat(actualCount, is(9)); - assertThat(mockGitHub.getRequestCount(), equalTo(startingCount + 1)); + assertThat(repository.getOwner().getLogin(), equalTo(owner)); + assertThat(repository.getStargazersCount(), is(1)); + repository.star(); + assertThat(repository.listStargazers().toList().size(), is(2)); + repository.unstar(); + assertThat(repository.listStargazers().toList().size(), is(1)); } /** - * List commits between paginated. + * Subscription. * * @throws Exception * the exception */ @Test - public void listCommitsBetweenPaginated() throws Exception { - GHRepository repository = getRepository(); - int startingCount = mockGitHub.getRequestCount(); - repository.setCompareUsePaginatedCommits(true); - GHCompare compare = repository.getCompare("e46a9f3f2ac55db96de3c5c4706f2813b3a96465", - "8051615eff597f4e49f4f47625e6fc2b49f26bfc"); - int actualCount = 0; - for (GHCompare.Commit item : compare.listCommits().withPageSize(5)) { - assertThat(item, notNullValue()); - actualCount++; - } - assertThat(compare.getTotalCommits(), is(9)); - assertThat(actualCount, is(9)); - assertThat(mockGitHub.getRequestCount(), equalTo(startingCount + 3)); - } - - /** - * Gets the commits between over 250. - * - * @throws Exception - * the exception - */ - @Test - public void getCommitsBetweenOver250() throws Exception { - GHRepository repository = getRepository(); - int startingCount = mockGitHub.getRequestCount(); - GHCompare compare = repository.getCompare("4261c42949915816a9f246eb14c3dfd21a637bc2", - "94ff089e60064bfa43e374baeb10846f7ce82f40"); - int actualCount = 0; - for (GHCompare.Commit item : compare.getCommits()) { - assertThat(item, notNullValue()); - actualCount++; - } - assertThat(compare.getTotalCommits(), is(283)); - assertThat(actualCount, is(250)); - assertThat(mockGitHub.getRequestCount(), equalTo(startingCount + 1)); - - // Additional GHCompare checks - assertThat(compare.getAheadBy(), equalTo(283)); - assertThat(compare.getBehindBy(), equalTo(0)); - assertThat(compare.getStatus(), equalTo(GHCompare.Status.ahead)); - assertThat(compare.getDiffUrl().toString(), - endsWith( - "compare/4261c42949915816a9f246eb14c3dfd21a637bc2...94ff089e60064bfa43e374baeb10846f7ce82f40.diff")); - assertThat(compare.getHtmlUrl().toString(), - endsWith( - "compare/4261c42949915816a9f246eb14c3dfd21a637bc2...94ff089e60064bfa43e374baeb10846f7ce82f40")); - assertThat(compare.getPatchUrl().toString(), - endsWith( - "compare/4261c42949915816a9f246eb14c3dfd21a637bc2...94ff089e60064bfa43e374baeb10846f7ce82f40.patch")); - assertThat(compare.getPermalinkUrl().toString(), - endsWith("compare/hub4j-test-org:4261c42...hub4j-test-org:94ff089")); - assertThat(compare.getUrl().toString(), - endsWith( - "compare/4261c42949915816a9f246eb14c3dfd21a637bc2...94ff089e60064bfa43e374baeb10846f7ce82f40")); - - assertThat(compare.getBaseCommit().getSHA1(), equalTo("4261c42949915816a9f246eb14c3dfd21a637bc2")); - - assertThat(compare.getMergeBaseCommit().getSHA1(), equalTo("4261c42949915816a9f246eb14c3dfd21a637bc2")); - // it appears this field is not present in the returned JSON. Strange. - assertThat(compare.getMergeBaseCommit().getCommit().getSha(), nullValue()); - assertThat(compare.getMergeBaseCommit().getCommit().getUrl(), - endsWith("/commits/4261c42949915816a9f246eb14c3dfd21a637bc2")); - assertThat(compare.getMergeBaseCommit().getCommit().getMessage(), - endsWith("[maven-release-plugin] prepare release github-api-1.123")); - assertThat(compare.getMergeBaseCommit().getCommit().getAuthor().getName(), equalTo("Liam Newman")); - assertThat(compare.getMergeBaseCommit().getCommit().getCommitter().getName(), equalTo("Liam Newman")); + public void subscription() throws Exception { + GHRepository r = getRepository(); + assertThat(r.getSubscription(), nullValue()); + GHSubscription s = r.subscribe(true, false); + try { - assertThat(compare.getMergeBaseCommit().getCommit().getTree().getSha(), - equalTo("5da98090976978c93aba0bdfa550e05675543f99")); - assertThat(compare.getMergeBaseCommit().getCommit().getTree().getUrl(), - endsWith("/git/trees/5da98090976978c93aba0bdfa550e05675543f99")); + assertThat(r, equalTo(s.getRepository())); + assertThat(s.isIgnored(), equalTo(false)); + assertThat(s.isSubscribed(), equalTo(true)); + assertThat(s.getRepositoryUrl().toString(), containsString("/repos/hub4j-test-org/github-api")); + assertThat(s.getUrl().toString(), containsString("/repos/hub4j-test-org/github-api/subscription")); - assertThat(compare.getFiles().length, equalTo(300)); - assertThat(compare.getFiles()[0].getFileName(), equalTo(".github/PULL_REQUEST_TEMPLATE.md")); - assertThat(compare.getFiles()[0].getLinesAdded(), equalTo(8)); - assertThat(compare.getFiles()[0].getLinesChanged(), equalTo(15)); - assertThat(compare.getFiles()[0].getLinesDeleted(), equalTo(7)); - assertThat(compare.getFiles()[0].getFileName(), equalTo(".github/PULL_REQUEST_TEMPLATE.md")); - assertThat(compare.getFiles()[0].getPatch(), startsWith("@@ -1,15 +1,16 @@")); - assertThat(compare.getFiles()[0].getPreviousFilename(), nullValue()); - assertThat(compare.getFiles()[0].getStatus(), equalTo("modified")); - assertThat(compare.getFiles()[0].getSha(), equalTo("e4234f5f6f39899282a6ef1edff343ae1269222e")); + assertThat(s.getReason(), nullValue()); + assertThat(s.getCreatedAt(), equalTo(Instant.ofEpochMilli(1611377286000L))); + } finally { + s.delete(); + } - assertThat(compare.getFiles()[0].getBlobUrl().toString(), - endsWith("/blob/94ff089e60064bfa43e374baeb10846f7ce82f40/.github/PULL_REQUEST_TEMPLATE.md")); - assertThat(compare.getFiles()[0].getRawUrl().toString(), - endsWith("/raw/94ff089e60064bfa43e374baeb10846f7ce82f40/.github/PULL_REQUEST_TEMPLATE.md")); + assertThat(r.getSubscription(), nullValue()); } /** - * Gets the commits between paged. + * Test sync of fork * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ @Test - public void getCommitsBetweenPaged() throws Exception { - GHRepository repository = getRepository(); - int startingCount = mockGitHub.getRequestCount(); - repository.setCompareUsePaginatedCommits(true); - GHCompare compare = repository.getCompare("4261c42949915816a9f246eb14c3dfd21a637bc2", - "94ff089e60064bfa43e374baeb10846f7ce82f40"); - int actualCount = 0; - for (GHCompare.Commit item : compare.getCommits()) { - assertThat(item, notNullValue()); - actualCount++; - } - assertThat(compare.getTotalCommits(), is(283)); - assertThat(actualCount, is(283)); - assertThat(mockGitHub.getRequestCount(), equalTo(startingCount + 4)); + public void sync() throws IOException { + GHRepository r = getRepository(); + assertThat(r.getForksCount(), equalTo(0)); + GHBranchSync sync = r.sync("main"); + assertThat(sync.getOwner().getFullName(), equalTo("hub4j-test-org/github-api")); + assertThat(sync.getMessage(), equalTo("Successfully fetched and fast-forwarded from upstream github-api:main")); + assertThat(sync.getMergeType(), equalTo("fast-forward")); + assertThat(sync.getBaseBranch(), equalTo("github-api:main")); } /** - * Creates the dispatch event without client payload. + * Test sync of repository not a fork * - * @throws Exception - * the exception + * @throws IOException + * Signals that an I/O exception has occurred. */ - @Test - public void createDispatchEventWithoutClientPayload() throws Exception { - GHRepository repository = getTempRepository(); - repository.dispatch("test", null); + @Test(expected = HttpException.class) + public void syncNoFork() throws IOException { + GHRepository r = getRepository(); + GHBranchSync sync = r.sync("main"); + fail("Should have thrown an exception"); + } /** - * Creates the dispatch event with client payload. + * Test create repo action variable. * - * @throws Exception + * @throws IOException * the exception */ @Test - public void createDispatchEventWithClientPayload() throws Exception { - GHRepository repository = getTempRepository(); - Map clientPayload = new HashMap<>(); - clientPayload.put("name", "joe.doe"); - clientPayload.put("list", new ArrayList<>()); - repository.dispatch("test", clientPayload); + public void testCreateRepoActionVariable() throws IOException { + GHRepository repository = getRepository(); + repository.createVariable("MYNEWVARIABLE", "mynewvalue"); + GHRepositoryVariable variable = repository.getVariable("mynewvariable"); + assertThat(variable.getName(), is("MYNEWVARIABLE")); + assertThat(variable.getValue(), is("mynewvalue")); } /** - * Creates the secret. + * Tests the creation of repositories with alternating visibilities for orgs. * * @throws Exception * the exception */ @Test - public void createSecret() throws Exception { - GHRepository repo = getTempRepository(); - repo.createSecret("secret", "encrypted", "public"); + public void testCreateVisibilityForOrganization() throws Exception { + GHOrganization organization = gitHub.getOrganization(GITHUB_API_TEST_ORG); + + // can not test for internal, as test org is not assigned to an enterprise + for (Visibility visibility : Sets.newHashSet(Visibility.PUBLIC, Visibility.PRIVATE)) { + String repoName = String.format("test-repo-visibility-%s", visibility.toString()); + GHRepository repository = organization.createRepository(repoName).visibility(visibility).create(); + try { + assertThat(repository.getVisibility(), is(visibility)); + assertThat(organization.getRepository(repoName).getVisibility(), is(visibility)); + } finally { + repository.delete(); + } + } } /** - * Test to check star method by verifying stargarzer count. + * Tests the creation of repositories with alternating visibilities for users. * * @throws Exception * the exception */ @Test - public void starTest() throws Exception { - String owner = "hub4j-test-org"; - GHRepository repository = getRepository(); - assertThat(repository.getOwner().getLogin(), equalTo(owner)); - assertThat(repository.getStargazersCount(), is(1)); - repository.star(); - assertThat(repository.listStargazers().toList().size(), is(2)); - repository.unstar(); - assertThat(repository.listStargazers().toList().size(), is(1)); + public void testCreateVisibilityForUser() throws Exception { + + GHUser myself = gitHub.getMyself(); + + // can not test for internal, as test org is not assigned to an enterprise + for (Visibility visibility : Sets.newHashSet(Visibility.PUBLIC, Visibility.PRIVATE)) { + String repoName = String.format("test-repo-visibility-%s", visibility.toString()); + boolean isPrivate = visibility.equals(Visibility.PRIVATE); + GHRepository repository = gitHub.createRepository(repoName) + .private_(isPrivate) + .visibility(visibility) + .create(); + try { + assertThat(repository.getVisibility(), is(visibility)); + assertThat(myself.getRepository(repoName).getVisibility(), is(visibility)); + } finally { + repository.delete(); + } + } } /** - * Test create repo action variable. + * Test delete repo action variable. * * @throws IOException * the exception */ @Test - public void testCreateRepoActionVariable() throws IOException { + public void testDeleteRepoActionVariable() throws IOException { GHRepository repository = getRepository(); - repository.createVariable("MYNEWVARIABLE", "mynewvalue"); GHRepositoryVariable variable = repository.getVariable("mynewvariable"); - assertThat(variable.getName(), is("MYNEWVARIABLE")); - assertThat(variable.getValue(), is("mynewvalue")); + variable.delete(); + Assert.assertThrows(GHFileNotFoundException.class, () -> repository.getVariable("mynewvariable")); } /** - * Test update repo action variable. + * Test get repository with visibility. * * @throws IOException - * the exception + * Signals that an I/O exception has occurred. */ @Test - public void testUpdateRepoActionVariable() throws IOException { - GHRepository repository = getRepository(); - GHRepositoryVariable variable = repository.getVariable("MYNEWVARIABLE"); - variable.set().value("myupdatevalue"); - variable = repository.getVariable("MYNEWVARIABLE"); - assertThat(variable.getValue(), is("myupdatevalue")); + public void testGetRepositoryWithVisibility() throws IOException { + snapshotNotAllowed(); + final String repoName = "test-repo-visibility"; + final GHRepository repo = getTempRepository(repoName); + assertThat(repo.getVisibility(), equalTo(Visibility.PUBLIC)); + + repo.setVisibility(Visibility.INTERNAL); + assertThat(gitHub.getRepository(repo.getOwnerName() + "/" + repo.getName()).getVisibility(), + equalTo(Visibility.INTERNAL)); + + repo.setVisibility(Visibility.PRIVATE); + assertThat(gitHub.getRepository(repo.getOwnerName() + "/" + repo.getName()).getVisibility(), + equalTo(Visibility.PRIVATE)); + + repo.setVisibility(Visibility.PUBLIC); + assertThat(gitHub.getRepository(repo.getOwnerName() + "/" + repo.getName()).getVisibility(), + equalTo(Visibility.PUBLIC)); + + // deliberately bogus response in snapshot + assertThat(gitHub.getRepository(repo.getOwnerName() + "/" + repo.getName()).getVisibility(), + equalTo(Visibility.UNKNOWN)); } /** - * Test delete repo action variable. + * Test getRulesForBranch. * - * @throws IOException + * @throws Exception * the exception */ @Test - public void testDeleteRepoActionVariable() throws IOException { - GHRepository repository = getRepository(); - GHRepositoryVariable variable = repository.getVariable("mynewvariable"); - variable.delete(); - Assert.assertThrows(GHFileNotFoundException.class, () -> repository.getVariable("mynewvariable")); - } + public void testGetRulesForBranch() throws Exception { + GHRepository repository = gitHub.getRepository("ihrigb/node-doorbird"); + List rules = repository.listRulesForBranch("main").toList(); + assertThat(rules.size(), equalTo(3)); - /** - * Test demoing the issue with a user having the maintain permission on a repository. - * - * Test checking the permission fallback mechanism in case the Github API changes. The test was recorded at a time a - * new permission was added by mistake. If a re-recording it is needed, you'll like have to manually edit the - * generated mocks to get a non existing permission See - * https://github.com/hub4j/github-api/issues/1671#issuecomment-1577515662 for the details. + GHRepositoryRule rule = rules.get(0); + assertThat(rule.getType(), is(equalTo(GHRepositoryRule.Type.DELETION))); + assertThat(rule.getRulesetSourceType(), is(equalTo(GHRepositoryRule.RulesetSourceType.REPOSITORY))); + assertThat(rule.getRulesetSource(), is(equalTo("ihrigb/node-doorbird"))); + assertThat(rule.getRulesetId(), is(equalTo(1170520L))); + + rule = rules.get(1); + assertThat(rule.getType(), is(equalTo(GHRepositoryRule.Type.NON_FAST_FORWARD))); + assertThat(rule.getRulesetSourceType(), is(equalTo(GHRepositoryRule.RulesetSourceType.REPOSITORY))); + assertThat(rule.getRulesetSource(), is(equalTo("ihrigb/node-doorbird"))); + assertThat(rule.getRulesetId(), is(equalTo(1170520L))); + + rule = rules.get(2); + assertThat(rule.getType(), is(equalTo(GHRepositoryRule.Type.PULL_REQUEST))); + assertThat(rule.getRulesetSourceType(), is(equalTo(GHRepositoryRule.RulesetSourceType.REPOSITORY))); + assertThat(rule.getRulesetSource(), is(equalTo("ihrigb/node-doorbird"))); + assertThat(rule.getRulesetId(), is(equalTo(1170520L))); + + // check parameters + assertThat(rule.getParameter(GHRepositoryRule.Parameters.NEGATE).isPresent(), is(equalTo(false))); + assertThat(rule.getParameter(GHRepositoryRule.Parameters.REQUIRED_APPROVING_REVIEW_COUNT).get(), + is(equalTo(1))); + assertThat(rule.getParameter(GHRepositoryRule.Parameters.DISMISS_STALE_REVIEWS_ON_PUSH).get(), + is(equalTo(true))); + assertThat(rule.getParameter(GHRepositoryRule.Parameters.REQUIRE_CODE_OWNER_REVIEW).get(), is(equalTo(false))); + } + + /** + * Test getTopReferralPaths. + * + * @throws Exception + * the exception + */ + @Test + public void testGetTopReferralPaths() throws Exception { + GHRepository repository = gitHub.getRepository("ihrigb/node-doorbird"); + List referralPaths = repository.getTopReferralPaths(); + assertThat(referralPaths.size(), greaterThan(0)); + } + + /** + * Test getTopReferralSources. + * + * @throws Exception + * the exception + */ + @Test + public void testGetTopReferralSources() throws Exception { + GHRepository repository = gitHub.getRepository("ihrigb/node-doorbird"); + List referralSources = repository.getTopReferralSources(); + assertThat(referralSources.size(), greaterThan(0)); + } + + /** + * Test getters. * * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testGetters() throws IOException { + GHRepository r = getTempRepository(); + + assertThat(r.hasAdminAccess(), is(true)); + assertThat(r.hasDownloads(), is(true)); + assertThat(r.hasIssues(), is(true)); + assertThat(r.hasPages(), is(false)); + assertThat(r.hasProjects(), is(true)); + assertThat(r.hasPullAccess(), is(true)); + assertThat(r.hasPushAccess(), is(true)); + assertThat(r.hasWiki(), is(true)); + + assertThat(r.isAllowMergeCommit(), is(true)); + assertThat(r.isAllowRebaseMerge(), is(true)); + assertThat(r.isAllowSquashMerge(), is(true)); + assertThat(r.isAllowForking(), is(false)); + + String httpTransport = "https://github.com/hub4j-test-org/temp-testGetters.git"; + assertThat(r.getHttpTransportUrl(), equalTo(httpTransport)); + assertThat(r.getGitTransportUrl(), equalTo("git://github.com/hub4j-test-org/temp-testGetters.git")); + assertThat(r.getSvnUrl(), equalTo("https://github.com/hub4j-test-org/temp-testGetters")); + assertThat(r.getMirrorUrl(), nullValue()); + assertThat(r.getSshUrl(), equalTo("git@github.com:hub4j-test-org/temp-testGetters.git")); + assertThat(r.getHtmlUrl().toString(), equalTo("https://github.com/hub4j-test-org/temp-testGetters")); + assertThat(r.getOpenIssueCount(), equalTo(0)); + assertThat(r.getSubscribersCount(), equalTo(7)); + + assertThat(r.getName(), equalTo("temp-testGetters")); + assertThat(r.getFullName(), equalTo("hub4j-test-org/temp-testGetters")); + } + + /** + * Test getVulnerabilityAlerts. + * + * @throws Exception * the exception */ @Test - public void cannotRetrievePermissionMaintainUser() throws IOException { - GHRepository r = gitHub.getRepository("hub4j-test-org/maintain-permission-issue"); - GHPermissionType permission = r.getPermission("alecharp"); - assertThat(permission.toString(), is("UNKNOWN")); + public void testIsVulnerabilityAlertsEnabled() throws Exception { + GHRepository repository = gitHub.getRepository("ihrigb/node-doorbird"); + assertThat(repository.isVulnerabilityAlertsEnabled(), is(true)); + } + + /** + * Test issue 162. + * + * @throws Exception + * the exception + */ + @Test // issue #162 + public void testIssue162() throws Exception { + GHRepository r = gitHub.getRepository("hub4j/github-api"); + List contents = r.getDirectoryContent("", "gh-pages"); + for (GHContent content : contents) { + if (content.isFile()) { + String content1 = content.getContent(); + String content2 = r.getFileContent(content.getPath(), "gh-pages").getContent(); + // System.out.println(content.getPath()); + assertThat(content2, equalTo(content1)); + } + } } /** @@ -1885,90 +1758,241 @@ public void testSearchPullRequests() throws Exception { } /** - * Test getTopReferralPaths. + * Test set public. * * @throws Exception * the exception */ @Test - public void testGetTopReferralPaths() throws Exception { - GHRepository repository = gitHub.getRepository("ihrigb/node-doorbird"); - List referralPaths = repository.getTopReferralPaths(); - assertThat(referralPaths.size(), greaterThan(0)); + public void testSetPublic() throws Exception { + kohsuke(); + GHUser myself = gitHub.getMyself(); + String repoName = "test-repo-public"; + GHRepository repo = gitHub.createRepository(repoName).private_(false).create(); + try { + assertThat(repo.isPrivate(), is(false)); + repo.setPrivate(true); + assertThat(myself.getRepository(repoName).isPrivate(), is(true)); + repo.setPrivate(false); + assertThat(myself.getRepository(repoName).isPrivate(), is(false)); + } finally { + repo.delete(); + } } /** - * Test getTopReferralSources. + * Test set topics. * * @throws Exception * the exception */ @Test - public void testGetTopReferralSources() throws Exception { - GHRepository repository = gitHub.getRepository("ihrigb/node-doorbird"); - List referralSources = repository.getTopReferralSources(); - assertThat(referralSources.size(), greaterThan(0)); + public void testSetTopics() throws Exception { + GHRepository repo = getRepository(gitHub); + + List topics = new ArrayList<>(); + + topics.add("java"); + topics.add("api-test-dummy"); + repo.setTopics(topics); + assertThat("Topics retain input order (are not sort when stored)", + repo.listTopics(), + contains("java", "api-test-dummy")); + + topics = new ArrayList<>(); + topics.add("ordered-state"); + topics.add("api-test-dummy"); + topics.add("java"); + repo.setTopics(topics); + assertThat("Topics behave as a set and retain order from previous calls", + repo.listTopics(), + contains("java", "api-test-dummy", "ordered-state")); + + topics = new ArrayList<>(); + topics.add("ordered-state"); + topics.add("api-test-dummy"); + repo.setTopics(topics); + assertThat("Topics retain order even when some are removed", + repo.listTopics(), + contains("api-test-dummy", "ordered-state")); + + topics = new ArrayList<>(); + repo.setTopics(topics); + assertThat("Topics can be set to empty", repo.listTopics(), is(empty())); } /** - * Test getRulesForBranch. + * Test tarball. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testTarball() throws IOException { + getTempRepository().readTar((InputStream inputstream) -> { + return new ByteArrayInputStream(IOUtils.toByteArray(inputstream)); + }, null); + } + + /** + * Test update repo action variable. + * + * @throws IOException + * the exception + */ + @Test + public void testUpdateRepoActionVariable() throws IOException { + GHRepository repository = getRepository(); + GHRepositoryVariable variable = repository.getVariable("MYNEWVARIABLE"); + variable.set().value("myupdatevalue"); + variable = repository.getVariable("MYNEWVARIABLE"); + assertThat(variable.getValue(), is("myupdatevalue")); + } + + /** + * Test update repository. * * @throws Exception * the exception */ @Test - public void testGetRulesForBranch() throws Exception { - GHRepository repository = gitHub.getRepository("ihrigb/node-doorbird"); - List rules = repository.listRulesForBranch("main").toList(); - assertThat(rules.size(), equalTo(3)); + public void testUpdateRepository() throws Exception { + String homepage = "https://github-api.kohsuke.org/apidocs/index.html"; + String description = "A test repository for update testing via the github-api project"; - GHRepositoryRule rule = rules.get(0); - assertThat(rule.getType(), is(equalTo(GHRepositoryRule.Type.DELETION))); - assertThat(rule.getRulesetSourceType(), is(equalTo(GHRepositoryRule.RulesetSourceType.REPOSITORY))); - assertThat(rule.getRulesetSource(), is(equalTo("ihrigb/node-doorbird"))); - assertThat(rule.getRulesetId(), is(equalTo(1170520L))); + GHRepository repo = getTempRepository(); + GHRepository.Updater builder = repo.update(); - rule = rules.get(1); - assertThat(rule.getType(), is(equalTo(GHRepositoryRule.Type.NON_FAST_FORWARD))); - assertThat(rule.getRulesetSourceType(), is(equalTo(GHRepositoryRule.RulesetSourceType.REPOSITORY))); - assertThat(rule.getRulesetSource(), is(equalTo("ihrigb/node-doorbird"))); - assertThat(rule.getRulesetId(), is(equalTo(1170520L))); + // one merge option is always required + GHRepository updated = builder.allowRebaseMerge(false) + .allowSquashMerge(false) + .deleteBranchOnMerge(true) + .allowForking(true) + .description(description) + .downloads(false) + .downloads(false) + .homepage(homepage) + .issues(false) + .private_(true) + .projects(false) + .wiki(false) + .done(); - rule = rules.get(2); - assertThat(rule.getType(), is(equalTo(GHRepositoryRule.Type.PULL_REQUEST))); - assertThat(rule.getRulesetSourceType(), is(equalTo(GHRepositoryRule.RulesetSourceType.REPOSITORY))); - assertThat(rule.getRulesetSource(), is(equalTo("ihrigb/node-doorbird"))); - assertThat(rule.getRulesetId(), is(equalTo(1170520L))); + assertThat(updated.isAllowMergeCommit(), is(true)); + assertThat(updated.isAllowRebaseMerge(), is(false)); + assertThat(updated.isAllowSquashMerge(), is(false)); + assertThat(updated.isDeleteBranchOnMerge(), is(true)); + assertThat(updated.isAllowForking(), is(true)); + assertThat(updated.isPrivate(), is(true)); + assertThat(updated.hasDownloads(), is(false)); + assertThat(updated.hasIssues(), is(false)); + assertThat(updated.hasProjects(), is(false)); + assertThat(updated.hasWiki(), is(false)); - // check parameters - assertThat(rule.getParameter(GHRepositoryRule.Parameters.NEGATE).isPresent(), is(equalTo(false))); - assertThat(rule.getParameter(GHRepositoryRule.Parameters.REQUIRED_APPROVING_REVIEW_COUNT).get(), - is(equalTo(1))); - assertThat(rule.getParameter(GHRepositoryRule.Parameters.DISMISS_STALE_REVIEWS_ON_PUSH).get(), - is(equalTo(true))); - assertThat(rule.getParameter(GHRepositoryRule.Parameters.REQUIRE_CODE_OWNER_REVIEW).get(), is(equalTo(false))); + assertThat(updated.getHomepage(), equalTo(homepage)); + assertThat(updated.getDescription(), equalTo(description)); + + // test the other merge option and making the repo public again + GHRepository redux = updated.update().allowMergeCommit(false).allowRebaseMerge(true).private_(false).done(); + + assertThat(redux.isAllowMergeCommit(), is(false)); + assertThat(redux.isAllowRebaseMerge(), is(true)); + assertThat(redux.isPrivate(), is(false)); + + String updatedDescription = "updated using set()"; + redux = redux.set().description(updatedDescription); + + assertThat(redux.getDescription(), equalTo(updatedDescription)); } /** - * Test getVulnerabilityAlerts. + * Test zipball. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testZipball() throws IOException { + getTempRepository().readZip((InputStream inputstream) -> { + return new ByteArrayInputStream(IOUtils.toByteArray(inputstream)); + }, null); + } + + /** + * User is collaborator. * * @throws Exception * the exception */ @Test - public void testIsVulnerabilityAlertsEnabled() throws Exception { - GHRepository repository = gitHub.getRepository("ihrigb/node-doorbird"); - assertThat(repository.isVulnerabilityAlertsEnabled(), is(true)); + public void userIsCollaborator() throws Exception { + GHRepository repo = getRepository(); + GHUser collaborator = repo.listCollaborators().toList().get(0); + assertThat(repo.isCollaborator(collaborator), is(true)); } - private void verifyEmptyResult(PagedSearchIterable searchResult) { - assertThat(searchResult.getTotalCount(), is(0)); + private GHRepository getRepository(GitHub gitHub) throws IOException { + return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); } - private void verifySingleResult(PagedSearchIterable searchResult, GHPullRequest expectedPR) - throws IOException { - assertThat(searchResult.getTotalCount(), is(1)); - assertThat(searchResult.toList().get(0).getNumber(), is(expectedPR.getNumber())); + @SuppressFBWarnings(value = "DMI_COLLECTION_OF_URLS", + justification = "It causes a performance degradation, but we have already exposed it to the API") + private Set setupPostCommitHooks(final GHRepository repo) { + return new AbstractSet() { + @Override + public boolean add(URL url) { + try { + repo.createWebHook(url); + return true; + } catch (IOException e) { + throw new GHException("Failed to update post-commit hooks", e); + } + } + + @Override + public Iterator iterator() { + return getPostCommitHooks().iterator(); + } + + @Override + public boolean remove(Object url) { + try { + String _url = ((URL) url).toExternalForm(); + for (GHHook h : repo.getHooks()) { + if (h.getName().equals("web") && h.getConfig().get("url").equals(_url)) { + h.delete(); + return true; + } + } + return false; + } catch (IOException e) { + throw new GHException("Failed to update post-commit hooks", e); + } + } + + @Override + public int size() { + return getPostCommitHooks().size(); + } + + private List getPostCommitHooks() { + try { + List r = new ArrayList<>(); + for (GHHook h : repo.getHooks()) { + if (h.getName().equals("web")) { + r.add(new URL(h.getConfig().get("url"))); + } + } + return r; + } catch (IOException e) { + throw new GHException("Failed to retrieve post-commit hooks", e); + } + } + }; + } + + private void verifyEmptyResult(PagedSearchIterable searchResult) { + assertThat(searchResult.getTotalCount(), is(0)); } private void verifyPluralResult(PagedSearchIterable searchResult, @@ -1978,4 +2002,21 @@ private void verifyPluralResult(PagedSearchIterable searchResult, assertThat(searchResult.toList().get(0).getNumber(), is(expectedPR1.getNumber())); assertThat(searchResult.toList().get(1).getNumber(), is(expectedPR2.getNumber())); } + + private void verifySingleResult(PagedSearchIterable searchResult, GHPullRequest expectedPR) + throws IOException { + assertThat(searchResult.getTotalCount(), is(1)); + assertThat(searchResult.toList().get(0).getNumber(), is(expectedPR.getNumber())); + } + + /** + * Gets the repository. + * + * @return the repository + * @throws IOException + * Signals that an I/O exception has occurred. + */ + protected GHRepository getRepository() throws IOException { + return getRepository(gitHub); + } } diff --git a/src/test/java/org/kohsuke/github/GHSBOMTest.java b/src/test/java/org/kohsuke/github/GHSBOMTest.java new file mode 100644 index 0000000000..df8f618830 --- /dev/null +++ b/src/test/java/org/kohsuke/github/GHSBOMTest.java @@ -0,0 +1,110 @@ +package org.kohsuke.github; + +import org.junit.Test; + +import java.io.IOException; +import java.util.List; + +import static org.hamcrest.Matchers.*; + +/** + * Tests for the SBOM (Software Bill of Materials) API. + * + * @see GHRepository#getSBOM() + * @see GitHub SBOM API + */ +public class GHSBOMTest extends AbstractGitHubWireMockTest { + + /** + * Create default GHSBOMTest instance. + */ + public GHSBOMTest() { + } + + /** + * Tests that the SBOM for a repository can be retrieved and has expected structure. + * + * @throws IOException + * if test fails + */ + @Test + public void getSBOM() throws IOException { + GHRepository repo = gitHub.getRepository("hub4j/github-api"); + GHSBOMExportResult result = repo.getSBOM(); + + assertThat("The SBOM result is populated", result, notNullValue()); + + GHSBOM sbom = result.getSbom(); + assertThat("The SBOM is populated", sbom, notNullValue()); + + assertThat("The SPDX ID is correct", sbom.getSPDXID(), equalTo("SPDXRef-DOCUMENT")); + assertThat("The SPDX version is correct", sbom.getSpdxVersion(), equalTo("SPDX-2.3")); + assertThat("The document name is correct", sbom.getName(), equalTo("com.github.hub4j/github-api")); + assertThat("The data license is CC0-1.0", sbom.getDataLicense(), equalTo("CC0-1.0")); + assertThat("The document namespace is set", sbom.getDocumentNamespace(), notNullValue()); + + GHSBOM.CreationInfo creationInfo = sbom.getCreationInfo(); + assertThat("The creation info is populated", creationInfo, notNullValue()); + assertThat("The created timestamp is set", creationInfo.getCreated(), notNullValue()); + assertThat("The creators list is not empty", creationInfo.getCreators(), not(empty())); + assertThat("GitHub is listed as creator", + creationInfo.getCreators(), + hasItem(containsString("GitHub.com-Dependency-Graph"))); + + // documentDescribes is not present in all responses + assertThat("getDocumentDescribes returns null when not present", sbom.getDocumentDescribes(), nullValue()); + + List packages = sbom.getPackages(); + assertThat("The packages list is not empty", packages, not(empty())); + + GHSBOM.Package firstPackage = packages.get(0); + assertThat("The first package has an SPDX ID", firstPackage.getSPDXID(), notNullValue()); + assertThat("The first package has a name", firstPackage.getName(), notNullValue()); + assertThat("Package has downloadLocation", firstPackage.getDownloadLocation(), notNullValue()); + assertThat("Package filesAnalyzed is accessible", firstPackage.isFilesAnalyzed(), is(false)); + + // Find package with version info, license, and copyright (hamcrest-library with version 3.0) + GHSBOM.Package hamcrestPkg = packages.stream() + .filter(p -> p.getName().contains("hamcrest-library") && "3.0".equals(p.getVersionInfo())) + .findFirst() + .orElse(null); + assertThat("Found hamcrest-library package", hamcrestPkg, notNullValue()); + assertThat("Package has versionInfo", hamcrestPkg.getVersionInfo(), equalTo("3.0")); + assertThat("Package has licenseConcluded", hamcrestPkg.getLicenseConcluded(), equalTo("BSD-3-Clause")); + assertThat("Package has copyrightText", hamcrestPkg.getCopyrightText(), containsString("hamcrest.org")); + + // Find package with licenseDeclared (hub4j/github-api) + GHSBOM.Package hub4jPkg = packages.stream() + .filter(p -> "com.github.hub4j/github-api".equals(p.getName())) + .findFirst() + .orElse(null); + assertThat("Found hub4j/github-api package", hub4jPkg, notNullValue()); + assertThat("Package has licenseDeclared", hub4jPkg.getLicenseDeclared(), equalTo("MIT")); + + // supplier is not present in this response + assertThat("getSupplier returns null when not present", firstPackage.getSupplier(), nullValue()); + + boolean foundPackageWithExternalRefs = false; + for (GHSBOM.Package pkg : packages) { + if (!pkg.getExternalRefs().isEmpty()) { + foundPackageWithExternalRefs = true; + GHSBOM.ExternalRef ref = pkg.getExternalRefs().get(0); + assertThat("External ref has a category", ref.getReferenceCategory(), notNullValue()); + assertThat("External ref has a type", ref.getReferenceType(), notNullValue()); + assertThat("External ref has a locator", ref.getReferenceLocator(), notNullValue()); + break; + } + } + assertThat("At least one package has external refs", foundPackageWithExternalRefs, is(true)); + + List relationships = sbom.getRelationships(); + assertThat("The relationships list is not empty", relationships, not(empty())); + + GHSBOM.Relationship firstRelationship = relationships.get(0); + assertThat("The first relationship has a type", firstRelationship.getRelationshipType(), notNullValue()); + assertThat("The first relationship has an element ID", firstRelationship.getSpdxElementId(), notNullValue()); + assertThat("The first relationship has a related element", + firstRelationship.getRelatedSpdxElement(), + notNullValue()); + } +} diff --git a/src/test/java/org/kohsuke/github/GHTagTest.java b/src/test/java/org/kohsuke/github/GHTagTest.java index 79b42c5648..cec99eddeb 100644 --- a/src/test/java/org/kohsuke/github/GHTagTest.java +++ b/src/test/java/org/kohsuke/github/GHTagTest.java @@ -75,6 +75,10 @@ public void testCreateTag() throws Exception { assertThat(ref, notNullValue()); } + private GHRepository getRepository(GitHub gitHub) throws IOException { + return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); + } + /** * Gets the repository. * @@ -85,8 +89,4 @@ public void testCreateTag() throws Exception { protected GHRepository getRepository() throws IOException { return getRepository(gitHub); } - - private GHRepository getRepository(GitHub gitHub) throws IOException { - return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); - } } diff --git a/src/test/java/org/kohsuke/github/GHTeamTest.java b/src/test/java/org/kohsuke/github/GHTeamTest.java index 1bd3ea6bae..fa80ce605c 100644 --- a/src/test/java/org/kohsuke/github/GHTeamTest.java +++ b/src/test/java/org/kohsuke/github/GHTeamTest.java @@ -29,34 +29,72 @@ public GHTeamTest() { } /** - * Test set description. + * Adds the remove member. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testSetDescription() throws IOException { - - String description = "Updated by API Test"; + public void addRemoveMember() throws IOException { String teamSlug = "dummy-team"; - // Set the description. GHTeam team = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(teamSlug); - assertThat(team.getHtmlUrl(), notNullValue()); - team.setDescription(description); - // Check that it was set correctly. - team = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(teamSlug); - assertThat(team.getDescription(), equalTo(description)); + List members = team.listMembers().toList(); - description += "Modified"; + assertThat(members, notNullValue()); + assertThat("One admin in dummy team", members.size(), equalTo(1)); + assertThat("Specific user in admin team", + members.stream().anyMatch(ghUser -> ghUser.getLogin().equals("bitwiseman"))); - // Set the description. - team.setDescription(description); + GHUser user = gitHub.getUser("gsmet"); - // Check that it was set correctly. - team = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(teamSlug); - assertThat(team.getDescription(), equalTo(description)); + try { + team.add(user, Role.MAINTAINER); + + // test all + members = team.listMembers().toList(); + + assertThat(members, notNullValue()); + assertThat("Two members for all roles in dummy team", members.size(), equalTo(2)); + assertThat("Specific users in team", + members, + containsInAnyOrder(hasProperty("login", equalTo("bitwiseman")), + hasProperty("login", equalTo("gsmet")))); + + // test maintainer role filter + members = team.listMembers(Role.MAINTAINER).toList(); + + assertThat(members, notNullValue()); + assertThat("Two members for all roles in dummy team", members.size(), equalTo(2)); + assertThat("Specific users in team", + members, + containsInAnyOrder(hasProperty("login", equalTo("bitwiseman")), + hasProperty("login", equalTo("gsmet")))); + + // test member role filter + // it's hard to test this as owner of the org are automatically made maintainer + // so let's just test that we don't have any members around + members = team.listMembers(Role.MEMBER).toList(); + + assertThat(members, notNullValue()); + assertThat("No members in dummy team", members.size(), equalTo(0)); + + // test removing the user has effect + team.remove(user); + + members = team.listMembers().toList(); + + assertThat(members, notNullValue()); + assertThat("One member for all roles in dummy team", members.size(), equalTo(1)); + assertThat("Specific user in team", + members, + containsInAnyOrder(hasProperty("login", equalTo("bitwiseman")))); + } finally { + if (team.hasMember(user)) { + team.remove(user); + } + } } /** @@ -137,307 +175,269 @@ public void listMembersNoMatch() throws IOException { } /** - * Test set privacy. + * Test fail to connect to external group from other organization. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testSetPrivacy() throws IOException { - // we need to use a team that doesn't have child teams - // as secret privacy is not supported for parent teams - String teamSlug = "simple-team"; - Privacy privacy = Privacy.CLOSED; + public void testConnectToExternalGroupByGroup() throws IOException { + String teamSlug = "acme-developers"; - // Set the privacy. - GHTeam team = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(teamSlug); - team.setPrivacy(privacy); + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + GHTeam team = org.getTeamBySlug(teamSlug); + GHExternalGroup group = org.getExternalGroup(467431); - // Check that it was set correctly. - team = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(teamSlug); - assertThat(team.getPrivacy(), equalTo(privacy)); + GHExternalGroup connectedGroup = team.connectToExternalGroup(group); - privacy = Privacy.SECRET; + assertThat(connectedGroup.getId(), equalTo(467431L)); + assertThat(connectedGroup.getName(), equalTo("acme-developers")); + assertThat(connectedGroup.getUpdatedAt(), notNullValue()); - // Set the privacy. - team.setPrivacy(privacy); + assertThat(connectedGroup.getMembers(), notNullValue()); + assertThat(membersSummary(connectedGroup), + hasItems("158311279:john-doe_acme:John Doe:john.doe@acme.corp", + "166731041:jane-doe_acme:Jane Doe:jane.doe@acme.corp")); - // Check that it was set correctly. - team = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(teamSlug); - assertThat(team.getPrivacy(), equalTo(privacy)); + assertThat(group.getTeams(), notNullValue()); + assertThat(teamSummary(connectedGroup), hasItems("34519919:ACME-DEVELOPERS")); } /** - * Test fetch child teams. + * Test connect to external group by id. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testFetchChildTeams() throws IOException { - String teamSlug = "dummy-team"; + public void testConnectToExternalGroupById() throws IOException { + String teamSlug = "acme-developers"; GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); GHTeam team = org.getTeamBySlug(teamSlug); - Set result = team.listChildTeams().toSet(); - assertThat(result.size(), equalTo(1)); - assertThat(result.toArray(new GHTeam[]{})[0].getName(), equalTo("child-team-for-dummy")); + final GHExternalGroup group = team.connectToExternalGroup(467431); + + assertThat(group.getId(), equalTo(467431L)); + assertThat(group.getName(), equalTo("acme-developers")); + assertThat(group.getUpdatedAt(), notNullValue()); + + assertThat(group.getMembers(), notNullValue()); + assertThat(membersSummary(group), + hasItems("158311279:john-doe_acme:John Doe:john.doe@acme.corp", + "166731041:jane-doe_acme:Jane Doe:jane.doe@acme.corp")); + + assertThat(group.getTeams(), notNullValue()); + assertThat(teamSummary(group), hasItems("34519919:ACME-DEVELOPERS")); } /** - * Test fetch empty child teams. + * Test delete connection to external group * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testFetchEmptyChildTeams() throws IOException { - String teamSlug = "simple-team"; + public void testDeleteExternalGroupConnection() throws IOException { + String teamSlug = "acme-developers"; GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); GHTeam team = org.getTeamBySlug(teamSlug); - Set result = team.listChildTeams().toSet(); - assertThat(result, is(empty())); + team.deleteExternalGroupConnection(); + + mockGitHub.apiServer() + .verify(1, + deleteRequestedFor(urlPathEqualTo("/orgs/" + team.getOrganization().getLogin() + "/teams/" + + team.getSlug() + "/external-groups"))); } /** - * Adds the remove member. + * Test failure when connecting to external group by id. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void addRemoveMember() throws IOException { - String teamSlug = "dummy-team"; - - GHTeam team = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(teamSlug); - - List members = team.listMembers().toList(); - - assertThat(members, notNullValue()); - assertThat("One admin in dummy team", members.size(), equalTo(1)); - assertThat("Specific user in admin team", - members.stream().anyMatch(ghUser -> ghUser.getLogin().equals("bitwiseman"))); - - GHUser user = gitHub.getUser("gsmet"); - - try { - team.add(user, Role.MAINTAINER); - - // test all - members = team.listMembers().toList(); - - assertThat(members, notNullValue()); - assertThat("Two members for all roles in dummy team", members.size(), equalTo(2)); - assertThat("Specific users in team", - members, - containsInAnyOrder(hasProperty("login", equalTo("bitwiseman")), - hasProperty("login", equalTo("gsmet")))); - - // test maintainer role filter - members = team.listMembers(Role.MAINTAINER).toList(); - - assertThat(members, notNullValue()); - assertThat("Two members for all roles in dummy team", members.size(), equalTo(2)); - assertThat("Specific users in team", - members, - containsInAnyOrder(hasProperty("login", equalTo("bitwiseman")), - hasProperty("login", equalTo("gsmet")))); - - // test member role filter - // it's hard to test this as owner of the org are automatically made maintainer - // so let's just test that we don't have any members around - members = team.listMembers(Role.MEMBER).toList(); - - assertThat(members, notNullValue()); - assertThat("No members in dummy team", members.size(), equalTo(0)); - - // test removing the user has effect - team.remove(user); + public void testFailConnectToExternalGroupTeamIsNotAvailableInOrg() throws IOException { + String teamSlug = "acme-developers"; - members = team.listMembers().toList(); + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + GHTeam team = org.getTeamBySlug(teamSlug); - assertThat(members, notNullValue()); - assertThat("One member for all roles in dummy team", members.size(), equalTo(1)); - assertThat("Specific user in team", - members, - containsInAnyOrder(hasProperty("login", equalTo("bitwiseman")))); - } finally { - if (team.hasMember(user)) { - team.remove(user); - } - } + assertThrows(GHFileNotFoundException.class, () -> team.connectToExternalGroup(12345)); } /** - * Test get external groups. + * Test failure when connecting to external group by id. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testGetExternalGroups() throws IOException { + public void testFailConnectToExternalGroupWhenTeamHasMembers() throws IOException { String teamSlug = "acme-developers"; GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); GHTeam team = org.getTeamBySlug(teamSlug); - final List groups = team.getExternalGroups(); - assertThat(groups, notNullValue()); - assertThat(groups.size(), equalTo(1)); - assertThat(groupSummary(groups), hasItems("467431:acme-developers")); - - groups.forEach(group -> assertThat(group, isExternalGroupSummary())); + final GHIOException failure = assertThrows(GHTeamCannotBeExternallyManagedException.class, + () -> team.connectToExternalGroup(467431)); + assertThat(failure.getMessage(), equalTo("Could not connect team to external group")); } /** - * Test get external groups from not enterprise managed organization. + * Test fetch child teams. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testGetExternalGroupsNotEnterpriseManagedOrganization() throws IOException { - String teamSlug = "acme-developers"; + public void testFetchChildTeams() throws IOException { + String teamSlug = "dummy-team"; GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); GHTeam team = org.getTeamBySlug(teamSlug); + Set result = team.listChildTeams().toSet(); - final GHIOException failure = assertThrows(GHNotExternallyManagedEnterpriseException.class, - () -> team.getExternalGroups()); - assertThat(failure.getMessage(), equalTo("Could not retrieve team external groups")); + assertThat(result.size(), equalTo(1)); + assertThat(result.toArray(new GHTeam[]{})[0].getName(), equalTo("child-team-for-dummy")); } /** - * Test get external groups from team that cannot be externally managed. + * Test fetch empty child teams. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testGetExternalGroupsTeamCannotBeExternallyManaged() throws IOException { - String teamSlug = "acme-developers"; + public void testFetchEmptyChildTeams() throws IOException { + String teamSlug = "simple-team"; GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); GHTeam team = org.getTeamBySlug(teamSlug); + Set result = team.listChildTeams().toSet(); - final GHIOException failure = assertThrows(GHTeamCannotBeExternallyManagedException.class, - () -> team.getExternalGroups()); - assertThat(failure.getMessage(), equalTo("Could not retrieve team external groups")); + assertThat(result, is(empty())); } /** - * Test connect to external group by id. + * Test get external groups. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testConnectToExternalGroupById() throws IOException { + public void testGetExternalGroups() throws IOException { String teamSlug = "acme-developers"; GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); GHTeam team = org.getTeamBySlug(teamSlug); + final List groups = team.getExternalGroups(); - final GHExternalGroup group = team.connectToExternalGroup(467431); - - assertThat(group.getId(), equalTo(467431L)); - assertThat(group.getName(), equalTo("acme-developers")); - assertThat(group.getUpdatedAt(), notNullValue()); - - assertThat(group.getMembers(), notNullValue()); - assertThat(membersSummary(group), - hasItems("158311279:john-doe_acme:John Doe:john.doe@acme.corp", - "166731041:jane-doe_acme:Jane Doe:jane.doe@acme.corp")); + assertThat(groups, notNullValue()); + assertThat(groups.size(), equalTo(1)); + assertThat(groupSummary(groups), hasItems("467431:acme-developers")); - assertThat(group.getTeams(), notNullValue()); - assertThat(teamSummary(group), hasItems("34519919:ACME-DEVELOPERS")); + groups.forEach(group -> assertThat(group, isExternalGroupSummary())); } /** - * Test fail to connect to external group from other organization. + * Test get external groups from not enterprise managed organization. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testConnectToExternalGroupByGroup() throws IOException { + public void testGetExternalGroupsNotEnterpriseManagedOrganization() throws IOException { String teamSlug = "acme-developers"; GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); GHTeam team = org.getTeamBySlug(teamSlug); - GHExternalGroup group = org.getExternalGroup(467431); - - GHExternalGroup connectedGroup = team.connectToExternalGroup(group); - - assertThat(connectedGroup.getId(), equalTo(467431L)); - assertThat(connectedGroup.getName(), equalTo("acme-developers")); - assertThat(connectedGroup.getUpdatedAt(), notNullValue()); - - assertThat(connectedGroup.getMembers(), notNullValue()); - assertThat(membersSummary(connectedGroup), - hasItems("158311279:john-doe_acme:John Doe:john.doe@acme.corp", - "166731041:jane-doe_acme:Jane Doe:jane.doe@acme.corp")); - assertThat(group.getTeams(), notNullValue()); - assertThat(teamSummary(connectedGroup), hasItems("34519919:ACME-DEVELOPERS")); + final GHIOException failure = assertThrows(GHNotExternallyManagedEnterpriseException.class, + () -> team.getExternalGroups()); + assertThat(failure.getMessage(), equalTo("Could not retrieve team external groups")); } /** - * Test failure when connecting to external group by id. + * Test get external groups from team that cannot be externally managed. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testFailConnectToExternalGroupWhenTeamHasMembers() throws IOException { + public void testGetExternalGroupsTeamCannotBeExternallyManaged() throws IOException { String teamSlug = "acme-developers"; GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); GHTeam team = org.getTeamBySlug(teamSlug); final GHIOException failure = assertThrows(GHTeamCannotBeExternallyManagedException.class, - () -> team.connectToExternalGroup(467431)); - assertThat(failure.getMessage(), equalTo("Could not connect team to external group")); + () -> team.getExternalGroups()); + assertThat(failure.getMessage(), equalTo("Could not retrieve team external groups")); } /** - * Test failure when connecting to external group by id. + * Test set description. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testFailConnectToExternalGroupTeamIsNotAvailableInOrg() throws IOException { - String teamSlug = "acme-developers"; + public void testSetDescription() throws IOException { - GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - GHTeam team = org.getTeamBySlug(teamSlug); + String description = "Updated by API Test"; + String teamSlug = "dummy-team"; - assertThrows(GHFileNotFoundException.class, () -> team.connectToExternalGroup(12345)); + // Set the description. + GHTeam team = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(teamSlug); + assertThat(team.getHtmlUrl(), notNullValue()); + team.setDescription(description); + + // Check that it was set correctly. + team = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(teamSlug); + assertThat(team.getDescription(), equalTo(description)); + + description += "Modified"; + + // Set the description. + team.setDescription(description); + + // Check that it was set correctly. + team = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(teamSlug); + assertThat(team.getDescription(), equalTo(description)); } /** - * Test delete connection to external group + * Test set privacy. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testDeleteExternalGroupConnection() throws IOException { - String teamSlug = "acme-developers"; + public void testSetPrivacy() throws IOException { + // we need to use a team that doesn't have child teams + // as secret privacy is not supported for parent teams + String teamSlug = "simple-team"; + Privacy privacy = Privacy.CLOSED; - GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - GHTeam team = org.getTeamBySlug(teamSlug); + // Set the privacy. + GHTeam team = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(teamSlug); + team.setPrivacy(privacy); - team.deleteExternalGroupConnection(); + // Check that it was set correctly. + team = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(teamSlug); + assertThat(team.getPrivacy(), equalTo(privacy)); - mockGitHub.apiServer() - .verify(1, - deleteRequestedFor(urlPathEqualTo("/orgs/" + team.getOrganization().getLogin() + "/teams/" - + team.getSlug() + "/external-groups"))); + privacy = Privacy.SECRET; + + // Set the privacy. + team.setPrivacy(privacy); + + // Check that it was set correctly. + team = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(teamSlug); + assertThat(team.getPrivacy(), equalTo(privacy)); } } diff --git a/src/test/java/org/kohsuke/github/GHTreeBuilderTest.java b/src/test/java/org/kohsuke/github/GHTreeBuilderTest.java index 2f3ea77fb4..0d0ea02d3a 100644 --- a/src/test/java/org/kohsuke/github/GHTreeBuilderTest.java +++ b/src/test/java/org/kohsuke/github/GHTreeBuilderTest.java @@ -16,29 +16,29 @@ */ public class GHTreeBuilderTest extends AbstractGitHubWireMockTest { - /** - * Create default GHTreeBuilderTest instance - */ - public GHTreeBuilderTest() { - } - - private static String REPO_NAME = "hub4j-test-org/GHTreeBuilderTest"; + private static byte[] CONTENT_DATA1 = { 0x01, 0x02, 0x03 }; - private static String PATH_SCRIPT = "app/run.sh"; - private static String CONTENT_SCRIPT = "#!/bin/bash\necho Hello\n"; + private static byte[] CONTENT_DATA2 = { 0x04, 0x05, 0x06, 0x07 }; - private static String PATH_README = "doc/readme.txt"; private static String CONTENT_README = "Thanks for using our application!\n"; + private static String CONTENT_SCRIPT = "#!/bin/bash\necho Hello\n"; private static String PATH_DATA1 = "data/val1.dat"; - private static byte[] CONTENT_DATA1 = { 0x01, 0x02, 0x03 }; - private static String PATH_DATA2 = "data/val2.dat"; - private static byte[] CONTENT_DATA2 = { 0x04, 0x05, 0x06, 0x07 }; - private GHRepository repo; + private static String PATH_README = "doc/readme.txt"; + private static String PATH_SCRIPT = "app/run.sh"; + + private static String REPO_NAME = "hub4j-test-org/GHTreeBuilderTest"; private GHRef mainRef; + + private GHRepository repo; private GHTreeBuilder treeBuilder; + /** + * Create default GHTreeBuilderTest instance + */ + public GHTreeBuilderTest() { + } /** * Cleanup. @@ -142,6 +142,13 @@ public void testDelete() throws Exception { } } + private long getFileSize(String path) throws IOException { + GHContent content = repo.getFileContent(path); + if (content == null) + throw new IOException("File not found: " + path); + return content.getSize(); + } + private GHCommit updateTree() throws IOException { String treeSha = treeBuilder.create().getSha(); GHCommit commit = new GHCommitBuilder(repo).message("Add files") @@ -155,11 +162,4 @@ private GHCommit updateTree() throws IOException { mainRef.updateTo(commitSha); return commit; } - - private long getFileSize(String path) throws IOException { - GHContent content = repo.getFileContent(path); - if (content == null) - throw new IOException("File not found: " + path); - return content.getSize(); - } } diff --git a/src/test/java/org/kohsuke/github/GHUserTest.java b/src/test/java/org/kohsuke/github/GHUserTest.java index bf47fb252e..adeaa294e8 100644 --- a/src/test/java/org/kohsuke/github/GHUserTest.java +++ b/src/test/java/org/kohsuke/github/GHUserTest.java @@ -27,52 +27,28 @@ public GHUserTest() { } /** - * Checks if is member of. + * Creates the and count private repos. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void isMemberOf() throws IOException { - GHUser u = gitHub.getUser("bitwiseman"); - String teamSlug = "dummy-team"; - GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - GHTeam team = org.getTeamBySlug(teamSlug); - - assertThat(u.isMemberOf(org), is(true)); - assertThat(u.isMemberOf(team), is(true)); - assertThat(u.isPublicMemberOf(org), is(false)); - - org = gitHub.getOrganization("hub4j"); - assertThat(u.isMemberOf(org), is(true)); - assertThat(u.isPublicMemberOf(org), is(true)); - - u = gitHub.getUser("rtyler"); - assertThat(u.isMemberOf(org), is(false)); - assertThat(u.isMemberOf(team), is(false)); - assertThat(u.isPublicMemberOf(org), is(false)); - } + public void createAndCountPrivateRepos() throws IOException { + String login = gitHub.getMyself().getLogin(); - /** - * List follows and followers. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Test - public void listFollowsAndFollowers() throws IOException { - GHUser u = gitHub.getUser("rtyler"); - assertThat(count30(u.listFollows()), not(count30(u.listFollowers()))); - } + GHRepository repository = gitHub.createRepository("github-user-test-private-repo") + .description("a test private repository used to test kohsuke's github-api") + .homepage("http://github-api.kohsuke.org/") + .private_(true) + .create(); - private Set count30(PagedIterable l) { - Set users = new HashSet(); - PagedIterator itr = l.iterator(); - for (int i = 0; i < 30 && itr.hasNext(); i++) { - users.add(itr.next()); + try { + assertThat(repository, notNullValue()); + GHUser ghUser = gitHub.getUser(login); + assertThat(ghUser.getTotalPrivateRepoCount().orElse(-1), greaterThan(0)); + } finally { + repository.delete(); } - assertThat(users.size(), equalTo(30)); - return users; } /** @@ -118,25 +94,42 @@ public int compare(GHKey ghKey, GHKey t1) { } /** - * List public repositories. + * Checks if is member of. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void listPublicRepositories() throws IOException { - GHUser user = gitHub.getUser("kohsuke"); - Iterator itr = user.listRepositories().iterator(); - int i = 0; - for (; i < 115; i++) { - assertThat(itr.hasNext(), is(true)); - GHRepository r = itr.next(); - // System.out.println(r.getFullName()); - assertThat(r.getUrl(), notNullValue()); - assertThat(r.getId(), not(0L)); - } + public void isMemberOf() throws IOException { + GHUser u = gitHub.getUser("bitwiseman"); + String teamSlug = "dummy-team"; + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + GHTeam team = org.getTeamBySlug(teamSlug); - assertThat(i, equalTo(115)); + assertThat(u.isMemberOf(org), is(true)); + assertThat(u.isMemberOf(team), is(true)); + assertThat(u.isPublicMemberOf(org), is(false)); + + org = gitHub.getOrganization("hub4j"); + assertThat(u.isMemberOf(org), is(true)); + assertThat(u.isPublicMemberOf(org), is(true)); + + u = gitHub.getUser("rtyler"); + assertThat(u.isMemberOf(org), is(false)); + assertThat(u.isMemberOf(team), is(false)); + assertThat(u.isPublicMemberOf(org), is(false)); + } + + /** + * List follows and followers. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void listFollowsAndFollowers() throws IOException { + GHUser u = gitHub.getUser("rtyler"); + assertThat(count30(u.listFollows()), not(count30(u.listFollowers()))); } /** @@ -157,15 +150,15 @@ public void listProjects() throws IOException { } /** - * List public repositories page size 62. + * List public repositories. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void listPublicRepositoriesPageSize62() throws IOException { + public void listPublicRepositories() throws IOException { GHUser user = gitHub.getUser("kohsuke"); - Iterator itr = user.listRepositories(62).iterator(); + Iterator itr = user.listRepositories().iterator(); int i = 0; for (; i < 115; i++) { assertThat(itr.hasNext(), is(true)); @@ -179,28 +172,25 @@ public void listPublicRepositoriesPageSize62() throws IOException { } /** - * Creates the and count private repos. + * List public repositories page size 62. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void createAndCountPrivateRepos() throws IOException { - String login = gitHub.getMyself().getLogin(); - - GHRepository repository = gitHub.createRepository("github-user-test-private-repo") - .description("a test private repository used to test kohsuke's github-api") - .homepage("http://github-api.kohsuke.org/") - .private_(true) - .create(); - - try { - assertThat(repository, notNullValue()); - GHUser ghUser = gitHub.getUser(login); - assertThat(ghUser.getTotalPrivateRepoCount().orElse(-1), greaterThan(0)); - } finally { - repository.delete(); + public void listPublicRepositoriesPageSize62() throws IOException { + GHUser user = gitHub.getUser("kohsuke"); + Iterator itr = user.listRepositories(62).iterator(); + int i = 0; + for (; i < 115; i++) { + assertThat(itr.hasNext(), is(true)); + GHRepository r = itr.next(); + // System.out.println(r.getFullName()); + assertThat(r.getUrl(), notNullValue()); + assertThat(r.getId(), not(0L)); } + + assertThat(i, equalTo(115)); } /** @@ -214,13 +204,15 @@ public void verifyBioAndHireable() throws IOException { GHUser u = gitHub.getUser("Chew"); assertThat(u.getBio(), equalTo("I like to program things and I hope to program something cool one day :D")); assertThat(u.isHireable(), is(true)); - assertThat(u.getTwitterUsername(), notNullValue()); + assertThat(u.getTwitterUsername(), equalTo("ChewCraft")); assertThat(u.getBlog(), equalTo("https://chew.pw")); assertThat(u.getCompany(), equalTo("@Memerator")); assertThat(u.getFollowersCount(), equalTo(29)); assertThat(u.getFollowingCount(), equalTo(3)); assertThat(u.getPublicGistCount(), equalTo(4)); assertThat(u.getPublicRepoCount(), equalTo(96)); + assertThat(u.getCreatedAt(), equalTo(GitHubClient.parseInstant("2014-07-26T23:41:36Z"))); + assertThat(u.getUpdatedAt(), equalTo(GitHubClient.parseInstant("2020-06-06T20:16:06Z"))); } /** @@ -247,7 +239,17 @@ public void verifySuspendedAt() throws IOException { assertThat(normal.getSuspendedAt(), is(nullValue())); GHUser suspended = gitHub.getUser("suspended"); - Date suspendedAt = new Date(Instant.parse("2024-08-08T00:00:00Z").toEpochMilli()); + Instant suspendedAt = Instant.ofEpochMilli(Instant.parse("2024-08-08T00:00:00Z").toEpochMilli()); assertThat(suspended.getSuspendedAt(), equalTo(suspendedAt)); } + + private Set count30(PagedIterable l) { + Set users = new HashSet(); + PagedIterator itr = l.iterator(); + for (int i = 0; i < 30 && itr.hasNext(); i++) { + users.add(itr.next()); + } + assertThat(users.size(), equalTo(30)); + return users; + } } diff --git a/src/test/java/org/kohsuke/github/GHVerificationReasonTest.java b/src/test/java/org/kohsuke/github/GHVerificationReasonTest.java index 3573fd3860..5a9b5205af 100644 --- a/src/test/java/org/kohsuke/github/GHVerificationReasonTest.java +++ b/src/test/java/org/kohsuke/github/GHVerificationReasonTest.java @@ -19,292 +19,292 @@ public GHVerificationReasonTest() { } /** - * Test expired key. + * Test bad cert. * * @throws Exception * the exception */ - // Issue 737 @Test - public void testExpiredKey() throws Exception { + public void testBadCert() throws Exception { GHRepository r = gitHub.getRepository("hub4j/github-api"); GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f01"); assertThat(commit.getCommitShortInfo().getAuthor().getName(), equalTo("Sourabh Parkala")); + assertThat(commit.getCommitShortInfo().getVerification().getSignature(), notNullValue()); assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); - assertThat(commit.getCommitShortInfo().getVerification().getReason(), - equalTo(GHVerification.Reason.EXPIRED_KEY)); + assertThat(commit.getCommitShortInfo().getVerification().getReason(), equalTo(GHVerification.Reason.BAD_CERT)); } /** - * Test not signing key. + * Test bad email. * * @throws Exception * the exception */ @Test - public void testNotSigningKey() throws Exception { + public void testBadEmail() throws Exception { GHRepository r = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f02"); + GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f09"); assertThat(commit.getCommitShortInfo().getAuthor().getName(), equalTo("Sourabh Parkala")); assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); - assertThat(commit.getCommitShortInfo().getVerification().getReason(), - equalTo(GHVerification.Reason.NOT_SIGNING_KEY)); + assertThat(commit.getCommitShortInfo().getVerification().getReason(), equalTo(GHVerification.Reason.BAD_EMAIL)); } /** - * Test gpgverify error. + * Test expired key. * * @throws Exception * the exception */ + // Issue 737 @Test - public void testGpgverifyError() throws Exception { + public void testExpiredKey() throws Exception { GHRepository r = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f03"); + GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f01"); assertThat(commit.getCommitShortInfo().getAuthor().getName(), equalTo("Sourabh Parkala")); assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); assertThat(commit.getCommitShortInfo().getVerification().getReason(), - equalTo(GHVerification.Reason.GPGVERIFY_ERROR)); + equalTo(GHVerification.Reason.EXPIRED_KEY)); } /** - * Test gpgverify unavailable. + * Test gpgverify error. * * @throws Exception * the exception */ @Test - public void testGpgverifyUnavailable() throws Exception { + public void testGpgverifyError() throws Exception { GHRepository r = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f04"); + GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f03"); assertThat(commit.getCommitShortInfo().getAuthor().getName(), equalTo("Sourabh Parkala")); assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); assertThat(commit.getCommitShortInfo().getVerification().getReason(), - equalTo(GHVerification.Reason.GPGVERIFY_UNAVAILABLE)); + equalTo(GHVerification.Reason.GPGVERIFY_ERROR)); } /** - * Test unsigned. + * Test gpgverify unavailable. * * @throws Exception * the exception */ @Test - public void testUnsigned() throws Exception { + public void testGpgverifyUnavailable() throws Exception { GHRepository r = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f05"); + GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f04"); assertThat(commit.getCommitShortInfo().getAuthor().getName(), equalTo("Sourabh Parkala")); assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); - assertThat(commit.getCommitShortInfo().getVerification().getReason(), equalTo(GHVerification.Reason.UNSIGNED)); + assertThat(commit.getCommitShortInfo().getVerification().getReason(), + equalTo(GHVerification.Reason.GPGVERIFY_UNAVAILABLE)); } /** - * Test unknown signature type. + * Test invalid. * * @throws Exception * the exception */ @Test - public void testUnknownSignatureType() throws Exception { + public void testInvalid() throws Exception { GHRepository r = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f06"); + GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f12"); assertThat(commit.getCommitShortInfo().getAuthor().getName(), equalTo("Sourabh Parkala")); assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); - assertThat(commit.getCommitShortInfo().getVerification().getReason(), - equalTo(GHVerification.Reason.UNKNOWN_SIGNATURE_TYPE)); + assertThat(commit.getCommitShortInfo().getVerification().getReason(), equalTo(GHVerification.Reason.INVALID)); } /** - * Test no user. + * Test malformed sig. * * @throws Exception * the exception */ @Test - public void testNoUser() throws Exception { + public void testMalformedSig() throws Exception { GHRepository r = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f07"); + GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f01"); assertThat(commit.getCommitShortInfo().getAuthor().getName(), equalTo("Sourabh Parkala")); + assertThat(commit.getCommitShortInfo().getVerification().getSignature(), notNullValue()); assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); - assertThat(commit.getCommitShortInfo().getVerification().getReason(), equalTo(GHVerification.Reason.NO_USER)); + assertThat(commit.getCommitShortInfo().getVerification().getReason(), + equalTo(GHVerification.Reason.MALFORMED_SIG)); } /** - * Test unverified email. + * Test malformed signature. * * @throws Exception * the exception */ @Test - public void testUnverifiedEmail() throws Exception { + public void testMalformedSignature() throws Exception { GHRepository r = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f08"); + GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f11"); assertThat(commit.getCommitShortInfo().getAuthor().getName(), equalTo("Sourabh Parkala")); assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); assertThat(commit.getCommitShortInfo().getVerification().getReason(), - equalTo(GHVerification.Reason.UNVERIFIED_EMAIL)); + equalTo(GHVerification.Reason.MALFORMED_SIGNATURE)); } /** - * Test bad email. + * Test no user. * * @throws Exception * the exception */ @Test - public void testBadEmail() throws Exception { + public void testNoUser() throws Exception { GHRepository r = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f09"); + GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f07"); assertThat(commit.getCommitShortInfo().getAuthor().getName(), equalTo("Sourabh Parkala")); assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); - assertThat(commit.getCommitShortInfo().getVerification().getReason(), equalTo(GHVerification.Reason.BAD_EMAIL)); + assertThat(commit.getCommitShortInfo().getVerification().getReason(), equalTo(GHVerification.Reason.NO_USER)); } /** - * Test unknown key. + * Test not signing key. * * @throws Exception * the exception */ @Test - public void testUnknownKey() throws Exception { + public void testNotSigningKey() throws Exception { GHRepository r = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f10"); + GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f02"); assertThat(commit.getCommitShortInfo().getAuthor().getName(), equalTo("Sourabh Parkala")); assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); assertThat(commit.getCommitShortInfo().getVerification().getReason(), - equalTo(GHVerification.Reason.UNKNOWN_KEY)); + equalTo(GHVerification.Reason.NOT_SIGNING_KEY)); } /** - * Test malformed signature. + * Test OSCP error. * * @throws Exception * the exception */ @Test - public void testMalformedSignature() throws Exception { + public void testOcspError() throws Exception { GHRepository r = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f11"); + GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f01"); assertThat(commit.getCommitShortInfo().getAuthor().getName(), equalTo("Sourabh Parkala")); + assertThat(commit.getCommitShortInfo().getVerification().getSignature(), notNullValue()); assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); assertThat(commit.getCommitShortInfo().getVerification().getReason(), - equalTo(GHVerification.Reason.MALFORMED_SIGNATURE)); + equalTo(GHVerification.Reason.OCSP_ERROR)); } /** - * Test invalid. + * Test OSCP pending. * * @throws Exception * the exception */ @Test - public void testInvalid() throws Exception { + public void testOscpPending() throws Exception { GHRepository r = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f12"); + GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f01"); assertThat(commit.getCommitShortInfo().getAuthor().getName(), equalTo("Sourabh Parkala")); + assertThat(commit.getCommitShortInfo().getVerification().getSignature(), notNullValue()); assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); - assertThat(commit.getCommitShortInfo().getVerification().getReason(), equalTo(GHVerification.Reason.INVALID)); + assertThat(commit.getCommitShortInfo().getVerification().getReason(), + equalTo(GHVerification.Reason.OCSP_PENDING)); } /** - * Test valid. + * Test OCSP revoked. * * @throws Exception * the exception */ @Test - public void testValid() throws Exception { + public void testOscpRevoked() throws Exception { GHRepository r = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f13"); + GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f01"); assertThat(commit.getCommitShortInfo().getAuthor().getName(), equalTo("Sourabh Parkala")); - assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(true)); - assertThat(commit.getCommitShortInfo().getVerification().getReason(), equalTo(GHVerification.Reason.VALID)); - assertThat(commit.getCommitShortInfo().getVerification().getPayload(), notNullValue()); assertThat(commit.getCommitShortInfo().getVerification().getSignature(), notNullValue()); + assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); + assertThat(commit.getCommitShortInfo().getVerification().getReason(), + equalTo(GHVerification.Reason.OCSP_REVOKED)); } /** - * Test bad cert. + * Test unknown key. * * @throws Exception * the exception */ @Test - public void testBadCert() throws Exception { + public void testUnknownKey() throws Exception { GHRepository r = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f01"); + GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f10"); assertThat(commit.getCommitShortInfo().getAuthor().getName(), equalTo("Sourabh Parkala")); - assertThat(commit.getCommitShortInfo().getVerification().getSignature(), notNullValue()); assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); - assertThat(commit.getCommitShortInfo().getVerification().getReason(), equalTo(GHVerification.Reason.BAD_CERT)); + assertThat(commit.getCommitShortInfo().getVerification().getReason(), + equalTo(GHVerification.Reason.UNKNOWN_KEY)); } /** - * Test malformed sig. + * Test unknown signature type. * * @throws Exception * the exception */ @Test - public void testMalformedSig() throws Exception { + public void testUnknownSignatureType() throws Exception { GHRepository r = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f01"); + GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f06"); assertThat(commit.getCommitShortInfo().getAuthor().getName(), equalTo("Sourabh Parkala")); - assertThat(commit.getCommitShortInfo().getVerification().getSignature(), notNullValue()); assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); assertThat(commit.getCommitShortInfo().getVerification().getReason(), - equalTo(GHVerification.Reason.MALFORMED_SIG)); + equalTo(GHVerification.Reason.UNKNOWN_SIGNATURE_TYPE)); } /** - * Test OSCP error. + * Test unsigned. * * @throws Exception * the exception */ @Test - public void testOcspError() throws Exception { + public void testUnsigned() throws Exception { GHRepository r = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f01"); + GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f05"); assertThat(commit.getCommitShortInfo().getAuthor().getName(), equalTo("Sourabh Parkala")); - assertThat(commit.getCommitShortInfo().getVerification().getSignature(), notNullValue()); assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); - assertThat(commit.getCommitShortInfo().getVerification().getReason(), - equalTo(GHVerification.Reason.OCSP_ERROR)); + assertThat(commit.getCommitShortInfo().getVerification().getReason(), equalTo(GHVerification.Reason.UNSIGNED)); } /** - * Test OSCP pending. + * Test unverified email. * * @throws Exception * the exception */ @Test - public void testOscpPending() throws Exception { + public void testUnverifiedEmail() throws Exception { GHRepository r = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f01"); + GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f08"); assertThat(commit.getCommitShortInfo().getAuthor().getName(), equalTo("Sourabh Parkala")); - assertThat(commit.getCommitShortInfo().getVerification().getSignature(), notNullValue()); assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); assertThat(commit.getCommitShortInfo().getVerification().getReason(), - equalTo(GHVerification.Reason.OCSP_PENDING)); + equalTo(GHVerification.Reason.UNVERIFIED_EMAIL)); } /** - * Test OCSP revoked. + * Test valid. * * @throws Exception * the exception */ @Test - public void testOscpRevoked() throws Exception { + public void testValid() throws Exception { GHRepository r = gitHub.getRepository("hub4j/github-api"); - GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f01"); + GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f13"); assertThat(commit.getCommitShortInfo().getAuthor().getName(), equalTo("Sourabh Parkala")); + assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(true)); + assertThat(commit.getCommitShortInfo().getVerification().getReason(), equalTo(GHVerification.Reason.VALID)); + assertThat(commit.getCommitShortInfo().getVerification().getPayload(), notNullValue()); assertThat(commit.getCommitShortInfo().getVerification().getSignature(), notNullValue()); - assertThat(commit.getCommitShortInfo().getVerification().isVerified(), is(false)); - assertThat(commit.getCommitShortInfo().getVerification().getReason(), - equalTo(GHVerification.Reason.OCSP_REVOKED)); } } diff --git a/src/test/java/org/kohsuke/github/GHWorkflowRunTest.java b/src/test/java/org/kohsuke/github/GHWorkflowRunTest.java index e3727d7c56..21c51c23fc 100644 --- a/src/test/java/org/kohsuke/github/GHWorkflowRunTest.java +++ b/src/test/java/org/kohsuke/github/GHWorkflowRunTest.java @@ -9,15 +9,17 @@ import org.kohsuke.github.GHWorkflowRun.Status; import org.kohsuke.github.function.InputStreamFunction; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; -import java.util.Date; import java.util.List; import java.util.Optional; import java.util.Scanner; import java.util.function.Function; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -30,327 +32,227 @@ */ public class GHWorkflowRunTest extends AbstractGitHubWireMockTest { - /** - * Create default GHWorkflowRunTest instance - */ - public GHWorkflowRunTest() { - } - - private static final String REPO_NAME = "hub4j-test-org/GHWorkflowRunTest"; - private static final String MAIN_BRANCH = "main"; - private static final String SECOND_BRANCH = "second-branch"; + private static final String ARTIFACTS_WORKFLOW_NAME = "Artifacts workflow"; - private static final String FAST_WORKFLOW_PATH = "fast-workflow.yml"; + private static final String ARTIFACTS_WORKFLOW_PATH = "artifacts-workflow.yml"; private static final String FAST_WORKFLOW_NAME = "Fast workflow"; + private static final String FAST_WORKFLOW_PATH = "fast-workflow.yml"; - private static final String SLOW_WORKFLOW_PATH = "slow-workflow.yml"; - private static final String SLOW_WORKFLOW_NAME = "Slow workflow"; - - private static final String ARTIFACTS_WORKFLOW_PATH = "artifacts-workflow.yml"; - private static final String ARTIFACTS_WORKFLOW_NAME = "Artifacts workflow"; + private static final String MAIN_BRANCH = "main"; + private static final String MULTI_JOBS_WORKFLOW_NAME = "Multi jobs workflow"; private static final String MULTI_JOBS_WORKFLOW_PATH = "multi-jobs-workflow.yml"; - private static final String MULTI_JOBS_WORKFLOW_NAME = "Multi jobs workflow"; - private static final String RUN_A_ONE_LINE_SCRIPT_STEP_NAME = "Run a one-line script"; - private static final String UBUNTU_LABEL = "ubuntu-latest"; + private static final String REPO_NAME = "hub4j-test-org/GHWorkflowRunTest"; - private GHRepository repo; + private static final String RUN_A_ONE_LINE_SCRIPT_STEP_NAME = "Run a one-line script"; + private static final String SECOND_BRANCH = "second-branch"; - /** - * Sets the up. - * - * @throws Exception - * the exception - */ - @Before - public void setUp() throws Exception { - repo = gitHub.getRepository(REPO_NAME); + private static final String SLOW_WORKFLOW_NAME = "Slow workflow"; + private static final String SLOW_WORKFLOW_PATH = "slow-workflow.yml"; + private static final String UBUNTU_LABEL = "ubuntu-latest"; + private static void checkArtifactProperties(GHArtifact artifact, String artifactName) throws IOException { + assertThat(artifact.getId(), notNullValue()); + assertThat(artifact.getNodeId(), notNullValue()); + assertThat(artifact.getRepository().getFullName(), equalTo(REPO_NAME)); + assertThat(artifact.getName(), is(artifactName)); + assertThat(artifact.getArchiveDownloadUrl().getPath(), containsString("actions/artifacts")); + assertThat(artifact.getCreatedAt(), notNullValue()); + assertThat(artifact.getUpdatedAt(), notNullValue()); + assertThat(artifact.getExpiresAt(), notNullValue()); + assertThat(artifact.getSizeInBytes(), greaterThan(0L)); + assertThat(artifact.isExpired(), is(false)); } - /** - * Test manual run and basic information. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Test - public void testManualRunAndBasicInformation() throws IOException { - GHWorkflow workflow = repo.getWorkflow(FAST_WORKFLOW_PATH); - - long latestPreexistingWorkflowRunId = getLatestPreexistingWorkflowRunId(); - - workflow.dispatch(MAIN_BRANCH); + private static void checkJobProperties(long workflowRunId, GHWorkflowJob job, String jobName) { + assertThat(job.getId(), notNullValue()); + assertThat(job.getNodeId(), notNullValue()); + assertThat(job.getRepository().getFullName(), equalTo(REPO_NAME)); + assertThat(job.getName(), is(jobName)); + assertThat(job.getStartedAt(), notNullValue()); + assertThat(job.getCompletedAt(), notNullValue()); + assertThat(job.getHeadSha(), notNullValue()); + assertThat(job.getStatus(), is(Status.COMPLETED)); + assertThat(job.getConclusion(), is(Conclusion.SUCCESS)); + assertThat(job.getRunId(), is(workflowRunId)); + assertThat(job.getUrl().getPath(), containsString("/actions/jobs/")); + assertThat(job.getHtmlUrl().getPath(), containsString("/runs/" + job.getId())); + assertThat(job.getCheckRunUrl().getPath(), containsString("/check-runs/")); + assertThat(job.getRunnerId(), is(1)); + assertThat(job.getRunnerName(), containsString("my runner")); + assertThat(job.getRunnerGroupId(), is(2)); + assertThat(job.getRunnerGroupName(), containsString("my runner group")); - await((nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, - FAST_WORKFLOW_NAME, - MAIN_BRANCH, - Status.COMPLETED, - latestPreexistingWorkflowRunId).isPresent()); + // we only test the step we have control over, the others are added by GitHub + Optional step = job.getSteps() + .stream() + .filter(s -> RUN_A_ONE_LINE_SCRIPT_STEP_NAME.equals(s.getName())) + .findFirst(); + if (!step.isPresent()) { + fail("Unable to find " + RUN_A_ONE_LINE_SCRIPT_STEP_NAME + " step"); + } - GHWorkflowRun workflowRun = getWorkflowRun(FAST_WORKFLOW_NAME, - MAIN_BRANCH, - Status.COMPLETED, - latestPreexistingWorkflowRunId) - .orElseThrow(() -> new IllegalStateException("We must have a valid workflow run starting from here")); + Optional labelOptional = job.getLabels().stream().filter(s -> s.equals(UBUNTU_LABEL)).findFirst(); + if (!labelOptional.isPresent()) { + fail("Unable to find " + UBUNTU_LABEL + " label"); + } - assertThat(workflowRun.getWorkflowId(), equalTo(workflow.getId())); - assertThat(workflowRun.getId(), notNullValue()); - assertThat(workflowRun.getNodeId(), notNullValue()); - assertThat(workflowRun.getRepository().getFullName(), equalTo(REPO_NAME)); - assertThat(workflowRun.getUrl().getPath(), containsString("/actions/runs/")); - assertThat(workflowRun.getHtmlUrl().getPath(), containsString("/actions/runs/")); - assertThat(workflowRun.getJobsUrl().getPath(), endsWith("/jobs")); - assertThat(workflowRun.getLogsUrl().getPath(), endsWith("/logs")); - assertThat(workflowRun.getCheckSuiteUrl().getPath(), containsString("/check-suites/")); - assertThat(workflowRun.getArtifactsUrl().getPath(), endsWith("/artifacts")); - assertThat(workflowRun.getCancelUrl().getPath(), endsWith("/cancel")); - assertThat(workflowRun.getRerunUrl().getPath(), endsWith("/rerun")); - assertThat(workflowRun.getWorkflowUrl().getPath(), containsString("/actions/workflows/")); - assertThat(workflowRun.getHeadBranch(), equalTo(MAIN_BRANCH)); - assertThat(workflowRun.getHeadCommit().getId(), notNullValue()); - assertThat(workflowRun.getHeadCommit().getTreeId(), notNullValue()); - assertThat(workflowRun.getHeadCommit().getMessage(), notNullValue()); - assertThat(workflowRun.getHeadCommit().getTimestamp(), notNullValue()); - assertThat(workflowRun.getHeadCommit().getAuthor().getEmail(), notNullValue()); - assertThat(workflowRun.getHeadCommit().getCommitter().getEmail(), notNullValue()); - assertThat(workflowRun.getEvent(), equalTo(GHEvent.WORKFLOW_DISPATCH)); - assertThat(workflowRun.getStatus(), equalTo(Status.COMPLETED)); - assertThat(workflowRun.getConclusion(), equalTo(Conclusion.SUCCESS)); - assertThat(workflowRun.getHeadSha(), notNullValue()); - assertThat(workflowRun.getTriggeringActor(), hasProperty("login", equalTo("octocat"))); + checkStepProperties(step.get(), RUN_A_ONE_LINE_SCRIPT_STEP_NAME, 2); } - /** - * Test cancel and rerun. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Test - public void testCancelAndRerun() throws IOException { - GHWorkflow workflow = repo.getWorkflow(SLOW_WORKFLOW_PATH); - - long latestPreexistingWorkflowRunId = getLatestPreexistingWorkflowRunId(); - - workflow.dispatch(MAIN_BRANCH); - - // now that we have triggered the workflow run, we will wait until it's in progress and then cancel it - await((nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, - SLOW_WORKFLOW_NAME, - MAIN_BRANCH, - Status.IN_PROGRESS, - latestPreexistingWorkflowRunId).isPresent()); - - GHWorkflowRun workflowRun = getWorkflowRun(SLOW_WORKFLOW_NAME, - MAIN_BRANCH, - Status.IN_PROGRESS, - latestPreexistingWorkflowRunId) - .orElseThrow(() -> new IllegalStateException("We must have a valid workflow run starting from here")); - - assertThat(workflowRun.getId(), notNullValue()); - - workflowRun.cancel(); - long cancelledWorkflowRunId = workflowRun.getId(); - - // let's wait until it's completed - await((nonRecordingRepo) -> getWorkflowRunStatus(nonRecordingRepo, cancelledWorkflowRunId) == Status.COMPLETED); - - // let's check that it has been properly cancelled - workflowRun = repo.getWorkflowRun(cancelledWorkflowRunId); - assertThat(workflowRun.getConclusion(), equalTo(Conclusion.CANCELLED)); + private static void checkStepProperties(Step step, String name, int number) { + assertThat(step.getName(), is(name)); + assertThat(step.getNumber(), is(number)); + assertThat(step.getStatus(), is(Status.COMPLETED)); + assertThat(step.getConclusion(), is(Conclusion.SUCCESS)); + assertThat(step.getStartedAt(), notNullValue()); + assertThat(step.getCompletedAt(), notNullValue()); + } - // now let's rerun it - workflowRun.rerun(); + @SuppressWarnings("resource") + private static InputStreamFunction getLogArchiveInputStreamFunction(String mainLogFileName, + List logsArchiveEntries) { + return (is) -> { + try (ZipInputStream zis = new ZipInputStream(is)) { + StringBuilder sb = new StringBuilder(); - // let's check that it has been rerun - await((nonRecordingRepo) -> getWorkflowRunStatus(nonRecordingRepo, - cancelledWorkflowRunId) == Status.IN_PROGRESS); + ZipEntry ze; + while ((ze = zis.getNextEntry()) != null) { + logsArchiveEntries.add(ze.getName()); + if (mainLogFileName.equals(ze.getName())) { + // the scanner has to be kept open to avoid closing zis + Scanner scanner = new Scanner(zis); + while (scanner.hasNextLine()) { + sb.append(scanner.nextLine()).append("\n"); + } + } + } - // cancel it again - workflowRun.cancel(); + return sb.toString(); + } + }; } - /** - * Test delete. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Test - public void testDelete() throws IOException { - GHWorkflow workflow = repo.getWorkflow(FAST_WORKFLOW_PATH); - - long latestPreexistingWorkflowRunId = getLatestPreexistingWorkflowRunId(); - - workflow.dispatch(MAIN_BRANCH); + @SuppressWarnings("resource") + private static InputStreamFunction getLogTextInputStreamFunction() { + return (is) -> { + StringBuilder sb = new StringBuilder(); + Scanner scanner = new Scanner(is); + while (scanner.hasNextLine()) { + sb.append(scanner.nextLine()).append("\n"); + } + return sb.toString(); + }; + } - await((nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, - FAST_WORKFLOW_NAME, - MAIN_BRANCH, - Status.COMPLETED, - latestPreexistingWorkflowRunId).isPresent()); + private static Optional getWorkflowRun(GHRepository repository, + String workflowName, + String branch, + Conclusion conclusion) { + List workflowRuns = repository.queryWorkflowRuns() + .branch(branch) + .conclusion(conclusion) + .event(GHEvent.PULL_REQUEST) + .list() + .withPageSize(20) + .iterator() + .nextPage(); - GHWorkflowRun workflowRunToDelete = getWorkflowRun(FAST_WORKFLOW_NAME, - MAIN_BRANCH, - Status.COMPLETED, - latestPreexistingWorkflowRunId) - .orElseThrow(() -> new IllegalStateException("We must have a valid workflow run starting from here")); + for (GHWorkflowRun workflowRun : workflowRuns) { + if (workflowRun.getName().equals(workflowName)) { + return Optional.of(workflowRun); + } + } + return Optional.empty(); + } - assertThat(workflowRunToDelete.getId(), notNullValue()); + private static Optional getWorkflowRun(GHRepository repository, + String workflowName, + String branch, + Status status, + long latestPreexistingWorkflowRunId) { + List workflowRuns = repository.queryWorkflowRuns() + .branch(branch) + .status(status) + .event(GHEvent.WORKFLOW_DISPATCH) + .list() + .withPageSize(20) + .iterator() + .nextPage(); - workflowRunToDelete.delete(); + for (GHWorkflowRun workflowRun : workflowRuns) { + if (workflowRun.getName().equals(workflowName) && workflowRun.getId() > latestPreexistingWorkflowRunId) { + return Optional.of(workflowRun); + } + } + return Optional.empty(); + } + private static Status getWorkflowRunStatus(GHRepository repository, long workflowRunId) { try { - repo.getWorkflowRun(workflowRunToDelete.getId()); - fail("The workflow " + workflowRunToDelete.getId() + " should have been deleted."); - } catch (GHFileNotFoundException e) { - // success + return repository.getWorkflowRun(workflowRunId).getStatus(); + } catch (IOException e) { + throw new IllegalStateException("Unable to get workflow run status", e); } } + private GHRepository repo; + /** - * Test search on branch. - * - * @throws IOException - * Signals that an I/O exception has occurred. + * Create default GHWorkflowRunTest instance */ - @Test - public void testSearchOnBranch() throws IOException { - GHWorkflow workflow = repo.getWorkflow(FAST_WORKFLOW_PATH); - - long latestPreexistingWorkflowRunId = getLatestPreexistingWorkflowRunId(); - - workflow.dispatch(SECOND_BRANCH); - - await((nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, - FAST_WORKFLOW_NAME, - SECOND_BRANCH, - Status.COMPLETED, - latestPreexistingWorkflowRunId).isPresent()); - - GHWorkflowRun workflowRun = getWorkflowRun(FAST_WORKFLOW_NAME, - SECOND_BRANCH, - Status.COMPLETED, - latestPreexistingWorkflowRunId) - .orElseThrow(() -> new IllegalStateException("We must have a valid workflow run starting from here")); - - assertThat(workflowRun.getWorkflowId(), equalTo(workflow.getId())); - assertThat(workflowRun.getHeadBranch(), equalTo(SECOND_BRANCH)); - assertThat(workflowRun.getEvent(), equalTo(GHEvent.WORKFLOW_DISPATCH)); - assertThat(workflowRun.getStatus(), equalTo(Status.COMPLETED)); - assertThat(workflowRun.getConclusion(), equalTo(Conclusion.SUCCESS)); + public GHWorkflowRunTest() { } /** - * Test search on created and head sha. + * Sets the up. * - * @throws IOException - * Signals that an I/O exception has occurred. + * @throws Exception + * the exception */ - @Test - public void testSearchOnCreatedAndHeadSha() throws IOException { - GHWorkflow workflow = repo.getWorkflow(FAST_WORKFLOW_PATH); - - long latestPreexistingWorkflowRunId = getLatestPreexistingWorkflowRunId(); - - Instant before = Instant.parse("2024-02-09T10:19:00.00Z"); - - String mainBranchHeadSha = repo.getBranch(MAIN_BRANCH).getSHA1(); - String secondBranchHeadSha = repo.getBranch(SECOND_BRANCH).getSHA1(); - - workflow.dispatch(MAIN_BRANCH); - workflow.dispatch(SECOND_BRANCH); - - await((nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, - FAST_WORKFLOW_NAME, - MAIN_BRANCH, - Status.COMPLETED, - latestPreexistingWorkflowRunId).isPresent()); - await((nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, - FAST_WORKFLOW_NAME, - SECOND_BRANCH, - Status.COMPLETED, - latestPreexistingWorkflowRunId).isPresent()); - - List mainBranchHeadShaWorkflowRuns = repo.queryWorkflowRuns() - .headSha(mainBranchHeadSha) - .created(">=" + before.toString()) - .list() - .toList(); - List secondBranchHeadShaWorkflowRuns = repo.queryWorkflowRuns() - .headSha(secondBranchHeadSha) - .created(">=" + before.toString()) - .list() - .toList(); - - assertThat(mainBranchHeadShaWorkflowRuns, hasSize(greaterThanOrEqualTo(1))); - assertThat(mainBranchHeadShaWorkflowRuns, everyItem(hasProperty("headSha", equalTo(mainBranchHeadSha)))); - // Ideally, we would use everyItem() but the bridge method is in the way - for (GHWorkflowRun workflowRun : mainBranchHeadShaWorkflowRuns) { - assertThat(workflowRun.getCreatedAt(), greaterThanOrEqualTo(Date.from(before))); - } - - assertThat(secondBranchHeadShaWorkflowRuns, hasSize(greaterThanOrEqualTo(1))); - assertThat(secondBranchHeadShaWorkflowRuns, everyItem(hasProperty("headSha", equalTo(secondBranchHeadSha)))); - // Ideally, we would use everyItem() but the bridge method is in the way - for (GHWorkflowRun workflowRun : secondBranchHeadShaWorkflowRuns) { - assertThat(workflowRun.getCreatedAt(), greaterThanOrEqualTo(Date.from(before))); - } - - List mainBranchHeadShaWorkflowRunsBefore = repo.queryWorkflowRuns() - .headSha(repo.getBranch(MAIN_BRANCH).getSHA1()) - .created("<" + before.toString()) - .list() - .toList(); - // Ideally, we would use that but the bridge method is causing issues - // assertThat(mainBranchHeadShaWorkflowRunsBefore, everyItem(hasProperty("createdAt", - // lessThan(Date.from(before))))); - for (GHWorkflowRun workflowRun : mainBranchHeadShaWorkflowRunsBefore) { - assertThat(workflowRun.getCreatedAt(), lessThan(Date.from(before))); - } + @Before + public void setUp() throws Exception { + repo = gitHub.getRepository(REPO_NAME); } /** - * Test logs. + * Test approval. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testLogs() throws IOException { - GHWorkflow workflow = repo.getWorkflow(FAST_WORKFLOW_PATH); - - long latestPreexistingWorkflowRunId = getLatestPreexistingWorkflowRunId(); + public void testApproval() throws IOException { + List pullRequests = repo.queryPullRequests() + .base(MAIN_BRANCH) + .sort(Sort.CREATED) + .direction(GHDirection.DESC) + .state(GHIssueState.OPEN) + .list() + .toList(); - workflow.dispatch(MAIN_BRANCH); + assertThat(pullRequests.size(), greaterThanOrEqualTo(1)); + GHPullRequest pullRequest = pullRequests.get(0); - await((nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, - FAST_WORKFLOW_NAME, - MAIN_BRANCH, - Status.COMPLETED, - latestPreexistingWorkflowRunId).isPresent()); + await("Waiting for workflow run to be pending", + (nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, + FAST_WORKFLOW_NAME, + MAIN_BRANCH, + Conclusion.ACTION_REQUIRED).isPresent()); - GHWorkflowRun workflowRun = getWorkflowRun(FAST_WORKFLOW_NAME, - MAIN_BRANCH, - Status.COMPLETED, - latestPreexistingWorkflowRunId) + GHWorkflowRun workflowRun = getWorkflowRun(FAST_WORKFLOW_NAME, MAIN_BRANCH, Conclusion.ACTION_REQUIRED) .orElseThrow(() -> new IllegalStateException("We must have a valid workflow run starting from here")); - List logsArchiveEntries = new ArrayList<>(); - String fullLogContent = workflowRun - .downloadLogs(getLogArchiveInputStreamFunction("1_build.txt", logsArchiveEntries)); - - assertThat(logsArchiveEntries, hasItems("1_build.txt", "build/9_Complete job.txt")); - assertThat(fullLogContent, containsString("Hello, world!")); + workflowRun.approve(); - workflowRun.deleteLogs(); + await("Waiting for workflow run to be approved", + (nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, + FAST_WORKFLOW_NAME, + pullRequest.getHead().getRef(), + Conclusion.SUCCESS).isPresent()); - try { - workflowRun.downloadLogs((is) -> ""); - fail("Downloading logs should not be possible as they were deleted"); - } catch (GHFileNotFoundException e) { - assertThat(e.getMessage(), containsString("Not Found")); - } + workflowRun = repo.getWorkflowRun(workflowRun.getId()); + + assertThat(workflowRun.getConclusion(), is(Conclusion.SUCCESS)); } /** @@ -393,8 +295,14 @@ public void testArtifacts() throws IOException { checkArtifactProperties(artifacts.get(0), "artifact1"); checkArtifactProperties(artifacts.get(1), "artifact2"); + Logger clientLogger = Logger.getLogger(GitHubClient.class.getName()); + // Test download from upload-artifact@v3 infrastructure String artifactContent = artifacts.get(0).download((is) -> { + // At finest log level, all body responses are byte arrays. + if (clientLogger.getLevel() != Level.FINEST) { + assertThat(is, not(isA(ByteArrayInputStream.class))); + } try (ZipInputStream zis = new ZipInputStream(is)) { StringBuilder sb = new StringBuilder(); @@ -462,6 +370,134 @@ public void testArtifacts() throws IOException { } } + /** + * Test cancel and rerun. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testCancelAndRerun() throws IOException { + GHWorkflow workflow = repo.getWorkflow(SLOW_WORKFLOW_PATH); + + long latestPreexistingWorkflowRunId = getLatestPreexistingWorkflowRunId(); + + workflow.dispatch(MAIN_BRANCH); + + // now that we have triggered the workflow run, we will wait until it's in progress and then cancel it + await((nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, + SLOW_WORKFLOW_NAME, + MAIN_BRANCH, + Status.IN_PROGRESS, + latestPreexistingWorkflowRunId).isPresent()); + + GHWorkflowRun workflowRun = getWorkflowRun(SLOW_WORKFLOW_NAME, + MAIN_BRANCH, + Status.IN_PROGRESS, + latestPreexistingWorkflowRunId) + .orElseThrow(() -> new IllegalStateException("We must have a valid workflow run starting from here")); + + assertThat(workflowRun.getId(), notNullValue()); + + workflowRun.cancel(); + long cancelledWorkflowRunId = workflowRun.getId(); + + // let's wait until it's completed + await((nonRecordingRepo) -> getWorkflowRunStatus(nonRecordingRepo, cancelledWorkflowRunId) == Status.COMPLETED); + + // let's check that it has been properly cancelled + workflowRun = repo.getWorkflowRun(cancelledWorkflowRunId); + assertThat(workflowRun.getConclusion(), equalTo(Conclusion.CANCELLED)); + + // now let's rerun it + workflowRun.rerun(); + + // let's check that it has been rerun + await((nonRecordingRepo) -> getWorkflowRunStatus(nonRecordingRepo, + cancelledWorkflowRunId) == Status.IN_PROGRESS); + + // cancel it again + workflowRun.cancel(); + } + + /** + * Test delete. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testDelete() throws IOException { + GHWorkflow workflow = repo.getWorkflow(FAST_WORKFLOW_PATH); + + long latestPreexistingWorkflowRunId = getLatestPreexistingWorkflowRunId(); + + workflow.dispatch(MAIN_BRANCH); + + await((nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, + FAST_WORKFLOW_NAME, + MAIN_BRANCH, + Status.COMPLETED, + latestPreexistingWorkflowRunId).isPresent()); + + GHWorkflowRun workflowRunToDelete = getWorkflowRun(FAST_WORKFLOW_NAME, + MAIN_BRANCH, + Status.COMPLETED, + latestPreexistingWorkflowRunId) + .orElseThrow(() -> new IllegalStateException("We must have a valid workflow run starting from here")); + + assertThat(workflowRunToDelete.getId(), notNullValue()); + + workflowRunToDelete.delete(); + + try { + repo.getWorkflowRun(workflowRunToDelete.getId()); + fail("The workflow " + workflowRunToDelete.getId() + " should have been deleted."); + } catch (GHFileNotFoundException e) { + // success + } + } + + /** + * Test force cancel a run. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testForceCancel() throws IOException { + GHWorkflow workflow = repo.getWorkflow(SLOW_WORKFLOW_PATH); + + long latestPreexistingWorkflowRunId = getLatestPreexistingWorkflowRunId(); + + workflow.dispatch(MAIN_BRANCH); + + // now that we have triggered the workflow run, we will wait until it's in progress and then force cancel it + await((nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, + SLOW_WORKFLOW_NAME, + MAIN_BRANCH, + Status.IN_PROGRESS, + latestPreexistingWorkflowRunId).isPresent()); + + GHWorkflowRun workflowRun = getWorkflowRun(SLOW_WORKFLOW_NAME, + MAIN_BRANCH, + Status.IN_PROGRESS, + latestPreexistingWorkflowRunId) + .orElseThrow(() -> new IllegalStateException("We must have a valid workflow run starting from here")); + + assertThat(workflowRun.getId(), notNullValue()); + + workflowRun.forceCancel(); + long cancelledWorkflowRunId = workflowRun.getId(); + + // let's wait until it's completed + await((nonRecordingRepo) -> getWorkflowRunStatus(nonRecordingRepo, cancelledWorkflowRunId) == Status.COMPLETED); + + // let's check that it has been properly cancelled + workflowRun = repo.getWorkflowRun(cancelledWorkflowRunId); + assertThat(workflowRun.getConclusion(), equalTo(Conclusion.CANCELLED)); + } + /** * Test jobs. * @@ -506,54 +542,230 @@ public void testJobs() throws IOException { fullLogContent = job2.downloadLogs(getLogTextInputStreamFunction()); assertThat(fullLogContent, containsString("Hello from job2!")); - // while we have a job around, test GHRepository#getWorkflowJob(id) - GHWorkflowJob job1ById = repo.getWorkflowJob(job1.getId()); - checkJobProperties(workflowRun.getId(), job1ById, "job1"); + // while we have a job around, test GHRepository#getWorkflowJob(id) + GHWorkflowJob job1ById = repo.getWorkflowJob(job1.getId()); + checkJobProperties(workflowRun.getId(), job1ById, "job1"); + + // Also test listAllJobs() works correctly + List allJobs = workflowRun.listAllJobs().withPageSize(10).iterator().nextPage(); + assertThat(allJobs.size(), greaterThanOrEqualTo(2)); + } + + /** + * Test logs. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testLogs() throws IOException { + GHWorkflow workflow = repo.getWorkflow(FAST_WORKFLOW_PATH); + + long latestPreexistingWorkflowRunId = getLatestPreexistingWorkflowRunId(); + + workflow.dispatch(MAIN_BRANCH); + + await((nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, + FAST_WORKFLOW_NAME, + MAIN_BRANCH, + Status.COMPLETED, + latestPreexistingWorkflowRunId).isPresent()); + + GHWorkflowRun workflowRun = getWorkflowRun(FAST_WORKFLOW_NAME, + MAIN_BRANCH, + Status.COMPLETED, + latestPreexistingWorkflowRunId) + .orElseThrow(() -> new IllegalStateException("We must have a valid workflow run starting from here")); + + List logsArchiveEntries = new ArrayList<>(); + String fullLogContent = workflowRun + .downloadLogs(getLogArchiveInputStreamFunction("1_build.txt", logsArchiveEntries)); + + assertThat(logsArchiveEntries, hasItems("1_build.txt", "build/9_Complete job.txt")); + assertThat(fullLogContent, containsString("Hello, world!")); + + workflowRun.deleteLogs(); + + try { + workflowRun.downloadLogs((is) -> ""); + fail("Downloading logs should not be possible as they were deleted"); + } catch (GHFileNotFoundException e) { + assertThat(e.getMessage(), containsString("Not Found")); + } + } + + /** + * Test manual run and basic information. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testManualRunAndBasicInformation() throws IOException { + GHWorkflow workflow = repo.getWorkflow(FAST_WORKFLOW_PATH); + + long latestPreexistingWorkflowRunId = getLatestPreexistingWorkflowRunId(); + + workflow.dispatch(MAIN_BRANCH); + + await((nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, + FAST_WORKFLOW_NAME, + MAIN_BRANCH, + Status.COMPLETED, + latestPreexistingWorkflowRunId).isPresent()); + + GHWorkflowRun workflowRun = getWorkflowRun(FAST_WORKFLOW_NAME, + MAIN_BRANCH, + Status.COMPLETED, + latestPreexistingWorkflowRunId) + .orElseThrow(() -> new IllegalStateException("We must have a valid workflow run starting from here")); + + assertThat(workflowRun.getWorkflowId(), equalTo(workflow.getId())); + assertThat(workflowRun.getId(), notNullValue()); + assertThat(workflowRun.getNodeId(), notNullValue()); + assertThat(workflowRun.getRepository().getFullName(), equalTo(REPO_NAME)); + assertThat(workflowRun.getUrl().getPath(), containsString("/actions/runs/")); + assertThat(workflowRun.getHtmlUrl().getPath(), containsString("/actions/runs/")); + assertThat(workflowRun.getJobsUrl().getPath(), endsWith("/jobs")); + assertThat(workflowRun.getLogsUrl().getPath(), endsWith("/logs")); + assertThat(workflowRun.getCheckSuiteUrl().getPath(), containsString("/check-suites/")); + assertThat(workflowRun.getArtifactsUrl().getPath(), endsWith("/artifacts")); + assertThat(workflowRun.getCancelUrl().getPath(), endsWith("/cancel")); + assertThat(workflowRun.getRerunUrl().getPath(), endsWith("/rerun")); + assertThat(workflowRun.getWorkflowUrl().getPath(), containsString("/actions/workflows/")); + assertThat(workflowRun.getHeadBranch(), equalTo(MAIN_BRANCH)); + assertThat(workflowRun.getHeadCommit().getId(), notNullValue()); + assertThat(workflowRun.getHeadCommit().getTreeId(), notNullValue()); + assertThat(workflowRun.getHeadCommit().getMessage(), notNullValue()); + assertThat(workflowRun.getHeadCommit().getTimestamp(), notNullValue()); + assertThat(workflowRun.getHeadCommit().getAuthor().getEmail(), notNullValue()); + assertThat(workflowRun.getHeadCommit().getCommitter().getEmail(), notNullValue()); + assertThat(workflowRun.getEvent(), equalTo(GHEvent.WORKFLOW_DISPATCH)); + assertThat(workflowRun.getStatus(), equalTo(Status.COMPLETED)); + assertThat(workflowRun.getConclusion(), equalTo(Conclusion.SUCCESS)); + assertThat(workflowRun.getHeadSha(), notNullValue()); + assertThat(workflowRun.getActor(), hasProperty("login", equalTo("octocat"))); + assertThat(workflowRun.getTriggeringActor(), hasProperty("login", equalTo("octocat_trigger"))); + } + + /** + * Test rerun variants. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testRerunVariants() throws IOException { + GHWorkflowRun workflowRun = repo.getWorkflowRun(686036126L); + + assertThat(workflowRun.getId(), is(686036126L)); + + workflowRun.rerunFailedJobs(); + workflowRun.rerunFailedJobs(true); + workflowRun.rerun(true); + workflowRun.rerun(); + } + + /** + * Test search on branch. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testSearchOnBranch() throws IOException { + GHWorkflow workflow = repo.getWorkflow(FAST_WORKFLOW_PATH); + + long latestPreexistingWorkflowRunId = getLatestPreexistingWorkflowRunId(); + + workflow.dispatch(SECOND_BRANCH); + + await((nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, + FAST_WORKFLOW_NAME, + SECOND_BRANCH, + Status.COMPLETED, + latestPreexistingWorkflowRunId).isPresent()); - // Also test listAllJobs() works correctly - List allJobs = workflowRun.listAllJobs().withPageSize(10).iterator().nextPage(); - assertThat(allJobs.size(), greaterThanOrEqualTo(2)); + GHWorkflowRun workflowRun = getWorkflowRun(FAST_WORKFLOW_NAME, + SECOND_BRANCH, + Status.COMPLETED, + latestPreexistingWorkflowRunId) + .orElseThrow(() -> new IllegalStateException("We must have a valid workflow run starting from here")); + + assertThat(workflowRun.getWorkflowId(), equalTo(workflow.getId())); + assertThat(workflowRun.getHeadBranch(), equalTo(SECOND_BRANCH)); + assertThat(workflowRun.getEvent(), equalTo(GHEvent.WORKFLOW_DISPATCH)); + assertThat(workflowRun.getStatus(), equalTo(Status.COMPLETED)); + assertThat(workflowRun.getConclusion(), equalTo(Conclusion.SUCCESS)); } /** - * Test approval. + * Test search on created and head sha. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void testApproval() throws IOException { - List pullRequests = repo.queryPullRequests() - .base(MAIN_BRANCH) - .sort(Sort.CREATED) - .direction(GHDirection.DESC) - .state(GHIssueState.OPEN) - .list() - .toList(); + public void testSearchOnCreatedAndHeadSha() throws IOException { + GHWorkflow workflow = repo.getWorkflow(FAST_WORKFLOW_PATH); - assertThat(pullRequests.size(), greaterThanOrEqualTo(1)); - GHPullRequest pullRequest = pullRequests.get(0); + long latestPreexistingWorkflowRunId = getLatestPreexistingWorkflowRunId(); - await("Waiting for workflow run to be pending", - (nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, - FAST_WORKFLOW_NAME, - MAIN_BRANCH, - Conclusion.ACTION_REQUIRED).isPresent()); + Instant before = Instant.parse("2024-02-09T10:19:00.00Z"); - GHWorkflowRun workflowRun = getWorkflowRun(FAST_WORKFLOW_NAME, MAIN_BRANCH, Conclusion.ACTION_REQUIRED) - .orElseThrow(() -> new IllegalStateException("We must have a valid workflow run starting from here")); + String mainBranchHeadSha = repo.getBranch(MAIN_BRANCH).getSHA1(); + String secondBranchHeadSha = repo.getBranch(SECOND_BRANCH).getSHA1(); - workflowRun.approve(); + workflow.dispatch(MAIN_BRANCH); + workflow.dispatch(SECOND_BRANCH); - await("Waiting for workflow run to be approved", - (nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, - FAST_WORKFLOW_NAME, - pullRequest.getHead().getRef(), - Conclusion.SUCCESS).isPresent()); + await((nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, + FAST_WORKFLOW_NAME, + MAIN_BRANCH, + Status.COMPLETED, + latestPreexistingWorkflowRunId).isPresent()); + await((nonRecordingRepo) -> getWorkflowRun(nonRecordingRepo, + FAST_WORKFLOW_NAME, + SECOND_BRANCH, + Status.COMPLETED, + latestPreexistingWorkflowRunId).isPresent()); - workflowRun = repo.getWorkflowRun(workflowRun.getId()); + List mainBranchHeadShaWorkflowRuns = repo.queryWorkflowRuns() + .headSha(mainBranchHeadSha) + .created(">=" + before.toString()) + .list() + .toList(); + List secondBranchHeadShaWorkflowRuns = repo.queryWorkflowRuns() + .headSha(secondBranchHeadSha) + .created(">=" + before.toString()) + .list() + .toList(); - assertThat(workflowRun.getConclusion(), is(Conclusion.SUCCESS)); + assertThat(mainBranchHeadShaWorkflowRuns, hasSize(greaterThanOrEqualTo(1))); + assertThat(mainBranchHeadShaWorkflowRuns, everyItem(hasProperty("headSha", equalTo(mainBranchHeadSha)))); + // Ideally, we would use everyItem() but the bridge method is in the way + for (GHWorkflowRun workflowRun : mainBranchHeadShaWorkflowRuns) { + assertThat(workflowRun.getCreatedAt(), greaterThanOrEqualTo(before)); + } + + assertThat(secondBranchHeadShaWorkflowRuns, hasSize(greaterThanOrEqualTo(1))); + assertThat(secondBranchHeadShaWorkflowRuns, everyItem(hasProperty("headSha", equalTo(secondBranchHeadSha)))); + // Ideally, we would use everyItem() but the bridge method is in the way + for (GHWorkflowRun workflowRun : secondBranchHeadShaWorkflowRuns) { + assertThat(workflowRun.getCreatedAt(), greaterThanOrEqualTo(before)); + } + + List mainBranchHeadShaWorkflowRunsBefore = repo.queryWorkflowRuns() + .headSha(repo.getBranch(MAIN_BRANCH).getSHA1()) + .created("<" + before) + .list() + .toList(); + // Ideally, we would use that but the bridge method is causing issues + // assertThat(mainBranchHeadShaWorkflowRunsBefore, everyItem(hasProperty("createdAt", + // lessThan(Date.from(before))))); + for (GHWorkflowRun workflowRun : mainBranchHeadShaWorkflowRunsBefore) { + assertThat(workflowRun.getCreatedAt(), lessThan(before)); + } } /** @@ -578,6 +790,10 @@ public void testStartupFailureConclusion() throws IOException { assertThat(list.get(0).getConclusion(), is(Conclusion.STARTUP_FAILURE)); } + private void await(Function condition) throws IOException { + await(null, condition); + } + private void await(String alias, Function condition) throws IOException { if (!mockGitHub.isUseProxy()) { return; @@ -590,34 +806,12 @@ private void await(String alias, Function condition) thro }); } - private void await(Function condition) throws IOException { - await(null, condition); - } - private long getLatestPreexistingWorkflowRunId() { return repo.queryWorkflowRuns().list().withPageSize(1).iterator().next().getId(); } - private static Optional getWorkflowRun(GHRepository repository, - String workflowName, - String branch, - Status status, - long latestPreexistingWorkflowRunId) { - List workflowRuns = repository.queryWorkflowRuns() - .branch(branch) - .status(status) - .event(GHEvent.WORKFLOW_DISPATCH) - .list() - .withPageSize(20) - .iterator() - .nextPage(); - - for (GHWorkflowRun workflowRun : workflowRuns) { - if (workflowRun.getName().equals(workflowName) && workflowRun.getId() > latestPreexistingWorkflowRunId) { - return Optional.of(workflowRun); - } - } - return Optional.empty(); + private Optional getWorkflowRun(String workflowName, String branch, Conclusion conclusion) { + return getWorkflowRun(this.repo, workflowName, branch, conclusion); } private Optional getWorkflowRun(String workflowName, @@ -626,131 +820,4 @@ private Optional getWorkflowRun(String workflowName, long latestPreexistingWorkflowRunId) { return getWorkflowRun(this.repo, workflowName, branch, status, latestPreexistingWorkflowRunId); } - - private static Optional getWorkflowRun(GHRepository repository, - String workflowName, - String branch, - Conclusion conclusion) { - List workflowRuns = repository.queryWorkflowRuns() - .branch(branch) - .conclusion(conclusion) - .event(GHEvent.PULL_REQUEST) - .list() - .withPageSize(20) - .iterator() - .nextPage(); - - for (GHWorkflowRun workflowRun : workflowRuns) { - if (workflowRun.getName().equals(workflowName)) { - return Optional.of(workflowRun); - } - } - return Optional.empty(); - } - - private Optional getWorkflowRun(String workflowName, String branch, Conclusion conclusion) { - return getWorkflowRun(this.repo, workflowName, branch, conclusion); - } - - private static Status getWorkflowRunStatus(GHRepository repository, long workflowRunId) { - try { - return repository.getWorkflowRun(workflowRunId).getStatus(); - } catch (IOException e) { - throw new IllegalStateException("Unable to get workflow run status", e); - } - } - - @SuppressWarnings("resource") - private static InputStreamFunction getLogArchiveInputStreamFunction(String mainLogFileName, - List logsArchiveEntries) { - return (is) -> { - try (ZipInputStream zis = new ZipInputStream(is)) { - StringBuilder sb = new StringBuilder(); - - ZipEntry ze; - while ((ze = zis.getNextEntry()) != null) { - logsArchiveEntries.add(ze.getName()); - if (mainLogFileName.equals(ze.getName())) { - // the scanner has to be kept open to avoid closing zis - Scanner scanner = new Scanner(zis); - while (scanner.hasNextLine()) { - sb.append(scanner.nextLine()).append("\n"); - } - } - } - - return sb.toString(); - } - }; - } - - @SuppressWarnings("resource") - private static InputStreamFunction getLogTextInputStreamFunction() { - return (is) -> { - StringBuilder sb = new StringBuilder(); - Scanner scanner = new Scanner(is); - while (scanner.hasNextLine()) { - sb.append(scanner.nextLine()).append("\n"); - } - return sb.toString(); - }; - } - - private static void checkArtifactProperties(GHArtifact artifact, String artifactName) throws IOException { - assertThat(artifact.getId(), notNullValue()); - assertThat(artifact.getNodeId(), notNullValue()); - assertThat(artifact.getRepository().getFullName(), equalTo(REPO_NAME)); - assertThat(artifact.getName(), is(artifactName)); - assertThat(artifact.getArchiveDownloadUrl().getPath(), containsString("actions/artifacts")); - assertThat(artifact.getCreatedAt(), notNullValue()); - assertThat(artifact.getUpdatedAt(), notNullValue()); - assertThat(artifact.getExpiresAt(), notNullValue()); - assertThat(artifact.getSizeInBytes(), greaterThan(0L)); - assertThat(artifact.isExpired(), is(false)); - } - - private static void checkJobProperties(long workflowRunId, GHWorkflowJob job, String jobName) { - assertThat(job.getId(), notNullValue()); - assertThat(job.getNodeId(), notNullValue()); - assertThat(job.getRepository().getFullName(), equalTo(REPO_NAME)); - assertThat(job.getName(), is(jobName)); - assertThat(job.getStartedAt(), notNullValue()); - assertThat(job.getCompletedAt(), notNullValue()); - assertThat(job.getHeadSha(), notNullValue()); - assertThat(job.getStatus(), is(Status.COMPLETED)); - assertThat(job.getConclusion(), is(Conclusion.SUCCESS)); - assertThat(job.getRunId(), is(workflowRunId)); - assertThat(job.getUrl().getPath(), containsString("/actions/jobs/")); - assertThat(job.getHtmlUrl().getPath(), containsString("/runs/" + job.getId())); - assertThat(job.getCheckRunUrl().getPath(), containsString("/check-runs/")); - assertThat(job.getRunnerId(), is(1)); - assertThat(job.getRunnerName(), containsString("my runner")); - assertThat(job.getRunnerGroupId(), is(2)); - assertThat(job.getRunnerGroupName(), containsString("my runner group")); - - // we only test the step we have control over, the others are added by GitHub - Optional step = job.getSteps() - .stream() - .filter(s -> RUN_A_ONE_LINE_SCRIPT_STEP_NAME.equals(s.getName())) - .findFirst(); - if (!step.isPresent()) { - fail("Unable to find " + RUN_A_ONE_LINE_SCRIPT_STEP_NAME + " step"); - } - - Optional labelOptional = job.getLabels().stream().filter(s -> s.equals(UBUNTU_LABEL)).findFirst(); - if (!labelOptional.isPresent()) { - fail("Unable to find " + UBUNTU_LABEL + " label"); - } - - checkStepProperties(step.get(), RUN_A_ONE_LINE_SCRIPT_STEP_NAME, 2); - } - - private static void checkStepProperties(Step step, String name, int number) { - assertThat(step.getName(), is(name)); - assertThat(step.getNumber(), is(number)); - assertThat(step.getStatus(), is(Status.COMPLETED)); - assertThat(step.getConclusion(), is(Conclusion.SUCCESS)); - assertThat(step.getStartedAt(), notNullValue()); - assertThat(step.getCompletedAt(), notNullValue()); - } } diff --git a/src/test/java/org/kohsuke/github/GHWorkflowTest.java b/src/test/java/org/kohsuke/github/GHWorkflowTest.java index be3c1bab9b..836907ae91 100644 --- a/src/test/java/org/kohsuke/github/GHWorkflowTest.java +++ b/src/test/java/org/kohsuke/github/GHWorkflowTest.java @@ -21,16 +21,43 @@ */ public class GHWorkflowTest extends AbstractGitHubWireMockTest { + private static String REPO_NAME = "hub4j-test-org/GHWorkflowTest"; + + private static void checkWorkflowRunProperties(GHWorkflowRun workflowRun, long workflowId) { + assertThat(workflowRun.getWorkflowId(), equalTo(workflowId)); + assertThat(workflowRun.getId(), notNullValue()); + assertThat(workflowRun.getNodeId(), notNullValue()); + assertThat(workflowRun.getRepository().getFullName(), equalTo(REPO_NAME)); + assertThat(workflowRun.getUrl().getPath(), containsString("/actions/runs/")); + assertThat(workflowRun.getHtmlUrl().getPath(), containsString("/actions/runs/")); + assertThat(workflowRun.getJobsUrl().getPath(), endsWith("/jobs")); + assertThat(workflowRun.getLogsUrl().getPath(), endsWith("/logs")); + assertThat(workflowRun.getCheckSuiteUrl().getPath(), containsString("/check-suites/")); + assertThat(workflowRun.getArtifactsUrl().getPath(), endsWith("/artifacts")); + assertThat(workflowRun.getCancelUrl().getPath(), endsWith("/cancel")); + assertThat(workflowRun.getRerunUrl().getPath(), endsWith("/rerun")); + assertThat(workflowRun.getWorkflowUrl().getPath(), containsString("/actions/workflows/")); + assertThat(workflowRun.getHeadBranch(), equalTo("main")); + assertThat(workflowRun.getHeadCommit().getId(), notNullValue()); + assertThat(workflowRun.getHeadCommit().getTreeId(), notNullValue()); + assertThat(workflowRun.getHeadCommit().getMessage(), notNullValue()); + assertThat(workflowRun.getHeadCommit().getTimestamp(), notNullValue()); + assertThat(workflowRun.getHeadCommit().getAuthor().getEmail(), notNullValue()); + assertThat(workflowRun.getHeadCommit().getCommitter().getEmail(), notNullValue()); + assertThat(workflowRun.getEvent(), equalTo(GHEvent.WORKFLOW_DISPATCH)); + assertThat(workflowRun.getStatus(), equalTo(GHWorkflowRun.Status.COMPLETED)); + assertThat(workflowRun.getConclusion(), equalTo(GHWorkflowRun.Conclusion.SUCCESS)); + assertThat(workflowRun.getHeadSha(), notNullValue()); + } + + private GHRepository repo; + /** * Create default GHWorkflowTest instance */ public GHWorkflowTest() { } - private static String REPO_NAME = "hub4j-test-org/GHWorkflowTest"; - - private GHRepository repo; - /** * Cleanup. * @@ -132,6 +159,23 @@ public void testDispatch() throws IOException { .withRequestBody(containing("value"))); } + /** + * Test list workflow runs. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testListWorkflowRuns() throws IOException { + GHWorkflow workflow = repo.getWorkflow("test-workflow.yml"); + + List workflowRuns = workflow.listRuns().toList(); + assertThat(workflowRuns.size(), greaterThan(2)); + + checkWorkflowRunProperties(workflowRuns.get(0), workflow.getId()); + checkWorkflowRunProperties(workflowRuns.get(1), workflow.getId()); + } + /** * Test list workflows. * @@ -156,48 +200,4 @@ public void testListWorkflows() throws IOException { equalTo("/hub4j-test-org/GHWorkflowTest/workflows/test-workflow/badge.svg")); } - /** - * Test list workflow runs. - * - * @throws IOException - * Signals that an I/O exception has occurred. - */ - @Test - public void testListWorkflowRuns() throws IOException { - GHWorkflow workflow = repo.getWorkflow("test-workflow.yml"); - - List workflowRuns = workflow.listRuns().toList(); - assertThat(workflowRuns.size(), greaterThan(2)); - - checkWorkflowRunProperties(workflowRuns.get(0), workflow.getId()); - checkWorkflowRunProperties(workflowRuns.get(1), workflow.getId()); - } - - private static void checkWorkflowRunProperties(GHWorkflowRun workflowRun, long workflowId) { - assertThat(workflowRun.getWorkflowId(), equalTo(workflowId)); - assertThat(workflowRun.getId(), notNullValue()); - assertThat(workflowRun.getNodeId(), notNullValue()); - assertThat(workflowRun.getRepository().getFullName(), equalTo(REPO_NAME)); - assertThat(workflowRun.getUrl().getPath(), containsString("/actions/runs/")); - assertThat(workflowRun.getHtmlUrl().getPath(), containsString("/actions/runs/")); - assertThat(workflowRun.getJobsUrl().getPath(), endsWith("/jobs")); - assertThat(workflowRun.getLogsUrl().getPath(), endsWith("/logs")); - assertThat(workflowRun.getCheckSuiteUrl().getPath(), containsString("/check-suites/")); - assertThat(workflowRun.getArtifactsUrl().getPath(), endsWith("/artifacts")); - assertThat(workflowRun.getCancelUrl().getPath(), endsWith("/cancel")); - assertThat(workflowRun.getRerunUrl().getPath(), endsWith("/rerun")); - assertThat(workflowRun.getWorkflowUrl().getPath(), containsString("/actions/workflows/")); - assertThat(workflowRun.getHeadBranch(), equalTo("main")); - assertThat(workflowRun.getHeadCommit().getId(), notNullValue()); - assertThat(workflowRun.getHeadCommit().getTreeId(), notNullValue()); - assertThat(workflowRun.getHeadCommit().getMessage(), notNullValue()); - assertThat(workflowRun.getHeadCommit().getTimestamp(), notNullValue()); - assertThat(workflowRun.getHeadCommit().getAuthor().getEmail(), notNullValue()); - assertThat(workflowRun.getHeadCommit().getCommitter().getEmail(), notNullValue()); - assertThat(workflowRun.getEvent(), equalTo(GHEvent.WORKFLOW_DISPATCH)); - assertThat(workflowRun.getStatus(), equalTo(GHWorkflowRun.Status.COMPLETED)); - assertThat(workflowRun.getConclusion(), equalTo(GHWorkflowRun.Conclusion.SUCCESS)); - assertThat(workflowRun.getHeadSha(), notNullValue()); - } - } diff --git a/src/test/java/org/kohsuke/github/GitHubConnectionTest.java b/src/test/java/org/kohsuke/github/GitHubConnectionTest.java index 384087d0ed..c79fdb3bd2 100644 --- a/src/test/java/org/kohsuke/github/GitHubConnectionTest.java +++ b/src/test/java/org/kohsuke/github/GitHubConnectionTest.java @@ -32,109 +32,23 @@ public GitHubConnectionTest() { } /** - * Test offline. - */ - @Test - public void testOffline() { - GitHub hub = GitHub.offline(); - assertThat(GitHubRequest.getApiURL(hub.getClient().getApiUrl(), "/test").toString(), - equalTo("https://api.github.invalid/test")); - assertThat(hub.isAnonymous(), is(true)); - try { - hub.getRateLimit(); - fail("Offline instance should always fail"); - } catch (IOException e) { - assertThat(e.getMessage(), equalTo("Offline")); - } - } - - /** - * Test git hub server with http. - * - * @throws Exception - * the exception - */ - @Test - public void testGitHubServerWithHttp() throws Exception { - GitHub hub = GitHub.connectToEnterpriseWithOAuth("http://enterprise.kohsuke.org/api/v3", "bogus", "bogus"); - assertThat(GitHubRequest.getApiURL(hub.getClient().getApiUrl(), "/test").toString(), - equalTo("http://enterprise.kohsuke.org/api/v3/test")); - } - - /** - * Test git hub server with https. - * - * @throws Exception - * the exception - */ - @Test - public void testGitHubServerWithHttps() throws Exception { - GitHub hub = GitHub.connectToEnterpriseWithOAuth("https://enterprise.kohsuke.org/api/v3", "bogus", "bogus"); - assertThat(GitHubRequest.getApiURL(hub.getClient().getApiUrl(), "/test").toString(), - equalTo("https://enterprise.kohsuke.org/api/v3/test")); - } - - /** - * Test git hub server without server. - * - * @throws Exception - * the exception - */ - @Test - public void testGitHubServerWithoutServer() throws Exception { - GitHub hub = GitHub.connect("kohsuke", "bogus"); - assertThat(GitHubRequest.getApiURL(hub.getClient().getApiUrl(), "/test").toString(), - equalTo("https://api.github.com/test")); - } - - /** - * Test git hub builder from environment. - * - * @throws IOException - * Signals that an I/O exception has occurred. + * Test anonymous. */ @Test - public void testGitHubBuilderFromEnvironment() throws IOException { + public void testAnonymous() { // we disable this test for JDK 16+ as the current hacks in setupEnvironment() don't work with JDK 16+ Assume.assumeThat(Double.valueOf(System.getProperty("java.specification.version")), lessThan(16.0)); Map props = new HashMap(); - props.put("endpoint", "bogus endpoint url"); - props.put("oauth", "bogus oauth token string"); - setupEnvironment(props); - GitHubBuilder builder = GitHubBuilder.fromEnvironment(); - - assertThat(builder.endpoint, equalTo("bogus endpoint url")); - - assertThat(builder.authorizationProvider, instanceOf(UserAuthorizationProvider.class)); - assertThat(builder.authorizationProvider.getEncodedAuthorization(), equalTo("token bogus oauth token string")); - assertThat(((UserAuthorizationProvider) builder.authorizationProvider).getLogin(), nullValue()); - - props.put("login", "bogus login"); - setupEnvironment(props); - builder = GitHubBuilder.fromEnvironment(); - - assertThat(builder.authorizationProvider, instanceOf(UserAuthorizationProvider.class)); - assertThat(builder.authorizationProvider.getEncodedAuthorization(), equalTo("token bogus oauth token string")); - assertThat(((UserAuthorizationProvider) builder.authorizationProvider).getLogin(), equalTo("bogus login")); - - props.put("jwt", "bogus jwt token string"); + props.put("endpoint", mockGitHub.apiServer().baseUrl()); setupEnvironment(props); - builder = GitHubBuilder.fromEnvironment(); - - assertThat(builder.authorizationProvider, not(instanceOf(UserAuthorizationProvider.class))); - assertThat(builder.authorizationProvider.getEncodedAuthorization(), equalTo("Bearer bogus jwt token string")); - - // props.put("password", "bogus weak password"); - // setupEnvironment(props); - // builder = GitHubBuilder.fromEnvironment(); - // assertThat(builder.authorizationProvider, instanceOf(UserAuthorizationProvider.class)); - // assertThat(builder.authorizationProvider.getEncodedAuthorization(), - // equalTo("Basic Ym9ndXMgbG9naW46Ym9ndXMgd2VhayBwYXNzd29yZA==")); - // assertThat(((UserAuthorizationProvider) builder.authorizationProvider).getLogin(), equalTo("bogus login")); + // No values present except endpoint + GitHubBuilder builder = GitHubBuilder.fromEnvironment(); + assertThat(builder.endpoint, equalTo(mockGitHub.apiServer().baseUrl())); + assertThat(builder.authorizationProvider, sameInstance(AuthorizationProvider.ANONYMOUS)); } /** @@ -243,56 +157,83 @@ public void testGitHubBuilderFromCredentialsWithPropertyFile() throws IOExceptio } } - private void setupPropertyFile(Map props) throws IOException { - File propertyFile = new File(getTestDirectory(), ".github"); - Properties properties = new Properties(); - properties.putAll(props); - properties.store(new FileOutputStream(propertyFile), ""); - } - - private String getTestDirectory() { - return new File("target").getAbsolutePath(); - } - /** - * Test anonymous. + * Test git hub builder from environment. + * + * @throws IOException + * Signals that an I/O exception has occurred. */ @Test - public void testAnonymous() { + public void testGitHubBuilderFromEnvironment() throws IOException { // we disable this test for JDK 16+ as the current hacks in setupEnvironment() don't work with JDK 16+ Assume.assumeThat(Double.valueOf(System.getProperty("java.specification.version")), lessThan(16.0)); Map props = new HashMap(); - props.put("endpoint", mockGitHub.apiServer().baseUrl()); + props.put("endpoint", "bogus endpoint url"); + props.put("oauth", "bogus oauth token string"); setupEnvironment(props); - - // No values present except endpoint GitHubBuilder builder = GitHubBuilder.fromEnvironment(); - assertThat(builder.endpoint, equalTo(mockGitHub.apiServer().baseUrl())); - assertThat(builder.authorizationProvider, sameInstance(AuthorizationProvider.ANONYMOUS)); + assertThat(builder.endpoint, equalTo("bogus endpoint url")); + + assertThat(builder.authorizationProvider, instanceOf(UserAuthorizationProvider.class)); + assertThat(builder.authorizationProvider.getEncodedAuthorization(), equalTo("token bogus oauth token string")); + assertThat(((UserAuthorizationProvider) builder.authorizationProvider).getLogin(), nullValue()); + + props.put("login", "bogus login"); + setupEnvironment(props); + builder = GitHubBuilder.fromEnvironment(); + + assertThat(builder.authorizationProvider, instanceOf(UserAuthorizationProvider.class)); + assertThat(builder.authorizationProvider.getEncodedAuthorization(), equalTo("token bogus oauth token string")); + assertThat(((UserAuthorizationProvider) builder.authorizationProvider).getLogin(), equalTo("bogus login")); + + props.put("jwt", "bogus jwt token string"); + setupEnvironment(props); + builder = GitHubBuilder.fromEnvironment(); + + assertThat(builder.authorizationProvider, not(instanceOf(UserAuthorizationProvider.class))); + assertThat(builder.authorizationProvider.getEncodedAuthorization(), equalTo("Bearer bogus jwt token string")); + + // props.put("password", "bogus weak password"); + // setupEnvironment(props); + // builder = GitHubBuilder.fromEnvironment(); + + // assertThat(builder.authorizationProvider, instanceOf(UserAuthorizationProvider.class)); + // assertThat(builder.authorizationProvider.getEncodedAuthorization(), + // equalTo("Basic Ym9ndXMgbG9naW46Ym9ndXMgd2VhayBwYXNzd29yZA==")); + // assertThat(((UserAuthorizationProvider) builder.authorizationProvider).getLogin(), equalTo("bogus login")); + } /** - * Test github builder with app installation token. + * Test that GitHub.com GraphQL URL is correctly constructed. * * @throws Exception * the exception */ @Test - public void testGithubBuilderWithAppInstallationToken() throws Exception { - - GitHubBuilder builder = new GitHubBuilder().withAppInstallationToken("bogus app token"); - // assertThat(builder.authorizationProvider, instanceOf(UserAuthorizationProvider.class)); - assertThat(builder.authorizationProvider.getEncodedAuthorization(), equalTo("token bogus app token")); - assertThat(((UserAuthorizationProvider) builder.authorizationProvider).getLogin(), is(emptyString())); + public void testGitHubCloudGraphQLUrl() throws Exception { + GitHub hub = GitHub.connect("bogus", "bogus"); + GitHubRequest request = hub.createGraphQLRequest("test query").build(); + assertThat(request.url().toString(), equalTo("https://api.github.com/graphql")); + } - // test authorization header is set as in the RFC6749 - GitHub github = builder.build(); - // change this to get a request - assertThat(github.getClient().getEncodedAuthorization(), equalTo("token bogus app token")); - assertThat(github.getClient().getLogin(), is(emptyString())); + /** + * Test that GitHub Enterprise GraphQL URL is correctly constructed. + *

+ * GitHub Enterprise Server has REST API at /api/v3 but GraphQL at /api/graphql. + *

+ * + * @throws Exception + * the exception + */ + @Test + public void testGitHubEnterpriseGraphQLUrl() throws Exception { + GitHub hub = GitHub.connectToEnterpriseWithOAuth("https://enterprise.kohsuke.org/api/v3", "bogus", "bogus"); + GitHubRequest request = hub.createGraphQLRequest("test query").build(); + assertThat(request.url().toString(), equalTo("https://enterprise.kohsuke.org/api/graphql")); } /** @@ -349,6 +290,87 @@ public void testGitHubOAuthUserQuery() throws IOException { assertThat(mockGitHub.getRequestCount(), equalTo(1)); } + /** + * Test git hub server with http. + * + * @throws Exception + * the exception + */ + @Test + public void testGitHubServerWithHttp() throws Exception { + GitHub hub = GitHub.connectToEnterpriseWithOAuth("http://enterprise.kohsuke.org/api/v3", "bogus", "bogus"); + assertThat(GitHubRequest.getApiURL(hub.getClient().getApiUrl(), "/test").toString(), + equalTo("http://enterprise.kohsuke.org/api/v3/test")); + } + + /** + * Test git hub server with https. + * + * @throws Exception + * the exception + */ + @Test + public void testGitHubServerWithHttps() throws Exception { + GitHub hub = GitHub.connectToEnterpriseWithOAuth("https://enterprise.kohsuke.org/api/v3", "bogus", "bogus"); + assertThat(GitHubRequest.getApiURL(hub.getClient().getApiUrl(), "/test").toString(), + equalTo("https://enterprise.kohsuke.org/api/v3/test")); + } + + /** + * Test git hub server without server. + * + * @throws Exception + * the exception + */ + @Test + public void testGitHubServerWithoutServer() throws Exception { + GitHub hub = GitHub.connect("kohsuke", "bogus"); + assertThat(GitHubRequest.getApiURL(hub.getClient().getApiUrl(), "/test").toString(), + equalTo("https://api.github.com/test")); + } + + /** + * Test github builder with app installation token. + * + * @throws Exception + * the exception + */ + @Test + public void testGithubBuilderWithAppInstallationToken() throws Exception { + + GitHubBuilder builder = new GitHubBuilder().withAppInstallationToken("bogus app token"); + // assertThat(builder.authorizationProvider, instanceOf(UserAuthorizationProvider.class)); + assertThat(builder.authorizationProvider.getEncodedAuthorization(), equalTo("token bogus app token")); + assertThat(((UserAuthorizationProvider) builder.authorizationProvider).getLogin(), is(emptyString())); + + // test authorization header is set as in the RFC6749 + GitHub github = builder.build(); + // change this to get a request + assertThat(github.getClient().getEncodedAuthorization(), equalTo("token bogus app token")); + assertThat(github.getClient().getLogin(), is(emptyString())); + } + + /** + * Test offline. + */ + @Test + public void testOffline() { + GitHub hub = GitHub.offline(); + assertThat(GitHubRequest.getApiURL(hub.getClient().getApiUrl(), "/test").toString(), + equalTo("https://api.github.invalid/test")); + assertThat(hub.isAnonymous(), is(true)); + try { + hub.getRateLimit(); + fail("Offline instance should always fail"); + } catch (IOException e) { + assertThat(e.getMessage(), equalTo("Offline")); + } + } + + private String getTestDirectory() { + return new File("target").getAbsolutePath(); + } + /* * Copied from StackOverflow: http://stackoverflow.com/a/7201825/2336755 * @@ -390,4 +412,11 @@ private void setupEnvironment(Map newenv) { e1.printStackTrace(); } } + + private void setupPropertyFile(Map props) throws IOException { + File propertyFile = new File(getTestDirectory(), ".github"); + Properties properties = new Properties(); + properties.putAll(props); + properties.store(new FileOutputStream(propertyFile), ""); + } } diff --git a/src/test/java/org/kohsuke/github/GitHubStaticTest.java b/src/test/java/org/kohsuke/github/GitHubStaticTest.java index a00f4c95b9..5db7bc1ead 100644 --- a/src/test/java/org/kohsuke/github/GitHubStaticTest.java +++ b/src/test/java/org/kohsuke/github/GitHubStaticTest.java @@ -6,20 +6,20 @@ import java.net.MalformedURLException; import java.net.URL; -import java.text.SimpleDateFormat; import java.time.Duration; import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoUnit; import java.util.Date; -import java.util.TimeZone; +import java.util.Locale; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.junit.Assert.assertThrows; -import static org.junit.Assert.fail; // TODO: Auto-generated Javadoc /** @@ -30,111 +30,38 @@ public class GitHubStaticTest extends AbstractGitHubWireMockTest { /** - * Create default GitHubStaticTest instance - */ - public GitHubStaticTest() { - } - - /** - * Test parse URL. + * Format instant. * - * @throws Exception - * the exception - */ - @Test - public void testParseURL() throws Exception { - assertThat(GitHubClient.parseURL("https://api.github.com"), equalTo(new URL("https://api.github.com"))); - assertThat(GitHubClient.parseURL(null), nullValue()); - - try { - GitHubClient.parseURL("bogus"); - fail(); - } catch (IllegalStateException e) { - assertThat(e.getMessage(), equalTo("Invalid URL: bogus")); - } - } - - /** - * Test parse instant. + * @param instant + * the instant + * @param format + * the format + * @return the string */ - @Test - public void testParseInstant() { - assertThat(GitHubClient.parseInstant(null), nullValue()); + static String formatInstant(Instant instant, String format) { + return formatZonedInstant(instant, format, "GMT"); } /** - * Test raw url path invalid. + * Format zoned instant. + * + * @param instant + * the instant + * @param format + * the format + * @param timeZone + * the time zone + * @return the string */ - @Test - public void testRawUrlPathInvalid() { - try { - gitHub.createRequest().setRawUrlPath("invalid.path.com"); - fail(); - } catch (GHException e) { - assertThat(e.getMessage(), equalTo("Raw URL must start with 'http'")); - } + static String formatZonedInstant(Instant instant, String format, String timeZone) { + return DateTimeFormatter.ofPattern(format, Locale.ENGLISH) + .format(instant.atZone(ZoneId.of(timeZone, ZoneId.SHORT_IDS))); } /** - * Time round trip. + * Create default GitHubStaticTest instance */ - @Test - public void timeRoundTrip() { - final long stableInstantEpochMilli = 1533721222255L; - Instant instantNow = Instant.ofEpochMilli(stableInstantEpochMilli); - - Date instantSeconds = Date.from(instantNow.truncatedTo(ChronoUnit.SECONDS)); - Date instantMillis = Date.from(instantNow.truncatedTo(ChronoUnit.MILLIS)); - - String instantFormatSlash = formatZonedDate(instantMillis, "yyyy/MM/dd HH:mm:ss ZZZZ", "PST"); - assertThat(instantFormatSlash, equalTo("2018/08/08 02:40:22 -0700")); - - String instantFormatDash = formatDate(instantMillis, "yyyy-MM-dd'T'HH:mm:ss'Z'"); - assertThat(instantFormatDash, equalTo("2018-08-08T09:40:22Z")); - - String instantFormatMillis = formatDate(instantMillis, "yyyy-MM-dd'T'HH:mm:ss.S'Z'"); - assertThat(instantFormatMillis, equalTo("2018-08-08T09:40:22.255Z")); - - String instantFormatMillisZoned = formatZonedDate(instantMillis, "yyyy-MM-dd'T'HH:mm:ss.SXXX", "PST"); - assertThat(instantFormatMillisZoned, equalTo("2018-08-08T02:40:22.255-07:00")); - - String instantSecondsFormatMillis = formatDate(instantSeconds, "yyyy-MM-dd'T'HH:mm:ss.S'Z'"); - assertThat(instantSecondsFormatMillis, equalTo("2018-08-08T09:40:22.0Z")); - - String instantSecondsFormatMillisZoned = formatZonedDate(instantSeconds, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", "PST"); - assertThat(instantSecondsFormatMillisZoned, equalTo("2018-08-08T02:40:22.000-07:00")); - - String instantBadFormat = formatDate(instantMillis, "yy-MM-dd'T'HH:mm'Z'"); - assertThat(instantBadFormat, equalTo("18-08-08T09:40Z")); - - assertThat(GitHubClient.parseDate(GitHubClient.printDate(instantSeconds)), - equalTo(GitHubClient.parseDate(GitHubClient.printDate(instantMillis)))); - assertThat(GitHubClient.printDate(instantSeconds), equalTo("2018-08-08T09:40:22Z")); - assertThat(GitHubClient.printDate(GitHubClient.parseDate(instantFormatMillisZoned)), - equalTo("2018-08-08T09:40:22Z")); - - assertThat(instantSeconds, equalTo(GitHubClient.parseDate(GitHubClient.printDate(instantSeconds)))); - - // printDate will truncate to the nearest second, so it should not be equal - assertThat(instantMillis, not(equalTo(GitHubClient.parseDate(GitHubClient.printDate(instantMillis))))); - - assertThat(instantSeconds, equalTo(GitHubClient.parseDate(instantFormatSlash))); - - assertThat(instantSeconds, equalTo(GitHubClient.parseDate(instantFormatDash))); - - // This parser does not truncate to the nearest second, so it will be equal - assertThat(instantMillis, equalTo(GitHubClient.parseDate(instantFormatMillis))); - assertThat(instantMillis, equalTo(GitHubClient.parseDate(instantFormatMillisZoned))); - - assertThat(instantSeconds, equalTo(GitHubClient.parseDate(instantSecondsFormatMillis))); - assertThat(instantSeconds, equalTo(GitHubClient.parseDate(instantSecondsFormatMillisZoned))); - - try { - GitHubClient.parseDate(instantBadFormat); - fail("Bad time format should throw."); - } catch (DateTimeParseException e) { - assertThat(e.getMessage(), equalTo("Text '" + instantBadFormat + "' could not be parsed at index 0")); - } + public GitHubStaticTest() { } /** @@ -339,6 +266,41 @@ public void testGitHubRateLimitShouldReplaceRateLimit() throws Exception { } + /** + * Test git hub request get api URL. + */ + @Test + public void testGitHubRequest_getApiURL() { + assertThat(GitHubRequest.getApiURL("github.com", "/endpoint").toString(), + equalTo("https://api.github.com/endpoint")); + + // This URL is completely invalid but doesn't throw + assertThat(GitHubRequest.getApiURL("github.com", "//endpoint&?").toString(), + equalTo("https://api.github.com//endpoint&?")); + + assertThat(GitHubRequest.getApiURL("ftp://whoa.github.com", "/endpoint").toString(), + equalTo("ftp://whoa.github.com/endpoint")); + assertThat(GitHubRequest.getApiURL(null, "ftp://api.test.github.com/endpoint").toString(), + equalTo("ftp://api.test.github.com/endpoint")); + + GHException e; + e = Assert.assertThrows(GHException.class, + () -> GitHubRequest.getApiURL("gopher://whoa.github.com", "/endpoint")); + assertThat(e.getMessage(), equalTo("Unable to build GitHub API URL")); + assertThat(e.getCause(), instanceOf(MalformedURLException.class)); + assertThat(e.getCause().getMessage(), equalTo("unknown protocol: gopher")); + + e = Assert.assertThrows(GHException.class, () -> GitHubRequest.getApiURL("bogus", "/endpoint")); + assertThat(e.getCause(), instanceOf(IllegalArgumentException.class)); + assertThat(e.getCause().getMessage(), equalTo("URI is not absolute")); + + e = Assert.assertThrows(GHException.class, + () -> GitHubRequest.getApiURL(null, "gopher://api.test.github.com/endpoint")); + assertThat(e.getCause(), instanceOf(MalformedURLException.class)); + assertThat(e.getCause().getMessage(), equalTo("unknown protocol: gopher")); + + } + /** * Test mapping reader writer. * @@ -383,68 +345,113 @@ public void testMappingReaderWriter() throws Exception { } /** - * Test git hub request get api URL. + * Test parse instant. */ @Test - public void testGitHubRequest_getApiURL() { - assertThat(GitHubRequest.getApiURL("github.com", "/endpoint").toString(), - equalTo("https://api.github.com/endpoint")); - - // This URL is completely invalid but doesn't throw - assertThat(GitHubRequest.getApiURL("github.com", "//endpoint&?").toString(), - equalTo("https://api.github.com//endpoint&?")); - - assertThat(GitHubRequest.getApiURL("ftp://whoa.github.com", "/endpoint").toString(), - equalTo("ftp://whoa.github.com/endpoint")); - assertThat(GitHubRequest.getApiURL(null, "ftp://api.test.github.com/endpoint").toString(), - equalTo("ftp://api.test.github.com/endpoint")); - - GHException e; - e = Assert.assertThrows(GHException.class, - () -> GitHubRequest.getApiURL("gopher://whoa.github.com", "/endpoint")); - assertThat(e.getMessage(), equalTo("Unable to build GitHub API URL")); - assertThat(e.getCause(), instanceOf(MalformedURLException.class)); - assertThat(e.getCause().getMessage(), equalTo("unknown protocol: gopher")); - - e = Assert.assertThrows(GHException.class, () -> GitHubRequest.getApiURL("bogus", "/endpoint")); - assertThat(e.getCause(), instanceOf(IllegalArgumentException.class)); - assertThat(e.getCause().getMessage(), equalTo("URI is not absolute")); + public void testParseInstant() { + assertThat(GitHubClient.parseInstant(null), nullValue()); + } - e = Assert.assertThrows(GHException.class, - () -> GitHubRequest.getApiURL(null, "gopher://api.test.github.com/endpoint")); - assertThat(e.getCause(), instanceOf(MalformedURLException.class)); - assertThat(e.getCause().getMessage(), equalTo("unknown protocol: gopher")); + /** + * Test parse URL. + * + * @throws Exception + * the exception + */ + @Test + public void testParseURL() throws Exception { + assertThat(GitHubClient.parseURL("https://api.github.com"), equalTo(new URL("https://api.github.com"))); + assertThat(GitHubClient.parseURL(null), nullValue()); + try { + GitHubClient.parseURL("bogus"); + fail(); + } catch (IllegalStateException e) { + assertThat(e.getMessage(), equalTo("Invalid URL: bogus")); + } } /** - * Format date. - * - * @param dt - * the dt - * @param format - * the format - * @return the string + * Test raw url path invalid. */ - static String formatDate(Date dt, String format) { - return formatZonedDate(dt, format, "GMT"); + @Test + public void testRawUrlPathInvalid() { + try { + gitHub.createRequest().setRawUrlPath("invalid.path.com"); + fail(); + } catch (GHException e) { + assertThat(e.getMessage(), equalTo("Raw URL must start with 'http'")); + } } /** - * Format zoned date. - * - * @param dt - * the dt - * @param format - * the format - * @param timeZone - * the time zone - * @return the string + * Time round trip. */ - static String formatZonedDate(Date dt, String format, String timeZone) { - SimpleDateFormat df = new SimpleDateFormat(format); - df.setTimeZone(TimeZone.getTimeZone(timeZone)); - return df.format(dt); + @Test + public void timeRoundTrip() { + final long stableInstantEpochMilli = 1533721222255L; + Instant instantNow = Instant.ofEpochMilli(stableInstantEpochMilli); + + Instant instantSeconds = instantNow.truncatedTo(ChronoUnit.SECONDS); + Instant instantMillis = instantNow.truncatedTo(ChronoUnit.MILLIS); + + String instantFormatSlash = formatZonedInstant(instantMillis, "yyyy/MM/dd HH:mm:ss Z", "PST"); + assertThat(instantFormatSlash, equalTo("2018/08/08 02:40:22 -0700")); + + String instantFormatDash = formatInstant(instantMillis, "yyyy-MM-dd'T'HH:mm:ss'Z'"); + assertThat(instantFormatDash, equalTo("2018-08-08T09:40:22Z")); + + String instantFormatMillis = formatInstant(instantMillis, "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + assertThat(instantFormatMillis, equalTo("2018-08-08T09:40:22.255Z")); + + String instantFormatMillisZoned = formatZonedInstant(instantMillis, "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ", "PST"); + assertThat(instantFormatMillisZoned, equalTo("2018-08-08T02:40:22.255-07:00")); + + String instantSecondsFormatMillis = formatInstant(instantSeconds, "yyyy-MM-dd'T'HH:mm:ss.S'Z'"); + assertThat(instantSecondsFormatMillis, equalTo("2018-08-08T09:40:22.0Z")); + + String instantSecondsFormatMillisZoned = formatZonedInstant(instantSeconds, + "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ", + "PST"); + assertThat(instantSecondsFormatMillisZoned, equalTo("2018-08-08T02:40:22.000-07:00")); + + String instantBadFormat = formatInstant(instantMillis, "yy-MM-dd'T'HH:mm'Z'"); + assertThat(instantBadFormat, equalTo("18-08-08T09:40Z")); + + assertThat(GitHubClient.parseInstant(GitHubClient.printInstant(instantSeconds)), + equalTo(GitHubClient.parseInstant(GitHubClient.printInstant(instantMillis)))); + assertThat(GitHubClient.printInstant(instantSeconds), equalTo("2018-08-08T09:40:22Z")); + assertThat(GitHubClient.printInstant(GitHubClient.parseInstant(instantFormatMillisZoned)), + equalTo("2018-08-08T09:40:22Z")); + + assertThat(instantSeconds, equalTo(GitHubClient.parseInstant(GitHubClient.printInstant(instantSeconds)))); + + // printDate will truncate to the nearest second, so it should not be equal + assertThat(instantMillis, not(equalTo(GitHubClient.parseInstant(GitHubClient.printInstant(instantMillis))))); + + assertThat(instantSeconds, equalTo(GitHubClient.parseInstant(instantFormatSlash))); + + assertThat(instantSeconds, equalTo(GitHubClient.parseInstant(instantFormatDash))); + + // This parser does not truncate to the nearest second, so it will be equal + assertThat(instantMillis, equalTo(GitHubClient.parseInstant(instantFormatMillis))); + assertThat(instantMillis, equalTo(GitHubClient.parseInstant(instantFormatMillisZoned))); + + assertThat(instantSeconds, equalTo(GitHubClient.parseInstant(instantSecondsFormatMillis))); + assertThat(instantSeconds, equalTo(GitHubClient.parseInstant(instantSecondsFormatMillisZoned))); + + try { + GitHubClient.parseInstant(instantBadFormat); + fail("Bad time format should throw."); + } catch (DateTimeParseException e) { + assertThat(e.getMessage(), equalTo("Text '" + instantBadFormat + "' could not be parsed at index 0")); + } + + final GitHubBridgeAdapterObject bridge = new GitHubBridgeAdapterObject() { + }; + assertThat(bridge.instantToDate(null, null), nullValue()); + assertThat(bridge.instantToDate(Instant.ofEpochMilli(stableInstantEpochMilli), null), + equalTo(Date.from(instantNow))); } } diff --git a/src/test/java/org/kohsuke/github/GitHubTest.java b/src/test/java/org/kohsuke/github/GitHubTest.java index 2c26f48daf..794eff5297 100644 --- a/src/test/java/org/kohsuke/github/GitHubTest.java +++ b/src/test/java/org/kohsuke/github/GitHubTest.java @@ -24,46 +24,98 @@ public GitHubTest() { } /** - * List users. + * Gets the meta. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void listUsers() throws IOException { - for (GHUser u : Iterables.limit(gitHub.listUsers(), 10)) { - assert u.getName() != null; - // System.out.println(u.getName()); + public void getMeta() throws IOException { + GHMeta meta = gitHub.getMeta(); + assertThat(meta.isVerifiablePasswordAuthentication(), is(true)); + assertThat(meta.getSshKeyFingerprints().size(), equalTo(4)); + assertThat(meta.getSshKeys().size(), equalTo(3)); + assertThat(meta.getApi().size(), equalTo(19)); + assertThat(meta.getGit().size(), equalTo(36)); + assertThat(meta.getHooks().size(), equalTo(4)); + assertThat(meta.getImporter().size(), equalTo(3)); + assertThat(meta.getPages().size(), equalTo(6)); + assertThat(meta.getWeb().size(), equalTo(20)); + assertThat(meta.getPackages().size(), equalTo(25)); + assertThat(meta.getActions().size(), equalTo(1739)); + assertThat(meta.getDependabot().size(), equalTo(3)); + + // Also test examples here + Class[] examples = new Class[]{ ReadOnlyObjects.GHMetaPublic.class, ReadOnlyObjects.GHMetaPackage.class, + ReadOnlyObjects.GHMetaGettersUnmodifiable.class, ReadOnlyObjects.GHMetaGettersFinal.class, + ReadOnlyObjects.GHMetaGettersFinalCreator.class, }; + + for (Class metaClass : examples) { + ReadOnlyObjects.GHMetaExample metaExample = gitHub.createRequest() + .withUrlPath("/meta") + .fetch((Class) metaClass); + assertThat(metaExample.isVerifiablePasswordAuthentication(), is(true)); + assertThat(metaExample.getApi().size(), equalTo(19)); + assertThat(metaExample.getGit().size(), equalTo(36)); + assertThat(metaExample.getHooks().size(), equalTo(4)); + assertThat(metaExample.getImporter().size(), equalTo(3)); + assertThat(metaExample.getPages().size(), equalTo(6)); + assertThat(metaExample.getWeb().size(), equalTo(20)); } } /** - * Gets the repository. + * Gets the my marketplace purchases. * * @throws IOException * Signals that an I/O exception has occurred. */ @Test - public void getRepository() throws IOException { - GHRepository repo = gitHub.getRepository("hub4j/github-api"); + public void getMyMarketplacePurchases() throws IOException { + List userPurchases = gitHub.getMyMarketplacePurchases().toList(); + assertThat(userPurchases.size(), equalTo(2)); - assertThat(repo.getFullName(), equalTo("hub4j/github-api")); + for (GHMarketplaceUserPurchase userPurchase : userPurchases) { + assertThat(userPurchase.isOnFreeTrial(), is(false)); + assertThat(userPurchase.getFreeTrialEndsOn(), nullValue()); + assertThat(userPurchase.getBillingCycle(), equalTo("monthly")); + assertThat(userPurchase.getNextBillingDate(), + equalTo(GitHubClient.parseInstant("2020-01-01T00:00:00.000+13:00"))); + assertThat(userPurchase.getUpdatedAt(), + equalTo(GitHubClient.parseInstant("2019-12-02T00:00:00.000+13:00"))); - GHRepository repo2 = gitHub.getRepositoryById(repo.getId()); - assertThat(repo2.getFullName(), equalTo("hub4j/github-api")); + GHMarketplacePlan plan = userPurchase.getPlan(); + // GHMarketplacePlan - Non-nullable fields + assertThat(plan.getUrl(), notNullValue()); + assertThat(plan.getAccountsUrl(), notNullValue()); + assertThat(plan.getName(), notNullValue()); + assertThat(plan.getDescription(), notNullValue()); + assertThat(plan.getPriceModel(), notNullValue()); + assertThat(plan.getState(), notNullValue()); - try { - gitHub.getRepository("hub4j_github-api"); - fail(); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage(), equalTo("Repository name must be in format owner/repo")); - } + // GHMarketplacePlan - primitive fields + assertThat(plan.getId(), not(0L)); + assertThat(plan.getNumber(), not(0L)); + assertThat(plan.getMonthlyPriceInCents(), greaterThanOrEqualTo(0L)); - try { - gitHub.getRepository("hub4j/github/api"); - fail(); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage(), equalTo("Repository name must be in format owner/repo")); + // GHMarketplacePlan - list + assertThat(plan.getBullets().size(), equalTo(2)); + + GHMarketplaceAccount account = userPurchase.getAccount(); + // GHMarketplaceAccount - Non-nullable fields + assertThat(account.getLogin(), notNullValue()); + assertThat(account.getUrl(), notNullValue()); + assertThat(account.getType(), notNullValue()); + + // GHMarketplaceAccount - primitive fields + assertThat(account.getId(), not(0L)); + + /* logical combination tests */ + // Rationale: organization_billing_email is only set when account type is ORGANIZATION. + if (account.getType() == ORGANIZATION) + assertThat(account.getOrganizationBillingEmail(), notNullValue()); + else + assertThat(account.getOrganizationBillingEmail(), nullValue()); } } @@ -96,6 +148,55 @@ public void getOrgs() throws IOException { assertThat(org, not(sameInstance(org2))); } + /** + * Gets the repository. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void getRepository() throws IOException { + GHRepository repo = gitHub.getRepository("hub4j/github-api"); + + assertThat(repo.getFullName(), equalTo("hub4j/github-api")); + + GHRepository repo2 = gitHub.getRepositoryById(repo.getId()); + assertThat(repo2.getFullName(), equalTo("hub4j/github-api")); + + try { + gitHub.getRepository("hub4j_github-api"); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), equalTo("Repository name must be in format owner/repo")); + } + + try { + gitHub.getRepository("hub4j/github/api"); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), equalTo("Repository name must be in format owner/repo")); + } + } + + /** + * Gzip. + * + * @throws Exception + * the exception + */ + @Test + public void gzip() throws Exception { + + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + + // getResponseHeaderFields is deprecated but we'll use it for testing. + assertThat(org.getResponseHeaderFields(), notNullValue()); + + // WireMock should automatically gzip all responses + assertThat(org.getResponseHeaderFields().get("Content-Encoding").get(0), is("gzip")); + assertThat(org.getResponseHeaderFields().get("Content-eNcoding").get(0), is("gzip")); + } + /** * Verifies that the `type` field is correctly fetched when listing organizations. *

@@ -113,37 +214,16 @@ public void listOrganizationsFetchesType() throws IOException { } /** - * Search users. - */ - @Test - public void searchUsers() { - PagedSearchIterable r = gitHub.searchUsers().q("tom").repos(">42").followers(">1000").list(); - GHUser u = r.iterator().next(); - // System.out.println(u.getName()); - assertThat(u.getId(), notNullValue()); - assertThat(r.getTotalCount(), greaterThan(0)); - } - - /** - * Test list all repositories. + * List users. + * + * @throws IOException + * Signals that an I/O exception has occurred. */ @Test - public void testListAllRepositories() { - Iterator itr = gitHub.listAllPublicRepositories().iterator(); - for (int i = 0; i < 115; i++) { - assertThat(itr.hasNext(), is(true)); - GHRepository r = itr.next(); - // System.out.println(r.getFullName()); - assertThat(r.getUrl(), notNullValue()); - assertThat(r.getId(), not(0L)); - } - - // ensure the iterator throws as expected - try { - itr.remove(); - fail(); - } catch (UnsupportedOperationException e) { - assertThat(e, notNullValue()); + public void listUsers() throws IOException { + for (GHUser u : Iterables.limit(gitHub.listUsers(), 10)) { + assert u.getName() != null; + // System.out.println(u.getName()); } } @@ -164,6 +244,10 @@ public void searchContent() throws Exception { .order(GHDirection.DESC) .list(); GHContent c = r.iterator().next(); + assertThat(c.getGitUrl(), endsWith("/repositories/167174/git/blobs/796fbcc808ca15bbe771f8c9c1a7bab3388f6128")); + assertThat(c.getHtmlUrl(), + endsWith( + "https://github.com/jquery/jquery/blob/a684e6ba836f7c553968d7d026ed7941e1a612d8/src/attributes/classes.js")); // System.out.println(c.getName()); assertThat(c.getDownloadUrl(), notNullValue()); @@ -269,128 +353,33 @@ public void searchContentWithForks() { } /** - * Test list my authorizations. - */ - @Test - public void testListMyAuthorizations() { - PagedIterable list = gitHub.listMyAuthorizations(); - - for (GHAuthorization auth : list) { - assertThat(auth.getAppName(), notNullValue()); - } - } - - /** - * Gets the meta. - * - * @throws IOException - * Signals that an I/O exception has occurred. + * Search users. */ @Test - public void getMeta() throws IOException { - GHMeta meta = gitHub.getMeta(); - assertThat(meta.isVerifiablePasswordAuthentication(), is(true)); - assertThat(meta.getSshKeyFingerprints().size(), equalTo(4)); - assertThat(meta.getSshKeys().size(), equalTo(3)); - assertThat(meta.getApi().size(), equalTo(19)); - assertThat(meta.getGit().size(), equalTo(36)); - assertThat(meta.getHooks().size(), equalTo(4)); - assertThat(meta.getImporter().size(), equalTo(3)); - assertThat(meta.getPages().size(), equalTo(6)); - assertThat(meta.getWeb().size(), equalTo(20)); - assertThat(meta.getPackages().size(), equalTo(25)); - assertThat(meta.getActions().size(), equalTo(1739)); - assertThat(meta.getDependabot().size(), equalTo(3)); - - // Also test examples here - Class[] examples = new Class[]{ ReadOnlyObjects.GHMetaPublic.class, ReadOnlyObjects.GHMetaPackage.class, - ReadOnlyObjects.GHMetaGettersUnmodifiable.class, ReadOnlyObjects.GHMetaGettersFinal.class, - ReadOnlyObjects.GHMetaGettersFinalCreator.class, }; - - for (Class metaClass : examples) { - ReadOnlyObjects.GHMetaExample metaExample = gitHub.createRequest() - .withUrlPath("/meta") - .fetch((Class) metaClass); - assertThat(metaExample.isVerifiablePasswordAuthentication(), is(true)); - assertThat(metaExample.getApi().size(), equalTo(19)); - assertThat(metaExample.getGit().size(), equalTo(36)); - assertThat(metaExample.getHooks().size(), equalTo(4)); - assertThat(metaExample.getImporter().size(), equalTo(3)); - assertThat(metaExample.getPages().size(), equalTo(6)); - assertThat(metaExample.getWeb().size(), equalTo(20)); - } + public void searchUsers() { + PagedSearchIterable r = gitHub.searchUsers().q("tom").repos(">42").followers(">1000").list(); + GHUser u = r.iterator().next(); + // System.out.println(u.getName()); + assertThat(u.getId(), notNullValue()); + assertThat(r.getTotalCount(), greaterThan(0)); } /** - * Gets the my marketplace purchases. + * Test expect GitHub {@link ServiceDownException} * - * @throws IOException - * Signals that an I/O exception has occurred. */ @Test - public void getMyMarketplacePurchases() throws IOException { - List userPurchases = gitHub.getMyMarketplacePurchases().toList(); - assertThat(userPurchases.size(), equalTo(2)); - - for (GHMarketplaceUserPurchase userPurchase : userPurchases) { - assertThat(userPurchase.isOnFreeTrial(), is(false)); - assertThat(userPurchase.getFreeTrialEndsOn(), nullValue()); - assertThat(userPurchase.getBillingCycle(), equalTo("monthly")); - - GHMarketplacePlan plan = userPurchase.getPlan(); - // GHMarketplacePlan - Non-nullable fields - assertThat(plan.getUrl(), notNullValue()); - assertThat(plan.getAccountsUrl(), notNullValue()); - assertThat(plan.getName(), notNullValue()); - assertThat(plan.getDescription(), notNullValue()); - assertThat(plan.getPriceModel(), notNullValue()); - assertThat(plan.getState(), notNullValue()); - - // GHMarketplacePlan - primitive fields - assertThat(plan.getId(), not(0L)); - assertThat(plan.getNumber(), not(0L)); - assertThat(plan.getMonthlyPriceInCents(), greaterThanOrEqualTo(0L)); - - // GHMarketplacePlan - list - assertThat(plan.getBullets().size(), equalTo(2)); - - GHMarketplaceAccount account = userPurchase.getAccount(); - // GHMarketplaceAccount - Non-nullable fields - assertThat(account.getLogin(), notNullValue()); - assertThat(account.getUrl(), notNullValue()); - assertThat(account.getType(), notNullValue()); - - // GHMarketplaceAccount - primitive fields - assertThat(account.getId(), not(0L)); - - /* logical combination tests */ - // Rationale: organization_billing_email is only set when account type is ORGANIZATION. - if (account.getType() == ORGANIZATION) - assertThat(account.getOrganizationBillingEmail(), notNullValue()); - else - assertThat(account.getOrganizationBillingEmail(), nullValue()); + public void testCatchServiceDownException() { + snapshotNotAllowed(); + try { + GHRepository repo = gitHub.getRepository("hub4j-test-org/github-api"); + repo.getFileContent("ghcontent-ro/service-down"); + fail("Exception was expected"); + } catch (IOException e) { + assertThat(e.getClass().getName(), equalToIgnoringCase(ServiceDownException.class.getName())); } } - /** - * Gzip. - * - * @throws Exception - * the exception - */ - @Test - public void gzip() throws Exception { - - GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - - // getResponseHeaderFields is deprecated but we'll use it for testing. - assertThat(org.getResponseHeaderFields(), notNullValue()); - - // WireMock should automatically gzip all responses - assertThat(org.getResponseHeaderFields().get("Content-Encoding").get(0), is("gzip")); - assertThat(org.getResponseHeaderFields().get("Content-eNcoding").get(0), is("gzip")); - } - /** * Test header field name. * @@ -414,18 +403,37 @@ public void testHeaderFieldName() throws Exception { } /** - * Test expect GitHub {@link ServiceDownException} - * + * Test list all repositories. */ @Test - public void testCatchServiceDownException() { - snapshotNotAllowed(); + public void testListAllRepositories() { + Iterator itr = gitHub.listAllPublicRepositories().iterator(); + for (int i = 0; i < 115; i++) { + assertThat(itr.hasNext(), is(true)); + GHRepository r = itr.next(); + // System.out.println(r.getFullName()); + assertThat(r.getUrl(), notNullValue()); + assertThat(r.getId(), not(0L)); + } + + // ensure the iterator throws as expected try { - GHRepository repo = gitHub.getRepository("hub4j-test-org/github-api"); - repo.getFileContent("ghcontent-ro/service-down"); - fail("Exception was expected"); - } catch (IOException e) { - assertThat(e.getClass().getName(), equalToIgnoringCase(ServiceDownException.class.getName())); + itr.remove(); + fail(); + } catch (UnsupportedOperationException e) { + assertThat(e, notNullValue()); + } + } + + /** + * Test list my authorizations. + */ + @Test + public void testListMyAuthorizations() { + PagedIterable list = gitHub.listMyAuthorizations(); + + for (GHAuthorization auth : list) { + assertThat(auth.getAppName(), notNullValue()); } } } diff --git a/src/test/java/org/kohsuke/github/GitHubWireMockRule.java b/src/test/java/org/kohsuke/github/GitHubWireMockRule.java index bd4224f9dc..e78790ed32 100644 --- a/src/test/java/org/kohsuke/github/GitHubWireMockRule.java +++ b/src/test/java/org/kohsuke/github/GitHubWireMockRule.java @@ -39,29 +39,213 @@ */ public class GitHubWireMockRule extends WireMockMultiServerRule { + /** + * A number of modifications are needed as runtime to make responses target the WireMock server and not accidentally + * switch to using the live github servers. + */ + private static class GitHubApiResponseTransformer extends ResponseTransformer { + private final GitHubWireMockRule rule; + + public GitHubApiResponseTransformer(GitHubWireMockRule rule) { + this.rule = rule; + } + + @Override + public String getName() { + return "github-api-url-rewrite"; + } + + @Override + public Response transform(Request request, Response response, FileSource files, Parameters parameters) { + Response.Builder builder = Response.Builder.like(response); + Collection headers = response.getHeaders().all(); + + fixListTraversalHeader(response, headers); + fixLocationHeader(response, headers); + + if ("application/json".equals(response.getHeaders().getContentTypeHeader().mimeTypePart())) { + + String body; + body = getBodyAsString(response, headers); + body = rule.mapToMockGitHub(body); + + builder.body(body); + + } + builder.headers(new HttpHeaders(headers)); + + return builder.build(); + } + + private void fixListTraversalHeader(Response response, Collection headers) { + // Lists are broken up into pages. The Link header contains urls for previous and next pages. + HttpHeader linkHeader = response.getHeaders().getHeader("Link"); + if (linkHeader.isPresent()) { + headers.removeIf(item -> item.keyEquals("Link")); + headers.add(HttpHeader.httpHeader("Link", rule.mapToMockGitHub(linkHeader.firstValue()))); + } + } + + private void fixLocationHeader(Response response, Collection headers) { + // For redirects, the Location header points to the new target. + HttpHeader locationHeader = response.getHeaders().getHeader("Location"); + if (locationHeader.isPresent()) { + String originalLocationHeaderValue = locationHeader.firstValue(); + String rewrittenLocationHeaderValue = rule.mapToMockGitHub(originalLocationHeaderValue); + + headers.removeIf(item -> item.keyEquals("Location")); + + // in the case of the blob.core.windows.net server, we need to keep the original host around + // as the host name is dynamic + // this is a hack as we pass the original host as an additional parameter which will + // end up in the request we push to the GitHub server but that is the best we can do + // given Wiremock's infrastructure + Matcher matcher = BLOB_CORE_WINDOWS_PATTERN.matcher(originalLocationHeaderValue); + if (matcher.find() && rule.isUseProxy()) { + rewrittenLocationHeaderValue += "&" + ORIGINAL_HOST + "=" + matcher.group(1); + } + + headers.add(HttpHeader.httpHeader("Location", rewrittenLocationHeaderValue)); + } + } + + private String getBodyAsString(Response response, Collection headers) { + String body; + if (response.getHeaders().getHeader("Content-Encoding").containsValue("gzip")) { + headers.removeIf(item -> item.keyEquals("Content-Encoding")); + body = unGzipToString(response.getBody()); + } else { + body = response.getBodyAsString(); + } + return body; + } + } + private static class MappingFileDetails { + private static Path getPathWithShortenedFileName(Path filePath, String name, String insertionIndex) { + String extension = FilenameUtils.getExtension(filePath.getFileName().toString()); + // Add an underscore to the start and end for easier pattern matching. + String fileName = "_" + name + "_"; + + // Shorten early segments of the file name + // which tend to be repetative - "repos_hub4j-test-org_{repository}". + // also shorten multiple underscores in these segments + fileName = fileName.replaceAll("^_([a-zA-Z0-9])[^_]+_+([a-zA-Z0-9])[^_]+_+([a-zA-Z0-9])[^_]+_+([^_])", + "_$1_$2_$3_$4"); + fileName = fileName.replaceAll("^_([a-zA-Z0-9])[^_]+_+([a-zA-Z0-9])[^_]+_+([^_])", "_$1_$2_$3"); + + // Any remaining segment that longer the 32 characters, truncate to 8 + fileName = fileName.replaceAll("_([^_]{8})[^_]{23}[^_]+_", "_$1_"); + + // If the file name is still longer than 60 characters, truncate it + fileName = fileName.replaceAll("^_(.{60}).+_$", "_$1_"); + + // Remove outer underscores + fileName = fileName.substring(1, fileName.length() - 1); + Path targetPath = filePath.resolveSibling(insertionIndex + "-" + fileName + "." + extension); + + return targetPath; + } + final Path bodyPath; // body file from the mapping file contents + final Path filePath; + final Path renamedBodyPath; + + final Path renamedFilePath; + + MappingFileDetails(Path filePath, Map parsedObject) { + this.filePath = filePath; + String insertionIndex = Long + .toString(((Double) parsedObject.getOrDefault("insertionIndex", 0.0)).longValue()); + + String name = (String) parsedObject.get("name"); + if (name == null) { + // if name is not present, use url and id to generate a name + Map request = (Map) parsedObject.get("request"); + // ignore + name = ((String) request.get("url")).split("[?]")[0].replaceAll("_", "-").replaceAll("[\\\\/]", "_"); + if (name.startsWith("_")) { + name = name.substring(1); + } + name += "_" + (String) parsedObject.get("id"); + } + + this.renamedFilePath = getPathWithShortenedFileName(this.filePath, name, insertionIndex); + + Map responseObject = (Map) parsedObject.get("response"); + String bodyFileName = responseObject == null ? null : (String) responseObject.get("bodyFileName"); + if (bodyFileName != null) { + this.bodyPath = filePath.getParent().resolveSibling("__files").resolve(bodyFileName); + this.renamedBodyPath = getPathWithShortenedFileName(this.bodyPath, name, insertionIndex); + } else { + this.bodyPath = null; + this.renamedBodyPath = null; + } + } + + void renameFiles() throws IOException { + if (!filePath.equals(renamedFilePath)) { + Files.move(filePath, renamedFilePath); + } + if (bodyPath != null && !bodyPath.equals(renamedBodyPath)) { + Files.move(bodyPath, renamedBodyPath); + } + } + } + private static class ProxyToOriginalHostTransformer extends ResponseDefinitionTransformer { + + private static final String NAME = "proxy-to-original-host"; + + private final GitHubWireMockRule rule; + + private ProxyToOriginalHostTransformer(GitHubWireMockRule rule) { + this.rule = rule; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public ResponseDefinition transform(Request request, + ResponseDefinition responseDefinition, + FileSource files, + Parameters parameters) { + if (!rule.isUseProxy() || !request.queryParameter(ORIGINAL_HOST).isPresent()) { + return responseDefinition; + } + + String originalHost = request.queryParameter(ORIGINAL_HOST).firstValue(); + + return ResponseDefinitionBuilder.like(responseDefinition).proxiedFrom("https://" + originalHost).build(); + } + } + + private final static Pattern ACTIONS_USER_CONTENT_PATTERN = Pattern + .compile("https://pipelines[a-z0-9]*\\.actions\\.githubusercontent\\.com", Pattern.CASE_INSENSITIVE); + private final static Pattern BLOB_CORE_WINDOWS_PATTERN = Pattern + .compile("https://([a-z0-9]*\\.blob\\.core\\.windows\\.net)", Pattern.CASE_INSENSITIVE); + private final static String ORIGINAL_HOST = "originalHost"; + // By default the wiremock tests will run without proxy or taking a snapshot. // The tests will use only the stubbed data and will fail if requests are made for missing data. // You can use the proxy without taking a snapshot while writing and debugging tests. // You cannot take a snapshot without proxying. private final static boolean takeSnapshot = System.getProperty("test.github.takeSnapshot", "false") != "false"; + private final static boolean testWithOrg = System.getProperty("test.github.org", "true") == "true"; + private final static boolean useProxy = takeSnapshot || System.getProperty("test.github.useProxy", "false") != "false"; - private final static Pattern ACTIONS_USER_CONTENT_PATTERN = Pattern - .compile("https://pipelines[a-z0-9]*\\.actions\\.githubusercontent\\.com", Pattern.CASE_INSENSITIVE); - private final static Pattern BLOB_CORE_WINDOWS_PATTERN = Pattern - .compile("https://([a-z0-9]*\\.blob\\.core\\.windows\\.net)", Pattern.CASE_INSENSITIVE); - private final static String ORIGINAL_HOST = "originalHost"; - /** - * Customize record spec. + * Gets the request count. * - * @param customizeRecordSpec - * the customize record spec + * @param server + * the server + * @return the request count */ - public void customizeRecordSpec(Consumer customizeRecordSpec) { - this.customizeRecordSpec = customizeRecordSpec; + public static int getRequestCount(WireMockServer server) { + return server.countRequestsMatching(RequestPatternBuilder.allRequests().build()).getCount(); } private Consumer customizeRecordSpec = null; @@ -96,30 +280,30 @@ public GitHubWireMockRule(WireMockConfiguration options, boolean failOnUnmatched } /** - * Api server. + * Actions user content server. * * @return the wire mock server */ - public WireMockServer apiServer() { - return servers.get("default"); + public WireMockServer actionsUserContentServer() { + return servers.get("actions-user-content"); } /** - * Raw server. + * Api server. * * @return the wire mock server */ - public WireMockServer rawServer() { - return servers.get("raw"); + public WireMockServer apiServer() { + return servers.get("default"); } /** - * Uploads server. + * Actions user content server. * * @return the wire mock server */ - public WireMockServer uploadsServer() { - return servers.get("uploads"); + public WireMockServer blobCoreWindowsNetServer() { + return servers.get("blob-core-windows-net"); } /** @@ -132,30 +316,22 @@ public WireMockServer codeloadServer() { } /** - * Actions user content server. - * - * @return the wire mock server - */ - public WireMockServer actionsUserContentServer() { - return servers.get("actions-user-content"); - } - - /** - * Actions user content server. + * Customize record spec. * - * @return the wire mock server + * @param customizeRecordSpec + * the customize record spec */ - public WireMockServer blobCoreWindowsNetServer() { - return servers.get("blob-core-windows-net"); + public void customizeRecordSpec(Consumer customizeRecordSpec) { + this.customizeRecordSpec = customizeRecordSpec; } /** - * Checks if is use proxy. + * Gets the request count. * - * @return true, if is use proxy + * @return the request count */ - public boolean isUseProxy() { - return GitHubWireMockRule.useProxy; + public int getRequestCount() { + return getRequestCount(apiServer()); } /** @@ -177,213 +353,99 @@ public boolean isTestWithOrg() { } /** - * Initialize servers. - */ - @Override - protected void initializeServers() { - super.initializeServers(); - initializeServer("default", new GitHubApiResponseTransformer(this)); - - // only start non-api servers if we might need them - if (new File(apiServer().getOptions().filesRoot().getPath() + "_raw").exists() || isUseProxy()) { - initializeServer("raw"); - } - if (new File(apiServer().getOptions().filesRoot().getPath() + "_uploads").exists() || isUseProxy()) { - initializeServer("uploads"); - } - - if (new File(apiServer().getOptions().filesRoot().getPath() + "_codeload").exists() || isUseProxy()) { - initializeServer("codeload"); - } - - if (new File(apiServer().getOptions().filesRoot().getPath() + "_actions-user-content").exists() - || isUseProxy()) { - initializeServer("actions-user-content"); - } - - if (new File(apiServer().getOptions().filesRoot().getPath() + "_blob-core-windows-net").exists() - || isUseProxy()) { - initializeServer("blob-core-windows-net", new ProxyToOriginalHostTransformer(this)); - } - } - - /** - * Before. + * Checks if is use proxy. + * + * @return true, if is use proxy */ - @Override - protected void before() { - super.before(); - if (!isUseProxy()) { - return; - } - - this.apiServer().stubFor(proxyAllTo("https://api.github.com").atPriority(100)); - - if (this.rawServer() != null) { - this.rawServer().stubFor(proxyAllTo("https://raw.githubusercontent.com").atPriority(100)); - } - - if (this.uploadsServer() != null) { - this.uploadsServer().stubFor(proxyAllTo("https://uploads.github.com").atPriority(100)); - } - - if (this.codeloadServer() != null) { - this.codeloadServer().stubFor(proxyAllTo("https://codeload.github.com").atPriority(100)); - } - - if (this.actionsUserContentServer() != null) { - this.actionsUserContentServer() - .stubFor(proxyAllTo("https://pipelines.actions.githubusercontent.com").atPriority(100)); - } - - if (this.blobCoreWindowsNetServer() != null) { - this.blobCoreWindowsNetServer() - .stubFor(any(anyUrl()).willReturn(aResponse().withTransformers(ProxyToOriginalHostTransformer.NAME)) - .atPriority(100)); - } + public boolean isUseProxy() { + return GitHubWireMockRule.useProxy; } /** - * After. + * Map to mock git hub. + * + * @param body + * the body + * @return the string */ - @Override - protected void after() { - super.after(); - if (!isTakeSnapshot()) { - return; - } - - recordSnapshot(this.apiServer(), "https://api.github.com", false); - - // For raw server, only fix up mapping files - recordSnapshot(this.rawServer(), "https://raw.githubusercontent.com", true); - - recordSnapshot(this.uploadsServer(), "https://uploads.github.com", false); - - recordSnapshot(this.codeloadServer(), "https://codeload.github.com", true); - - recordSnapshot(this.actionsUserContentServer(), "https://pipelines.actions.githubusercontent.com", true); + @Nonnull + public String mapToMockGitHub(String body) { + body = body.replace("https://api.github.com", this.apiServer().baseUrl()); - recordSnapshot(this.blobCoreWindowsNetServer(), "https://productionresults.blob.core.windows.net", true); - } + body = replaceTargetServerUrl(body, this.rawServer(), "https://raw.githubusercontent.com", "/raw"); - private void recordSnapshot(WireMockServer server, String target, boolean isRawServer) { - if (server != null) { + body = replaceTargetServerUrl(body, this.uploadsServer(), "https://uploads.github.com", "/uploads"); - final RecordSpecBuilder recordSpecBuilder = recordSpec().forTarget(target) - // "If-None-Match" header used for ETag matching for caching connections - .captureHeader("If-None-Match") - // "If-Modified-Since" header used for ETag matching for caching connections - .captureHeader("If-Modified-Since") - .captureHeader("Cache-Control") - // "Accept" header is used to specify previews. If it changes expected data may not be retrieved. - .captureHeader("Accept") - // This is required, or some requests will return data from unexpected stubs - // For example, if you update "title" and "body", and then update just "title" to the same value - // the mock framework will treat those two requests as equivalent, which we do not want. - .chooseBodyMatchTypeAutomatically(true, false, false) - .extractTextBodiesOver(255); + body = replaceTargetServerUrl(body, this.codeloadServer(), "https://codeload.github.com", "/codeload"); - if (customizeRecordSpec != null) { - customizeRecordSpec.accept(recordSpecBuilder); - } + body = replaceTargetServerUrl(body, + this.actionsUserContentServer(), + ACTIONS_USER_CONTENT_PATTERN, + "/actions-user-content"); - server.snapshotRecord(recordSpecBuilder); + body = replaceTargetServerUrl(body, + this.blobCoreWindowsNetServer(), + BLOB_CORE_WINDOWS_PATTERN, + "/blob-core-windows-net"); - // After taking the snapshot, format the output - formatTestResources(new File(server.getOptions().filesRoot().getPath()).toPath(), isRawServer); - } + return body; } /** - * Gets the request count. + * Raw server. * - * @return the request count + * @return the wire mock server */ - public int getRequestCount() { - return getRequestCount(apiServer()); + public WireMockServer rawServer() { + return servers.get("raw"); } /** - * Gets the request count. + * Uploads server. * - * @param server - * the server - * @return the request count + * @return the wire mock server */ - public static int getRequestCount(WireMockServer server) { - return server.countRequestsMatching(RequestPatternBuilder.allRequests().build()).getCount(); + public WireMockServer uploadsServer() { + return servers.get("uploads"); } - private static class MappingFileDetails { - final Path filePath; - final Path bodyPath; // body file from the mapping file contents - final Path renamedFilePath; - final Path renamedBodyPath; - - MappingFileDetails(Path filePath, Map parsedObject) { - this.filePath = filePath; - String insertionIndex = Long - .toString(((Double) parsedObject.getOrDefault("insertionIndex", 0.0)).longValue()); - - String name = (String) parsedObject.get("name"); - if (name == null) { - // if name is not present, use url and id to generate a name - Map request = (Map) parsedObject.get("request"); - // ignore - name = ((String) request.get("url")).split("[?]")[0].replaceAll("_", "-").replaceAll("[\\\\/]", "_"); - if (name.startsWith("_")) { - name = name.substring(1); - } - name += "_" + (String) parsedObject.get("id"); - } - - this.renamedFilePath = getPathWithShortenedFileName(this.filePath, name, insertionIndex); + private void fixJsonContents(Gson g, Path filePath, Path bodyPath, Path renamedBodyPath) throws IOException { + String fileText = new String(Files.readAllBytes(filePath)); + // while recording responses we replaced all github calls localhost + // now we reverse that for storage. + fileText = fileText.replace(this.apiServer().baseUrl(), "https://api.github.com"); - Map responseObject = (Map) parsedObject.get("response"); - String bodyFileName = responseObject == null ? null : (String) responseObject.get("bodyFileName"); - if (bodyFileName != null) { - this.bodyPath = filePath.getParent().resolveSibling("__files").resolve(bodyFileName); - this.renamedBodyPath = getPathWithShortenedFileName(this.bodyPath, name, insertionIndex); - } else { - this.bodyPath = null; - this.renamedBodyPath = null; - } + if (this.rawServer() != null) { + fileText = fileText.replace(this.rawServer().baseUrl(), "https://raw.githubusercontent.com"); } - void renameFiles() throws IOException { - if (!filePath.equals(renamedFilePath)) { - Files.move(filePath, renamedFilePath); - } - if (bodyPath != null && !bodyPath.equals(renamedBodyPath)) { - Files.move(bodyPath, renamedBodyPath); - } + if (this.uploadsServer() != null) { + fileText = fileText.replace(this.uploadsServer().baseUrl(), "https://uploads.github.com"); } - private static Path getPathWithShortenedFileName(Path filePath, String name, String insertionIndex) { - String extension = FilenameUtils.getExtension(filePath.getFileName().toString()); - // Add an underscore to the start and end for easier pattern matching. - String fileName = "_" + name + "_"; - - // Shorten early segments of the file name - // which tend to be repetative - "repos_hub4j-test-org_{repository}". - // also shorten multiple underscores in these segments - fileName = fileName.replaceAll("^_([a-zA-Z0-9])[^_]+_+([a-zA-Z0-9])[^_]+_+([a-zA-Z0-9])[^_]+_+([^_])", - "_$1_$2_$3_$4"); - fileName = fileName.replaceAll("^_([a-zA-Z0-9])[^_]+_+([a-zA-Z0-9])[^_]+_+([^_])", "_$1_$2_$3"); - - // Any remaining segment that longer the 32 characters, truncate to 8 - fileName = fileName.replaceAll("_([^_]{8})[^_]{23}[^_]+_", "_$1_"); + if (this.codeloadServer() != null) { + fileText = fileText.replace(this.codeloadServer().baseUrl(), "https://codeload.github.com"); + } - // If the file name is still longer than 60 characters, truncate it - fileName = fileName.replaceAll("^_(.{60}).+_$", "_$1_"); + if (this.actionsUserContentServer() != null) { + fileText = fileText.replace(this.actionsUserContentServer().baseUrl(), + "https://pipelines.actions.githubusercontent.com"); + } - // Remove outer underscores - fileName = fileName.substring(1, fileName.length() - 1); - Path targetPath = filePath.resolveSibling(insertionIndex + "-" + fileName + "." + extension); + if (this.blobCoreWindowsNetServer() != null) { + fileText = fileText.replace(this.blobCoreWindowsNetServer().baseUrl(), + "https://productionresults.blob.core.windows.net"); + } - return targetPath; + // point body file path to the renamed body file + if (bodyPath != null) { + fileText = fileText.replace(bodyPath.getFileName().toString(), renamedBodyPath.getFileName().toString()); } + + // Can be Array or Map + Object parsedObject = g.fromJson(fileText, Object.class); + String outputFileText = g.toJson(parsedObject); + Files.write(filePath, outputFileText.getBytes()); } private void formatTestResources(Path path, boolean isRawServer) { @@ -465,210 +527,146 @@ public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContex } } - private void fixJsonContents(Gson g, Path filePath, Path bodyPath, Path renamedBodyPath) throws IOException { - String fileText = new String(Files.readAllBytes(filePath)); - // while recording responses we replaced all github calls localhost - // now we reverse that for storage. - fileText = fileText.replace(this.apiServer().baseUrl(), "https://api.github.com"); - - if (this.rawServer() != null) { - fileText = fileText.replace(this.rawServer().baseUrl(), "https://raw.githubusercontent.com"); - } - - if (this.uploadsServer() != null) { - fileText = fileText.replace(this.uploadsServer().baseUrl(), "https://uploads.github.com"); - } + private void recordSnapshot(WireMockServer server, String target, boolean isRawServer) { + if (server != null) { - if (this.codeloadServer() != null) { - fileText = fileText.replace(this.codeloadServer().baseUrl(), "https://codeload.github.com"); - } + final RecordSpecBuilder recordSpecBuilder = recordSpec().forTarget(target) + // "If-None-Match" header used for ETag matching for caching connections + .captureHeader("If-None-Match") + // "If-Modified-Since" header used for ETag matching for caching connections + .captureHeader("If-Modified-Since") + .captureHeader("Cache-Control") + // "Accept" header is used to specify previews. If it changes expected data may not be retrieved. + .captureHeader("Accept") + // This is required, or some requests will return data from unexpected stubs + // For example, if you update "title" and "body", and then update just "title" to the same value + // the mock framework will treat those two requests as equivalent, which we do not want. + .chooseBodyMatchTypeAutomatically(true, false, false) + .extractTextBodiesOver(255); - if (this.actionsUserContentServer() != null) { - fileText = fileText.replace(this.actionsUserContentServer().baseUrl(), - "https://pipelines.actions.githubusercontent.com"); - } + if (customizeRecordSpec != null) { + customizeRecordSpec.accept(recordSpecBuilder); + } - if (this.blobCoreWindowsNetServer() != null) { - fileText = fileText.replace(this.blobCoreWindowsNetServer().baseUrl(), - "https://productionresults.blob.core.windows.net"); - } + server.snapshotRecord(recordSpecBuilder); - // point body file path to the renamed body file - if (bodyPath != null) { - fileText = fileText.replace(bodyPath.getFileName().toString(), renamedBodyPath.getFileName().toString()); + // After taking the snapshot, format the output + formatTestResources(new File(server.getOptions().filesRoot().getPath()).toPath(), isRawServer); } - - // Can be Array or Map - Object parsedObject = g.fromJson(fileText, Object.class); - String outputFileText = g.toJson(parsedObject); - Files.write(filePath, outputFileText.getBytes()); - } - - /** - * Map to mock git hub. - * - * @param body - * the body - * @return the string - */ - @Nonnull - public String mapToMockGitHub(String body) { - body = body.replace("https://api.github.com", this.apiServer().baseUrl()); - - body = replaceTargetServerUrl(body, this.rawServer(), "https://raw.githubusercontent.com", "/raw"); - - body = replaceTargetServerUrl(body, this.uploadsServer(), "https://uploads.github.com", "/uploads"); - - body = replaceTargetServerUrl(body, this.codeloadServer(), "https://codeload.github.com", "/codeload"); - - body = replaceTargetServerUrl(body, - this.actionsUserContentServer(), - ACTIONS_USER_CONTENT_PATTERN, - "/actions-user-content"); - - body = replaceTargetServerUrl(body, - this.blobCoreWindowsNetServer(), - BLOB_CORE_WINDOWS_PATTERN, - "/blob-core-windows-net"); - - return body; } - @NonNull - private String replaceTargetServerUrl(String body, + @NonNull private String replaceTargetServerUrl(String body, WireMockServer wireMockServer, - String rawTarget, + Pattern regexp, String inactiveTarget) { if (wireMockServer != null) { - body = body.replace(rawTarget, wireMockServer.baseUrl()); + body = regexp.matcher(body).replaceAll(wireMockServer.baseUrl()); } else { - body = body.replace(rawTarget, this.apiServer().baseUrl() + inactiveTarget); + body = regexp.matcher(body).replaceAll(this.apiServer().baseUrl() + inactiveTarget); } return body; } - @NonNull - private String replaceTargetServerUrl(String body, + @NonNull private String replaceTargetServerUrl(String body, WireMockServer wireMockServer, - Pattern regexp, + String rawTarget, String inactiveTarget) { if (wireMockServer != null) { - body = regexp.matcher(body).replaceAll(wireMockServer.baseUrl()); + body = body.replace(rawTarget, wireMockServer.baseUrl()); } else { - body = regexp.matcher(body).replaceAll(this.apiServer().baseUrl() + inactiveTarget); + body = body.replace(rawTarget, this.apiServer().baseUrl() + inactiveTarget); } return body; } /** - * A number of modifications are needed as runtime to make responses target the WireMock server and not accidentally - * switch to using the live github servers. + * After. */ - private static class GitHubApiResponseTransformer extends ResponseTransformer { - private final GitHubWireMockRule rule; - - public GitHubApiResponseTransformer(GitHubWireMockRule rule) { - this.rule = rule; + @Override + protected void after() { + super.after(); + if (!isTakeSnapshot()) { + return; } - @Override - public Response transform(Request request, Response response, FileSource files, Parameters parameters) { - Response.Builder builder = Response.Builder.like(response); - Collection headers = response.getHeaders().all(); + recordSnapshot(this.apiServer(), "https://api.github.com", false); - fixListTraversalHeader(response, headers); - fixLocationHeader(response, headers); + // For raw server, only fix up mapping files + recordSnapshot(this.rawServer(), "https://raw.githubusercontent.com", true); - if ("application/json".equals(response.getHeaders().getContentTypeHeader().mimeTypePart())) { + recordSnapshot(this.uploadsServer(), "https://uploads.github.com", false); - String body; - body = getBodyAsString(response, headers); - body = rule.mapToMockGitHub(body); + recordSnapshot(this.codeloadServer(), "https://codeload.github.com", true); - builder.body(body); + recordSnapshot(this.actionsUserContentServer(), "https://pipelines.actions.githubusercontent.com", true); - } - builder.headers(new HttpHeaders(headers)); + recordSnapshot(this.blobCoreWindowsNetServer(), "https://productionresults.blob.core.windows.net", true); + } - return builder.build(); + /** + * Before. + */ + @Override + protected void before() { + super.before(); + if (!isUseProxy()) { + return; } - private String getBodyAsString(Response response, Collection headers) { - String body; - if (response.getHeaders().getHeader("Content-Encoding").containsValue("gzip")) { - headers.removeIf(item -> item.keyEquals("Content-Encoding")); - body = unGzipToString(response.getBody()); - } else { - body = response.getBodyAsString(); - } - return body; - } + this.apiServer().stubFor(proxyAllTo("https://api.github.com").atPriority(100)); - private void fixListTraversalHeader(Response response, Collection headers) { - // Lists are broken up into pages. The Link header contains urls for previous and next pages. - HttpHeader linkHeader = response.getHeaders().getHeader("Link"); - if (linkHeader.isPresent()) { - headers.removeIf(item -> item.keyEquals("Link")); - headers.add(HttpHeader.httpHeader("Link", rule.mapToMockGitHub(linkHeader.firstValue()))); - } + if (this.rawServer() != null) { + this.rawServer().stubFor(proxyAllTo("https://raw.githubusercontent.com").atPriority(100)); } - private void fixLocationHeader(Response response, Collection headers) { - // For redirects, the Location header points to the new target. - HttpHeader locationHeader = response.getHeaders().getHeader("Location"); - if (locationHeader.isPresent()) { - String originalLocationHeaderValue = locationHeader.firstValue(); - String rewrittenLocationHeaderValue = rule.mapToMockGitHub(originalLocationHeaderValue); - - headers.removeIf(item -> item.keyEquals("Location")); + if (this.uploadsServer() != null) { + this.uploadsServer().stubFor(proxyAllTo("https://uploads.github.com").atPriority(100)); + } - // in the case of the blob.core.windows.net server, we need to keep the original host around - // as the host name is dynamic - // this is a hack as we pass the original host as an additional parameter which will - // end up in the request we push to the GitHub server but that is the best we can do - // given Wiremock's infrastructure - Matcher matcher = BLOB_CORE_WINDOWS_PATTERN.matcher(originalLocationHeaderValue); - if (matcher.find() && rule.isUseProxy()) { - rewrittenLocationHeaderValue += "&" + ORIGINAL_HOST + "=" + matcher.group(1); - } + if (this.codeloadServer() != null) { + this.codeloadServer().stubFor(proxyAllTo("https://codeload.github.com").atPriority(100)); + } - headers.add(HttpHeader.httpHeader("Location", rewrittenLocationHeaderValue)); - } + if (this.actionsUserContentServer() != null) { + this.actionsUserContentServer() + .stubFor(proxyAllTo("https://pipelines.actions.githubusercontent.com").atPriority(100)); } - @Override - public String getName() { - return "github-api-url-rewrite"; + if (this.blobCoreWindowsNetServer() != null) { + this.blobCoreWindowsNetServer() + .stubFor(any(anyUrl()).willReturn(aResponse().withTransformers(ProxyToOriginalHostTransformer.NAME)) + .atPriority(100)); } } - private static class ProxyToOriginalHostTransformer extends ResponseDefinitionTransformer { - - private static final String NAME = "proxy-to-original-host"; - - private final GitHubWireMockRule rule; + /** + * Initialize servers. + */ + @Override + protected void initializeServers() { + super.initializeServers(); + initializeServer("default", new GitHubApiResponseTransformer(this)); - private ProxyToOriginalHostTransformer(GitHubWireMockRule rule) { - this.rule = rule; + // only start non-api servers if we might need them + if (new File(apiServer().getOptions().filesRoot().getPath() + "_raw").exists() || isUseProxy()) { + initializeServer("raw"); } - - @Override - public String getName() { - return NAME; + if (new File(apiServer().getOptions().filesRoot().getPath() + "_uploads").exists() || isUseProxy()) { + initializeServer("uploads"); } - @Override - public ResponseDefinition transform(Request request, - ResponseDefinition responseDefinition, - FileSource files, - Parameters parameters) { - if (!rule.isUseProxy() || !request.queryParameter(ORIGINAL_HOST).isPresent()) { - return responseDefinition; - } + if (new File(apiServer().getOptions().filesRoot().getPath() + "_codeload").exists() || isUseProxy()) { + initializeServer("codeload"); + } - String originalHost = request.queryParameter(ORIGINAL_HOST).firstValue(); + if (new File(apiServer().getOptions().filesRoot().getPath() + "_actions-user-content").exists() + || isUseProxy()) { + initializeServer("actions-user-content"); + } - return ResponseDefinitionBuilder.like(responseDefinition).proxiedFrom("https://" + originalHost).build(); + if (new File(apiServer().getOptions().filesRoot().getPath() + "_blob-core-windows-net").exists() + || isUseProxy()) { + initializeServer("blob-core-windows-net", new ProxyToOriginalHostTransformer(this)); } } } diff --git a/src/test/java/org/kohsuke/github/LifecycleTest.java b/src/test/java/org/kohsuke/github/LifecycleTest.java index b518fb6444..efa9335c8f 100644 --- a/src/test/java/org/kohsuke/github/LifecycleTest.java +++ b/src/test/java/org/kohsuke/github/LifecycleTest.java @@ -59,33 +59,15 @@ public void testCreateRepository() throws IOException { deleteAsset(release, asset); } - private void updateAsset(GHRelease release, GHAsset asset) throws IOException { - asset.setLabel("test label"); - assertThat(release.listAssets().toList().get(0).getLabel(), equalTo("test label")); - } - - private void deleteAsset(GHRelease release, GHAsset asset) throws IOException { - asset.delete(); - assertThat(release.listAssets().toList(), is(empty())); - } - - private GHAsset uploadAsset(GHRelease release) throws IOException { - GHAsset asset = release.uploadAsset(new File("LICENSE.txt"), "application/text"); - assertThat(asset, notNullValue()); - List cachedAssets = release.getAssets(); - assertThat(cachedAssets, is(empty())); - List assets = release.listAssets().toList(); - assertThat(assets.size(), equalTo(1)); - assertThat(assets.get(0).getName(), equalTo("LICENSE.txt")); - assertThat(assets.get(0).getSize(), equalTo(1104L)); - assertThat(assets.get(0).getContentType(), equalTo("application/text")); - assertThat(assets.get(0).getState(), equalTo("uploaded")); - assertThat(assets.get(0).getDownloadCount(), equalTo(0L)); - assertThat(assets.get(0).getOwner(), sameInstance(release.getOwner())); - assertThat(assets.get(0).getBrowserDownloadUrl(), - containsString("/temp-testCreateRepository/releases/download/release_tag/LICENSE.txt")); - - return asset; + private File createDummyFile(File repoDir) throws IOException { + File file = new File(repoDir, "testFile-" + System.currentTimeMillis()); + PrintWriter writer = new PrintWriter(new FileWriter(file)); + try { + writer.println("test file"); + } finally { + writer.close(); + } + return file; } private GHRelease createRelease(GHRepository repository) throws IOException { @@ -119,14 +101,32 @@ private void delete(File toDelete) { toDelete.delete(); } - private File createDummyFile(File repoDir) throws IOException { - File file = new File(repoDir, "testFile-" + System.currentTimeMillis()); - PrintWriter writer = new PrintWriter(new FileWriter(file)); - try { - writer.println("test file"); - } finally { - writer.close(); - } - return file; + private void deleteAsset(GHRelease release, GHAsset asset) throws IOException { + asset.delete(); + assertThat(release.listAssets().toList(), is(empty())); + } + + private void updateAsset(GHRelease release, GHAsset asset) throws IOException { + asset.setLabel("test label"); + assertThat(release.listAssets().toList().get(0).getLabel(), equalTo("test label")); + } + + private GHAsset uploadAsset(GHRelease release) throws IOException { + GHAsset asset = release.uploadAsset(new File("LICENSE.txt"), "application/text"); + assertThat(asset, notNullValue()); + List cachedAssets = release.getAssets(); + assertThat(cachedAssets, is(empty())); + List assets = release.listAssets().toList(); + assertThat(assets.size(), equalTo(1)); + assertThat(assets.get(0).getName(), equalTo("LICENSE.txt")); + assertThat(assets.get(0).getSize(), equalTo(1104L)); + assertThat(assets.get(0).getContentType(), equalTo("application/text")); + assertThat(assets.get(0).getState(), equalTo("uploaded")); + assertThat(assets.get(0).getDownloadCount(), equalTo(0L)); + assertThat(assets.get(0).getOwner(), sameInstance(release.getOwner())); + assertThat(assets.get(0).getBrowserDownloadUrl(), + containsString("/temp-testCreateRepository/releases/download/release_tag/LICENSE.txt")); + + return asset; } } diff --git a/src/test/java/org/kohsuke/github/PayloadRule.java b/src/test/java/org/kohsuke/github/PayloadRule.java index dcd41cae0f..3afe64d072 100644 --- a/src/test/java/org/kohsuke/github/PayloadRule.java +++ b/src/test/java/org/kohsuke/github/PayloadRule.java @@ -24,11 +24,11 @@ */ public class PayloadRule implements TestRule { - private final String type; + private String resourceName; private Class testClass; - private String resourceName; + private final String type; /** * Instantiates a new payload rule. @@ -65,24 +65,6 @@ public void evaluate() throws Throwable { }; } - /** - * As input stream. - * - * @return the input stream - * @throws FileNotFoundException - * the file not found exception - */ - public InputStream asInputStream() throws FileNotFoundException { - String name = resourceName.startsWith("/") - ? resourceName + type - : testClass.getSimpleName() + "/" + resourceName + type; - InputStream stream = testClass.getResourceAsStream(name); - if (stream == null) { - throw new FileNotFoundException(String.format("Resource %s from class %s", name, testClass)); - } - return stream; - } - /** * As bytes. * @@ -100,51 +82,45 @@ public byte[] asBytes() throws IOException { } /** - * As string. - * - * @param encoding - * the encoding - * @return the string - * @throws IOException - * Signals that an I/O exception has occurred. - */ - public String asString(Charset encoding) throws IOException { - return new String(asBytes(), encoding.name()); - } - - /** - * As string. + * As input stream. * - * @param encoding - * the encoding - * @return the string - * @throws IOException - * Signals that an I/O exception has occurred. + * @return the input stream + * @throws FileNotFoundException + * the file not found exception */ - public String asString(String encoding) throws IOException { - return new String(asBytes(), encoding); + public InputStream asInputStream() throws FileNotFoundException { + String name = resourceName.startsWith("/") + ? resourceName + type + : testClass.getSimpleName() + "/" + resourceName + type; + InputStream stream = testClass.getResourceAsStream(name); + if (stream == null) { + throw new FileNotFoundException(String.format("Resource %s from class %s", name, testClass)); + } + return stream; } /** - * As string. + * As reader. * - * @return the string - * @throws IOException - * Signals that an I/O exception has occurred. + * @return the reader + * @throws FileNotFoundException + * the file not found exception */ - public String asString() throws IOException { - return new String(asBytes(), Charset.defaultCharset().name()); + public Reader asReader() throws FileNotFoundException { + return new InputStreamReader(asInputStream(), Charset.defaultCharset()); } /** * As reader. * + * @param encoding + * the encoding * @return the reader * @throws FileNotFoundException * the file not found exception */ - public Reader asReader() throws FileNotFoundException { - return new InputStreamReader(asInputStream(), Charset.defaultCharset()); + public Reader asReader(Charset encoding) throws FileNotFoundException { + return new InputStreamReader(asInputStream(), encoding); } /** @@ -175,15 +151,39 @@ public Reader asReader(String encoding) throws IOException { } /** - * As reader. + * As string. + * + * @return the string + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public String asString() throws IOException { + return new String(asBytes(), Charset.defaultCharset().name()); + } + + /** + * As string. * * @param encoding * the encoding - * @return the reader - * @throws FileNotFoundException - * the file not found exception + * @return the string + * @throws IOException + * Signals that an I/O exception has occurred. */ - public Reader asReader(Charset encoding) throws FileNotFoundException { - return new InputStreamReader(asInputStream(), encoding); + public String asString(Charset encoding) throws IOException { + return new String(asBytes(), encoding.name()); + } + + /** + * As string. + * + * @param encoding + * the encoding + * @return the string + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public String asString(String encoding) throws IOException { + return new String(asBytes(), encoding); } } diff --git a/src/test/java/org/kohsuke/github/RateLimitCheckerTest.java b/src/test/java/org/kohsuke/github/RateLimitCheckerTest.java index 8192ce0359..12ce65b2f7 100644 --- a/src/test/java/org/kohsuke/github/RateLimitCheckerTest.java +++ b/src/test/java/org/kohsuke/github/RateLimitCheckerTest.java @@ -17,12 +17,16 @@ */ public class RateLimitCheckerTest extends AbstractGitHubWireMockTest { - /** The rate limit. */ - GHRateLimit rateLimit = null; + private static GHRepository getRepository(GitHub gitHub) throws IOException { + return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); + } /** The previous limit. */ GHRateLimit previousLimit = null; + /** The rate limit. */ + GHRateLimit rateLimit = null; + /** * Instantiates a new rate limit checker test. */ @@ -30,16 +34,6 @@ public RateLimitCheckerTest() { useDefaultGitHub = false; } - /** - * Gets the wire mock options. - * - * @return the wire mock options - */ - @Override - protected WireMockConfiguration getWireMockOptions() { - return super.getWireMockOptions().extensions(templating.newResponseTransformer()); - } - /** * Test git hub rate limit. * @@ -57,6 +51,7 @@ public void testGitHubRateLimit() throws Exception { // Give this a moment Thread.sleep(1000); + // noinspection UseOfObsoleteDateTimeApi templating.testStartDate = new Date(); // ------------------------------------------------------------- // /user gets response with rate limit information @@ -102,6 +97,16 @@ public void testGitHubRateLimit() throws Exception { assertThat(rateLimit.getCore().getRemaining(), equalTo(4601)); } + /** + * Gets the wire mock options. + * + * @return the wire mock options + */ + @Override + protected WireMockConfiguration getWireMockOptions() { + return super.getWireMockOptions().extensions(templating.newResponseTransformer()); + } + /** * Update test rate limit. */ @@ -110,8 +115,4 @@ protected void updateTestRateLimit() { rateLimit = gitHub.lastRateLimit(); } - private static GHRepository getRepository(GitHub gitHub) throws IOException { - return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); - } - } diff --git a/src/test/java/org/kohsuke/github/RateLimitHandlerTest.java b/src/test/java/org/kohsuke/github/RateLimitHandlerTest.java index 5e326cbba3..8781b544ee 100644 --- a/src/test/java/org/kohsuke/github/RateLimitHandlerTest.java +++ b/src/test/java/org/kohsuke/github/RateLimitHandlerTest.java @@ -42,16 +42,6 @@ public RateLimitHandlerTest() { useDefaultGitHub = false; } - /** - * Gets the wire mock options. - * - * @return the wire mock options - */ - @Override - protected WireMockConfiguration getWireMockOptions() { - return super.getWireMockOptions().extensions(templating.newResponseTransformer()); - } - /** * Test handler fail. * @@ -147,25 +137,20 @@ public void onError(@NotNull GitHubConnectorResponse connectorResponse) throws I } /** - * Test the wait logic in the case where the "Date" header field is missing from the response. + * Test handler wait stuck. * - * @throws IOException - * if the code under test throws that exception + * @throws Exception + * the exception */ @Test - public void testHandler_Wait_Missing_Date_Header() throws IOException { + public void testHandler_WaitStuck() throws Exception { // Customized response that templates the date to keep things working snapshotNotAllowed(); gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()) .withRateLimitHandler(new GitHubRateLimitHandler() { - @Override - public void onError(@NotNull GitHubConnectorResponse connectorResponse) throws IOException { - long waitTime = GitHubRateLimitHandler.WAIT.parseWaitTime(connectorResponse); - assertThat(waitTime, equalTo(3 * 1000l)); - - GitHubAbuseLimitHandler.WAIT.onError(connectorResponse); + public void onError(@Nonnull GitHubConnectorResponse connectorResponse) throws IOException { } }) .build(); @@ -173,25 +158,36 @@ public void onError(@NotNull GitHubConnectorResponse connectorResponse) throws I gitHub.getMyself(); assertThat(mockGitHub.getRequestCount(), equalTo(1)); - getTempRepository(); - assertThat(mockGitHub.getRequestCount(), equalTo(3)); + try { + getTempRepository(); + fail(); + } catch (Exception e) { + assertThat(e, instanceOf(GHIOException.class)); + } + + assertThat(mockGitHub.getRequestCount(), equalTo(4)); } /** - * Test handler wait stuck. + * Test the wait logic in the case where the "Date" header field is missing from the response. * - * @throws Exception - * the exception + * @throws IOException + * if the code under test throws that exception */ @Test - public void testHandler_WaitStuck() throws Exception { + public void testHandler_Wait_Missing_Date_Header() throws IOException { // Customized response that templates the date to keep things working snapshotNotAllowed(); gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()) .withRateLimitHandler(new GitHubRateLimitHandler() { + @Override - public void onError(@Nonnull GitHubConnectorResponse connectorResponse) throws IOException { + public void onError(@NotNull GitHubConnectorResponse connectorResponse) throws IOException { + long waitTime = GitHubRateLimitHandler.WAIT.parseWaitTime(connectorResponse); + assertThat(waitTime, equalTo(3 * 1000l)); + + GitHubAbuseLimitHandler.WAIT.onError(connectorResponse); } }) .build(); @@ -199,14 +195,18 @@ public void onError(@Nonnull GitHubConnectorResponse connectorResponse) throws I gitHub.getMyself(); assertThat(mockGitHub.getRequestCount(), equalTo(1)); - try { - getTempRepository(); - fail(); - } catch (Exception e) { - assertThat(e, instanceOf(GHIOException.class)); - } + getTempRepository(); + assertThat(mockGitHub.getRequestCount(), equalTo(3)); + } - assertThat(mockGitHub.getRequestCount(), equalTo(4)); + /** + * Gets the wire mock options. + * + * @return the wire mock options + */ + @Override + protected WireMockConfiguration getWireMockOptions() { + return super.getWireMockOptions().extensions(templating.newResponseTransformer()); } } diff --git a/src/test/java/org/kohsuke/github/RepositoryTrafficTest.java b/src/test/java/org/kohsuke/github/RepositoryTrafficTest.java index 2fd49958b6..16513ca57c 100644 --- a/src/test/java/org/kohsuke/github/RepositoryTrafficTest.java +++ b/src/test/java/org/kohsuke/github/RepositoryTrafficTest.java @@ -9,78 +9,24 @@ import java.util.Iterator; import java.util.List; +import static org.hamcrest.CoreMatchers.equalTo; + // TODO: Auto-generated Javadoc /** * The Class RepositoryTrafficTest. */ public class RepositoryTrafficTest extends AbstractGitHubWireMockTest { - /** - * Create default RepositoryTrafficTest instance - */ - public RepositoryTrafficTest() { - } - - final private String repositoryName = "github-api"; - - @SuppressWarnings("unchecked") - private void checkResponse(T expected, T actual) { - assertThat(actual.getCount(), Matchers.equalTo(expected.getCount())); - assertThat(actual.getUniques(), Matchers.equalTo(expected.getUniques())); - - List expectedList = expected.getDailyInfo(); - List actualList = actual.getDailyInfo(); - Iterator expectedIt; - Iterator actualIt; - - assertThat(actualList.size(), Matchers.equalTo(expectedList.size())); - expectedIt = expectedList.iterator(); - actualIt = actualList.iterator(); - - while (expectedIt.hasNext() && actualIt.hasNext()) { - DailyInfo expectedDailyInfo = expectedIt.next(); - DailyInfo actualDailyInfo = actualIt.next(); - assertThat(actualDailyInfo.getCount(), Matchers.equalTo(expectedDailyInfo.getCount())); - assertThat(actualDailyInfo.getUniques(), Matchers.equalTo(expectedDailyInfo.getUniques())); - assertThat(actualDailyInfo.getTimestamp(), Matchers.equalTo(expectedDailyInfo.getTimestamp())); - } - } - private static GHRepository getRepository(GitHub gitHub) throws IOException { return gitHub.getOrganization("hub4j").getRepository("github-api"); } + final private String repositoryName = "github-api"; + /** - * Test get views. - * - * @throws IOException - * Signals that an I/O exception has occurred. + * Create default RepositoryTrafficTest instance */ - @Test - public void testGetViews() throws IOException { - // Would change all the time - snapshotNotAllowed(); - - GHRepository repository = getRepository(gitHub); - GHRepositoryViewTraffic views = repository.getViewTraffic(); - - GHRepositoryViewTraffic expectedResult = new GHRepositoryViewTraffic(3533, - 616, - Arrays.asList(new GHRepositoryViewTraffic.DailyInfo("2020-02-08T00:00:00Z", 101, 31), - new GHRepositoryViewTraffic.DailyInfo("2020-02-09T00:00:00Z", 92, 22), - new GHRepositoryViewTraffic.DailyInfo("2020-02-10T00:00:00Z", 317, 84), - new GHRepositoryViewTraffic.DailyInfo("2020-02-11T00:00:00Z", 365, 90), - new GHRepositoryViewTraffic.DailyInfo("2020-02-12T00:00:00Z", 428, 78), - new GHRepositoryViewTraffic.DailyInfo("2020-02-13T00:00:00Z", 334, 52), - new GHRepositoryViewTraffic.DailyInfo("2020-02-14T00:00:00Z", 138, 44), - new GHRepositoryViewTraffic.DailyInfo("2020-02-15T00:00:00Z", 76, 13), - new GHRepositoryViewTraffic.DailyInfo("2020-02-16T00:00:00Z", 99, 27), - new GHRepositoryViewTraffic.DailyInfo("2020-02-17T00:00:00Z", 367, 65), - new GHRepositoryViewTraffic.DailyInfo("2020-02-18T00:00:00Z", 411, 76), - new GHRepositoryViewTraffic.DailyInfo("2020-02-19T00:00:00Z", 140, 61), - new GHRepositoryViewTraffic.DailyInfo("2020-02-20T00:00:00Z", 259, 55), - new GHRepositoryViewTraffic.DailyInfo("2020-02-21T00:00:00Z", 406, 66))); - checkResponse(expectedResult, views); + public RepositoryTrafficTest() { } /** @@ -95,6 +41,8 @@ public void testGetClones() throws IOException { snapshotNotAllowed(); GHRepository repository = getRepository(gitHub); + assertThat(repository.getPushedAt(), equalTo(GitHubClient.parseInstant("2020-02-21T21:17:31Z"))); + GHRepositoryCloneTraffic clones = repository.getCloneTraffic(); GHRepositoryCloneTraffic expectedResult = new GHRepositoryCloneTraffic(128, @@ -138,4 +86,60 @@ public void testGetTrafficStatsAccessFailureDueToInsufficientPermissions() throw } catch (HttpException ex) { } } + + /** + * Test get views. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testGetViews() throws IOException { + // Would change all the time + snapshotNotAllowed(); + + GHRepository repository = getRepository(gitHub); + GHRepositoryViewTraffic views = repository.getViewTraffic(); + + GHRepositoryViewTraffic expectedResult = new GHRepositoryViewTraffic(3533, + 616, + Arrays.asList(new GHRepositoryViewTraffic.DailyInfo("2020-02-08T00:00:00Z", 101, 31), + new GHRepositoryViewTraffic.DailyInfo("2020-02-09T00:00:00Z", 92, 22), + new GHRepositoryViewTraffic.DailyInfo("2020-02-10T00:00:00Z", 317, 84), + new GHRepositoryViewTraffic.DailyInfo("2020-02-11T00:00:00Z", 365, 90), + new GHRepositoryViewTraffic.DailyInfo("2020-02-12T00:00:00Z", 428, 78), + new GHRepositoryViewTraffic.DailyInfo("2020-02-13T00:00:00Z", 334, 52), + new GHRepositoryViewTraffic.DailyInfo("2020-02-14T00:00:00Z", 138, 44), + new GHRepositoryViewTraffic.DailyInfo("2020-02-15T00:00:00Z", 76, 13), + new GHRepositoryViewTraffic.DailyInfo("2020-02-16T00:00:00Z", 99, 27), + new GHRepositoryViewTraffic.DailyInfo("2020-02-17T00:00:00Z", 367, 65), + new GHRepositoryViewTraffic.DailyInfo("2020-02-18T00:00:00Z", 411, 76), + new GHRepositoryViewTraffic.DailyInfo("2020-02-19T00:00:00Z", 140, 61), + new GHRepositoryViewTraffic.DailyInfo("2020-02-20T00:00:00Z", 259, 55), + new GHRepositoryViewTraffic.DailyInfo("2020-02-21T00:00:00Z", 406, 66))); + checkResponse(expectedResult, views); + } + + @SuppressWarnings("unchecked") + private void checkResponse(T expected, T actual) { + assertThat(actual.getCount(), Matchers.equalTo(expected.getCount())); + assertThat(actual.getUniques(), Matchers.equalTo(expected.getUniques())); + + List expectedList = expected.getDailyInfo(); + List actualList = actual.getDailyInfo(); + Iterator expectedIt; + Iterator actualIt; + + assertThat(actualList.size(), Matchers.equalTo(expectedList.size())); + expectedIt = expectedList.iterator(); + actualIt = actualList.iterator(); + + while (expectedIt.hasNext() && actualIt.hasNext()) { + DailyInfo expectedDailyInfo = expectedIt.next(); + DailyInfo actualDailyInfo = actualIt.next(); + assertThat(actualDailyInfo.getCount(), Matchers.equalTo(expectedDailyInfo.getCount())); + assertThat(actualDailyInfo.getUniques(), Matchers.equalTo(expectedDailyInfo.getUniques())); + assertThat(actualDailyInfo.getTimestamp(), Matchers.equalTo(expectedDailyInfo.getTimestamp())); + } + } } diff --git a/src/test/java/org/kohsuke/github/RequesterRetryTest.java b/src/test/java/org/kohsuke/github/RequesterRetryTest.java index 81a8b34ec9..5d6865a869 100644 --- a/src/test/java/org/kohsuke/github/RequesterRetryTest.java +++ b/src/test/java/org/kohsuke/github/RequesterRetryTest.java @@ -3,18 +3,17 @@ import okhttp3.ConnectionPool; import okhttp3.OkHttpClient; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.kohsuke.github.connector.GitHubConnector; import org.kohsuke.github.connector.GitHubConnectorRequest; import org.kohsuke.github.connector.GitHubConnectorResponse; +import org.kohsuke.github.connector.GitHubConnectorResponseTest; import org.kohsuke.github.extras.HttpClientGitHubConnector; import org.kohsuke.github.extras.okhttp3.OkHttpGitHubConnector; import java.io.*; -import java.net.URL; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -36,37 +35,171 @@ */ public class RequesterRetryTest extends AbstractGitHubWireMockTest { - private static Logger log = Logger.getLogger(GitHubClient.class.getName()); // matches the logger in the affected - // class - private static OutputStream logCapturingStream; - private static StreamHandler customLogHandler; + /** + * The Interface Thrower. + * + * @param + * the element type + */ + @FunctionalInterface + public interface Thrower { - /** The connection. */ - // HttpURLConnection connection; + /** + * Throw error. + * + * @throws E + * the e + */ + void throwError() throws E; + } + private static class GitHubConnectorResponseWrapper extends GitHubConnectorResponse { - /** The base request count. */ - int baseRequestCount; + private final GitHubConnectorResponse wrapped; + + GitHubConnectorResponseWrapper(GitHubConnectorResponse response) { + super(GitHubConnectorResponseTest.EMPTY_REQUEST, -1, new HashMap<>()); + wrapped = response; + } + + @Nonnull + @Override + public Map> allHeaders() { + return wrapped.allHeaders(); + } + + @NotNull @Override + public InputStream bodyStream() throws IOException { + return wrapped.bodyStream(); + } + + @Override + public void close() throws IOException { + wrapped.close(); + } + + @CheckForNull + @Override + public String header(String name) { + return wrapped.header(name); + } + + @Nonnull + @Override + public GitHubConnectorRequest request() { + return wrapped.request(); + } + @Override + public int statusCode() { + return wrapped.statusCode(); + } + + @Override + protected InputStream rawBodyStream() throws IOException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'rawBodyStream'"); + } + } /** - * Instantiates a new requester retry test. + * The Class InputStreamThrowingHttpConnector. + * + * @param + * the element type */ - public RequesterRetryTest() { - useDefaultGitHub = false; + static class BodyStreamThrowingGitHubConnector extends HttpClientGitHubConnector { + + private final Thrower thrower; + + final int[] count = { 0 }; + + /** + * Instantiates a new input stream throwing http connector. + * + * @param thrower + * the thrower + */ + BodyStreamThrowingGitHubConnector(final Thrower thrower) { + super(); + this.thrower = thrower; + } + + @Override + public GitHubConnectorResponse send(GitHubConnectorRequest connectorRequest) throws IOException { + if (connectorRequest.url().toString().contains(GITHUB_API_TEST_ORG)) { + count[0]++; + } + GitHubConnectorResponse response = super.send(connectorRequest); + return new GitHubConnectorResponseWrapper(response) { + @NotNull @Override + public InputStream bodyStream() throws IOException { + if (response.request().url().toString().contains(GITHUB_API_TEST_ORG)) { + if (count[0] % 3 != 0) { + thrower.throwError(); + } + } + return super.bodyStream(); + } + }; + } } + /** The connection. */ + // HttpURLConnection connection; + /** - * Gets the repository. + * The Class ResponseCodeThrowingGitHubConnector. * - * @return the repository - * @throws IOException - * Signals that an I/O exception has occurred. + * @param + * the element type */ - protected GHRepository getRepository() throws IOException { - return getRepository(gitHub); + static class SendThrowingGitHubConnector extends HttpClientGitHubConnector { + + private final Thrower thrower; + + final int[] count = { 0 }; + + /** + * Instantiates a new response code throwing http connector. + * + * @param thrower + * the thrower + */ + SendThrowingGitHubConnector(final Thrower thrower) { + super(); + this.thrower = thrower; + } + + @Override + public GitHubConnectorResponse send(GitHubConnectorRequest connectorRequest) throws IOException { + if (connectorRequest.url().toString().contains(GITHUB_API_TEST_ORG)) { + count[0]++; + // throwing before we call super.send() simulates error + if (count[0] % 3 != 0) { + thrower.throwError(); + } + } + + GitHubConnectorResponse response = super.send(connectorRequest); + return new GitHubConnectorResponseWrapper(response); + } + } - private GHRepository getRepository(GitHub gitHub) throws IOException { - return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); + private static StreamHandler customLogHandler; + + private static Logger log = Logger.getLogger(GitHubClient.class.getName()); // matches the logger in the affected + + // class + private static OutputStream logCapturingStream; + + /** The base request count. */ + int baseRequestCount; + + /** + * Instantiates a new requester retry test. + */ + public RequesterRetryTest() { + useDefaultGitHub = false; } /** @@ -90,50 +223,6 @@ public String getTestCapturedLog() { return logCapturingStream.toString(); } - /** - * Reset test captured log. - */ - public void resetTestCapturedLog() { - Logger.getLogger(GitHubClient.class.getName()).removeHandler(customLogHandler); - Logger.getLogger(OkHttpClient.class.getName()).removeHandler(customLogHandler); - customLogHandler.close(); - attachLogCapturer(); - } - - /** - * Test git hub is api url valid. - * - * @throws Exception - * the exception - */ - @Ignore("Used okhttp3 and this to verify connection closing. Too flaky for CI system.") - @Test - public void testGitHubIsApiUrlValid() throws Exception { - - OkHttpClient client = new OkHttpClient().newBuilder() - .connectionPool(new ConnectionPool(2, 100, TimeUnit.MILLISECONDS)) - .build(); - - OkHttpGitHubConnector connector = new OkHttpGitHubConnector(client); - - for (int x = 0; x < 100; x++) { - - this.gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()) - .withConnector(connector) - .build(); - - try { - gitHub.checkApiUrlValidity(); - } catch (IOException ioe) { - assertThat(ioe.getMessage(), containsString("private mode enabled")); - } - Thread.sleep(100); - } - - String capturedLog = getTestCapturedLog(); - assertThat(capturedLog, not(containsString("leaked"))); - } - // /** // * Test socket connection and retry. // * @@ -209,74 +298,87 @@ public void testGitHubIsApiUrlValid() throws Exception { // } /** - * Test socket connection and retry success. + * Reset test captured log. + */ + public void resetTestCapturedLog() { + Logger.getLogger(GitHubClient.class.getName()).removeHandler(customLogHandler); + Logger.getLogger(OkHttpClient.class.getName()).removeHandler(customLogHandler); + customLogHandler.close(); + attachLogCapturer(); + } + + /** + * Test git hub is api url valid. * * @throws Exception * the exception */ - // @Test - // public void testSocketConnectionAndRetry_Success() throws Exception { - // // Only implemented for HttpURLConnection connectors - // Assume.assumeThat(DefaultGitHubConnector.create(), not(instanceOf(HttpClientGitHubConnector.class))); + @Ignore("Used okhttp3 and this to verify connection closing. Too flaky for CI system.") + @Test + public void testGitHubIsApiUrlValid() throws Exception { - // // CONNECTION_RESET_BY_PEER errors result in two requests each - // // to get this failure for "3" tries we have to do 6 queries. - // // If there are only 5 errors we succeed. - // this.mockGitHub.apiServer() - // .stubFor(get(urlMatching(".+/branches/test/timeout")).atPriority(0) - // .inScenario("Retry") - // .whenScenarioStateIs(Scenario.STARTED) - // .willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER))) - // .setNewScenarioState("Retry-1"); - // this.mockGitHub.apiServer() - // .stubFor(get(urlMatching(".+/branches/test/timeout")).atPriority(0) - // .inScenario("Retry") - // .whenScenarioStateIs("Retry-1") - // .willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER))) - // .setNewScenarioState("Retry-2"); - // this.mockGitHub.apiServer() - // .stubFor(get(urlMatching(".+/branches/test/timeout")).atPriority(0) - // .inScenario("Retry") - // .whenScenarioStateIs("Retry-2") - // .willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER))) - // .setNewScenarioState("Retry-3"); - // this.mockGitHub.apiServer() - // .stubFor(get(urlMatching(".+/branches/test/timeout")).atPriority(0) - // .inScenario("Retry") - // .whenScenarioStateIs("Retry-3") - // .willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER))) - // .setNewScenarioState("Retry-4"); - // this.mockGitHub.apiServer() - // .stubFor(get(urlMatching(".+/branches/test/timeout")).atPriority(0) - // .atPriority(0) - // .inScenario("Retry") - // .whenScenarioStateIs("Retry-4") - // .willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER))) - // .setNewScenarioState("Retry-5"); + OkHttpClient client = new OkHttpClient().newBuilder() + .connectionPool(new ConnectionPool(2, 100, TimeUnit.MILLISECONDS)) + .build(); - // this.gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()).build(); + OkHttpGitHubConnector connector = new OkHttpGitHubConnector(client); - // GHRepository repo = getRepository(); - // baseRequestCount = this.mockGitHub.getRequestCount(); - // GHBranch branch = repo.getBranch("test/timeout"); - // assertThat(branch, notNullValue()); - // String capturedLog = getTestCapturedLog(); - // assertThat(capturedLog, containsString("(2 retries remaining)")); - // assertThat(capturedLog, containsString("(1 retries remaining)")); + for (int x = 0; x < 100; x++) { - // assertThat(this.mockGitHub.getRequestCount(), equalTo(baseRequestCount + 6)); + this.gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()) + .withConnector(connector) + .build(); + + try { + gitHub.checkApiUrlValidity(); + } catch (IOException ioe) { + assertThat(ioe.getMessage(), containsString("private mode enabled")); + } + Thread.sleep(100); + } + + String capturedLog = getTestCapturedLog(); + assertThat(capturedLog, not(containsString("leaked"))); + } + + /** + * Test response code connection exceptions. + * + * @throws Exception + * the exception + */ + // @Test + // public void testResponseCodeConnectionExceptions() throws Exception { + // // Because the test throws at the very start of send(), there is only one connection for 3 retries + // GitHubConnector connector = new SendThrowingGitHubConnector<>(() -> { + // throw new SocketException(); + // }); + // runConnectionExceptionTest(connector, 1); + // runConnectionExceptionStatusCodeTest(connector, 1); + + // connector = new SendThrowingGitHubConnector<>(() -> { + // throw new SocketTimeoutException(); + // }); + // runConnectionExceptionTest(connector, 1); + // runConnectionExceptionStatusCodeTest(connector, 1); + + // connector = new SendThrowingGitHubConnector<>(() -> { + // throw new SSLHandshakeException("TestFailure"); + // }); + // runConnectionExceptionTest(connector, 1); + // runConnectionExceptionStatusCodeTest(connector, 1); // } /** - * Test response code failure exceptions. + * Test input stream failure exceptions. * * @throws Exception * the exception */ @Test - public void testResponseCodeFailureExceptions() throws Exception { + public void testInputStreamFailureExceptions() throws Exception { // No retry for these Exceptions - GitHubConnector connector = new SendThrowingGitHubConnector<>(() -> { + GitHubConnector connector = new BodyStreamThrowingGitHubConnector<>(() -> { throw new IOException("Custom"); }); this.gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()) @@ -294,40 +396,51 @@ public void testResponseCodeFailureExceptions() throws Exception { assertThat(e.getCause().getMessage(), is("Custom")); String capturedLog = getTestCapturedLog(); assertThat(capturedLog, not(containsString("retries remaining"))); - assertThat(this.mockGitHub.getRequestCount(), equalTo(baseRequestCount)); + assertThat(this.mockGitHub.getRequestCount(), equalTo(baseRequestCount + 1)); } - connector = new SendThrowingGitHubConnector<>(() -> { - throw new FileNotFoundException("Custom"); - }); - this.gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()) - .withConnector(connector) - .build(); + // FileNotFound doesn't need a special connector + this.gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()).build(); resetTestCapturedLog(); baseRequestCount = this.mockGitHub.getRequestCount(); try { - this.gitHub.getOrganization(GITHUB_API_TEST_ORG); + this.gitHub.getOrganization(GITHUB_API_TEST_ORG + "-missing"); fail(); } catch (Exception e) { - assertThat(e, instanceOf(FileNotFoundException.class)); - assertThat(e.getMessage(), is("Custom")); + assertThat(e, instanceOf(GHFileNotFoundException.class)); + assertThat(e.getCause(), instanceOf(FileNotFoundException.class)); + assertThat(e.getCause().getMessage(), containsString("hub4j-test-org-missing")); String capturedLog = getTestCapturedLog(); assertThat(capturedLog, not(containsString("retries remaining"))); - assertThat(this.mockGitHub.getRequestCount(), equalTo(baseRequestCount)); + assertThat(this.mockGitHub.getRequestCount(), equalTo(baseRequestCount + 1)); } + + // FileNotFound doesn't need a special connector + this.gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()).build(); + + resetTestCapturedLog(); + baseRequestCount = this.mockGitHub.getRequestCount(); + assertThat( + this.gitHub.createRequest() + .withUrlPath("/orgs/" + GITHUB_API_TEST_ORG + "-missing") + .fetchHttpStatusCode(), + equalTo(404)); + String capturedLog = getTestCapturedLog(); + assertThat(capturedLog, not(containsString("retries remaining"))); + assertThat(this.mockGitHub.getRequestCount(), equalTo(baseRequestCount + 1)); } /** - * Test input stream failure exceptions. + * Test response code failure exceptions. * * @throws Exception * the exception */ @Test - public void testInputStreamFailureExceptions() throws Exception { + public void testResponseCodeFailureExceptions() throws Exception { // No retry for these Exceptions - GitHubConnector connector = new BodyStreamThrowingGitHubConnector<>(() -> { + GitHubConnector connector = new SendThrowingGitHubConnector<>(() -> { throw new IOException("Custom"); }); this.gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()) @@ -345,69 +458,116 @@ public void testInputStreamFailureExceptions() throws Exception { assertThat(e.getCause().getMessage(), is("Custom")); String capturedLog = getTestCapturedLog(); assertThat(capturedLog, not(containsString("retries remaining"))); - assertThat(this.mockGitHub.getRequestCount(), equalTo(baseRequestCount + 1)); + assertThat(this.mockGitHub.getRequestCount(), equalTo(baseRequestCount)); } - // FileNotFound doesn't need a special connector - this.gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()).build(); + connector = new SendThrowingGitHubConnector<>(() -> { + throw new FileNotFoundException("Custom"); + }); + this.gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()) + .withConnector(connector) + .build(); resetTestCapturedLog(); baseRequestCount = this.mockGitHub.getRequestCount(); try { - this.gitHub.getOrganization(GITHUB_API_TEST_ORG + "-missing"); + this.gitHub.getOrganization(GITHUB_API_TEST_ORG); fail(); } catch (Exception e) { - assertThat(e, instanceOf(GHFileNotFoundException.class)); - assertThat(e.getCause(), instanceOf(FileNotFoundException.class)); - assertThat(e.getCause().getMessage(), containsString("hub4j-test-org-missing")); + assertThat(e, instanceOf(FileNotFoundException.class)); + assertThat(e.getMessage(), is("Custom")); String capturedLog = getTestCapturedLog(); assertThat(capturedLog, not(containsString("retries remaining"))); - assertThat(this.mockGitHub.getRequestCount(), equalTo(baseRequestCount + 1)); + assertThat(this.mockGitHub.getRequestCount(), equalTo(baseRequestCount)); } - - // FileNotFound doesn't need a special connector - this.gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()).build(); - - resetTestCapturedLog(); - baseRequestCount = this.mockGitHub.getRequestCount(); - assertThat( - this.gitHub.createRequest() - .withUrlPath("/orgs/" + GITHUB_API_TEST_ORG + "-missing") - .fetchHttpStatusCode(), - equalTo(404)); - String capturedLog = getTestCapturedLog(); - assertThat(capturedLog, not(containsString("retries remaining"))); - assertThat(this.mockGitHub.getRequestCount(), equalTo(baseRequestCount + 1)); } /** - * Test response code connection exceptions. + * Test socket connection and retry success. * * @throws Exception * the exception */ // @Test - // public void testResponseCodeConnectionExceptions() throws Exception { - // // Because the test throws at the very start of send(), there is only one connection for 3 retries - // GitHubConnector connector = new SendThrowingGitHubConnector<>(() -> { - // throw new SocketException(); - // }); - // runConnectionExceptionTest(connector, 1); - // runConnectionExceptionStatusCodeTest(connector, 1); + // public void testSocketConnectionAndRetry_Success() throws Exception { + // // Only implemented for HttpURLConnection connectors + // Assume.assumeThat(DefaultGitHubConnector.create(), not(instanceOf(HttpClientGitHubConnector.class))); - // connector = new SendThrowingGitHubConnector<>(() -> { - // throw new SocketTimeoutException(); - // }); - // runConnectionExceptionTest(connector, 1); - // runConnectionExceptionStatusCodeTest(connector, 1); + // // CONNECTION_RESET_BY_PEER errors result in two requests each + // // to get this failure for "3" tries we have to do 6 queries. + // // If there are only 5 errors we succeed. + // this.mockGitHub.apiServer() + // .stubFor(get(urlMatching(".+/branches/test/timeout")).atPriority(0) + // .inScenario("Retry") + // .whenScenarioStateIs(Scenario.STARTED) + // .willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER))) + // .setNewScenarioState("Retry-1"); + // this.mockGitHub.apiServer() + // .stubFor(get(urlMatching(".+/branches/test/timeout")).atPriority(0) + // .inScenario("Retry") + // .whenScenarioStateIs("Retry-1") + // .willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER))) + // .setNewScenarioState("Retry-2"); + // this.mockGitHub.apiServer() + // .stubFor(get(urlMatching(".+/branches/test/timeout")).atPriority(0) + // .inScenario("Retry") + // .whenScenarioStateIs("Retry-2") + // .willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER))) + // .setNewScenarioState("Retry-3"); + // this.mockGitHub.apiServer() + // .stubFor(get(urlMatching(".+/branches/test/timeout")).atPriority(0) + // .inScenario("Retry") + // .whenScenarioStateIs("Retry-3") + // .willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER))) + // .setNewScenarioState("Retry-4"); + // this.mockGitHub.apiServer() + // .stubFor(get(urlMatching(".+/branches/test/timeout")).atPriority(0) + // .atPriority(0) + // .inScenario("Retry") + // .whenScenarioStateIs("Retry-4") + // .willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER))) + // .setNewScenarioState("Retry-5"); - // connector = new SendThrowingGitHubConnector<>(() -> { - // throw new SSLHandshakeException("TestFailure"); - // }); - // runConnectionExceptionTest(connector, 1); - // runConnectionExceptionStatusCodeTest(connector, 1); + // this.gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()).build(); + + // GHRepository repo = getRepository(); + // baseRequestCount = this.mockGitHub.getRequestCount(); + // GHBranch branch = repo.getBranch("test/timeout"); + // assertThat(branch, notNullValue()); + // String capturedLog = getTestCapturedLog(); + // assertThat(capturedLog, containsString("(2 retries remaining)")); + // assertThat(capturedLog, containsString("(1 retries remaining)")); + + // assertThat(this.mockGitHub.getRequestCount(), equalTo(baseRequestCount + 6)); // } + private GHRepository getRepository(GitHub gitHub) throws IOException { + return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); + } + + private void runConnectionExceptionStatusCodeTest(GitHubConnector connector, int expectedRequestCount) + throws IOException { + // now wire in the connector + this.gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()) + .withConnector(connector) + .build(); + + resetTestCapturedLog(); + baseRequestCount = this.mockGitHub.getRequestCount(); + assertThat(this.gitHub.createRequest().withUrlPath("/orgs/" + GITHUB_API_TEST_ORG).fetchHttpStatusCode(), + equalTo(200)); + String capturedLog = getTestCapturedLog(); + if (expectedRequestCount > 0) { + assertThat(capturedLog, containsString("(2 retries remaining)")); + assertThat(capturedLog, containsString("(1 retries remaining)")); + assertThat(this.mockGitHub.getRequestCount(), equalTo(baseRequestCount + expectedRequestCount)); + } else { + // Success without retries + assertThat(capturedLog, not(containsString("retries remaining"))); + assertThat(this.mockGitHub.getRequestCount(), equalTo(baseRequestCount + 1)); + } + } + /** * Test input stream connection exceptions. * @@ -460,214 +620,14 @@ private void runConnectionExceptionTest(GitHubConnector connector, int expectedR assertThat(this.mockGitHub.getRequestCount(), equalTo(baseRequestCount + expectedRequestCount)); } - private void runConnectionExceptionStatusCodeTest(GitHubConnector connector, int expectedRequestCount) - throws IOException { - // now wire in the connector - this.gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()) - .withConnector(connector) - .build(); - - resetTestCapturedLog(); - baseRequestCount = this.mockGitHub.getRequestCount(); - assertThat(this.gitHub.createRequest().withUrlPath("/orgs/" + GITHUB_API_TEST_ORG).fetchHttpStatusCode(), - equalTo(200)); - String capturedLog = getTestCapturedLog(); - if (expectedRequestCount > 0) { - assertThat(capturedLog, containsString("(2 retries remaining)")); - assertThat(capturedLog, containsString("(1 retries remaining)")); - assertThat(this.mockGitHub.getRequestCount(), equalTo(baseRequestCount + expectedRequestCount)); - } else { - // Success without retries - assertThat(capturedLog, not(containsString("retries remaining"))); - assertThat(this.mockGitHub.getRequestCount(), equalTo(baseRequestCount + 1)); - } - } - - /** - * The Class ResponseCodeThrowingGitHubConnector. - * - * @param - * the element type - */ - static class SendThrowingGitHubConnector extends HttpClientGitHubConnector { - - final int[] count = { 0 }; - - private final Thrower thrower; - - /** - * Instantiates a new response code throwing http connector. - * - * @param thrower - * the thrower - */ - SendThrowingGitHubConnector(final Thrower thrower) { - super(); - this.thrower = thrower; - } - - @Override - public GitHubConnectorResponse send(GitHubConnectorRequest connectorRequest) throws IOException { - if (connectorRequest.url().toString().contains(GITHUB_API_TEST_ORG)) { - count[0]++; - // throwing before we call super.send() simulates error - if (count[0] % 3 != 0) { - thrower.throwError(); - } - } - - GitHubConnectorResponse response = super.send(connectorRequest); - return new GitHubConnectorResponseWrapper(response); - } - - } - - /** - * The Class InputStreamThrowingHttpConnector. - * - * @param - * the element type - */ - static class BodyStreamThrowingGitHubConnector extends HttpClientGitHubConnector { - - final int[] count = { 0 }; - - private final Thrower thrower; - - /** - * Instantiates a new input stream throwing http connector. - * - * @param thrower - * the thrower - */ - BodyStreamThrowingGitHubConnector(final Thrower thrower) { - super(); - this.thrower = thrower; - } - - @Override - public GitHubConnectorResponse send(GitHubConnectorRequest connectorRequest) throws IOException { - if (connectorRequest.url().toString().contains(GITHUB_API_TEST_ORG)) { - count[0]++; - } - GitHubConnectorResponse response = super.send(connectorRequest); - return new GitHubConnectorResponseWrapper(response) { - @NotNull - @Override - public InputStream bodyStream() throws IOException { - if (response.request().url().toString().contains(GITHUB_API_TEST_ORG)) { - if (count[0] % 3 != 0) { - thrower.throwError(); - } - } - return super.bodyStream(); - } - }; - } - } - - private static final GitHubConnectorRequest IGNORED_EMPTY_REQUEST = new GitHubConnectorRequest() { - @NotNull - @Override - public String method() { - return null; - } - - @NotNull - @Override - public Map> allHeaders() { - return null; - } - - @Nullable - @Override - public String header(String name) { - return null; - } - - @Nullable - @Override - public String contentType() { - return null; - } - - @Nullable - @Override - public InputStream body() { - return null; - } - - @NotNull - @Override - public URL url() { - return null; - } - - @Override - public boolean hasBody() { - return false; - } - }; - - private static class GitHubConnectorResponseWrapper extends GitHubConnectorResponse { - - private final GitHubConnectorResponse wrapped; - - GitHubConnectorResponseWrapper(GitHubConnectorResponse response) { - super(IGNORED_EMPTY_REQUEST, -1, new HashMap<>()); - wrapped = response; - } - - @CheckForNull - @Override - public String header(String name) { - return wrapped.header(name); - } - - @NotNull - @Override - public InputStream bodyStream() throws IOException { - return wrapped.bodyStream(); - } - - @Nonnull - @Override - public GitHubConnectorRequest request() { - return wrapped.request(); - } - - @Override - public int statusCode() { - return wrapped.statusCode(); - } - - @Nonnull - @Override - public Map> allHeaders() { - return wrapped.allHeaders(); - } - - @Override - public void close() throws IOException { - wrapped.close(); - } - } - /** - * The Interface Thrower. + * Gets the repository. * - * @param - * the element type + * @return the repository + * @throws IOException + * Signals that an I/O exception has occurred. */ - @FunctionalInterface - public interface Thrower { - - /** - * Throw error. - * - * @throws E - * the e - */ - void throwError() throws E; + protected GHRepository getRepository() throws IOException { + return getRepository(gitHub); } } diff --git a/src/test/java/org/kohsuke/github/WireMockMultiServerRule.java b/src/test/java/org/kohsuke/github/WireMockMultiServerRule.java index 0df77f446f..43699e51f2 100644 --- a/src/test/java/org/kohsuke/github/WireMockMultiServerRule.java +++ b/src/test/java/org/kohsuke/github/WireMockMultiServerRule.java @@ -26,22 +26,20 @@ */ public class WireMockMultiServerRule implements MethodRule, TestRule { - /** The servers. */ - protected final Map servers = new HashMap<>(); private boolean failOnUnmatchedRequests; + private String methodName = null; private final Options options; + /** The servers. */ + protected final Map servers = new HashMap<>(); + /** - * Gets the method name. - * - * @return the method name + * Instantiates a new wire mock multi server rule. */ - public String getMethodName() { - return methodName; + public WireMockMultiServerRule() { + this(WireMockRuleConfiguration.wireMockConfig()); } - private String methodName = null; - /** * Instantiates a new wire mock multi server rule. * @@ -65,13 +63,6 @@ public WireMockMultiServerRule(Options options, boolean failOnUnmatchedRequests) this.failOnUnmatchedRequests = failOnUnmatchedRequests; } - /** - * Instantiates a new wire mock multi server rule. - */ - public WireMockMultiServerRule() { - this(WireMockRuleConfiguration.wireMockConfig()); - } - /** * Apply. * @@ -100,6 +91,15 @@ public Statement apply(final Statement base, FrameworkMethod method, Object targ return this.apply(base, method.getName()); } + /** + * Gets the method name. + * + * @return the method name + */ + public String getMethodName() { + return methodName; + } + private Statement apply(final Statement base, final String methodName) { return new Statement() { public void evaluate() throws Throwable { @@ -122,54 +122,6 @@ public void evaluate() throws Throwable { }; } - /** - * Initialize servers. - */ - protected void initializeServers() { - } - - /** - * Initialize server. - * - * @param serverId - * the server id - * @param extensions - * the extensions - */ - protected final void initializeServer(String serverId, Extension... extensions) { - String directoryName = methodName; - if (!serverId.equals("default")) { - directoryName += "_" + serverId; - } - - final Options localOptions = new WireMockRuleConfiguration(WireMockMultiServerRule.this.options, - directoryName, - extensions); - - new File(localOptions.filesRoot().getPath(), "mappings").mkdirs(); - new File(localOptions.filesRoot().getPath(), "__files").mkdirs(); - - WireMockServer server = new WireMockServer(localOptions); - this.servers.put(serverId, server); - server.start(); - - if (!serverId.equals("default")) { - WireMock.configureFor("localhost", server.port()); - } - } - - /** - * Before. - */ - protected void before() { - } - - /** - * After. - */ - protected void after() { - } - private void checkForUnmatchedRequests() { servers.values().forEach(server -> checkForUnmatchedRequests(server)); } @@ -216,4 +168,52 @@ private void stop() { }); } + /** + * After. + */ + protected void after() { + } + + /** + * Before. + */ + protected void before() { + } + + /** + * Initialize server. + * + * @param serverId + * the server id + * @param extensions + * the extensions + */ + protected final void initializeServer(String serverId, Extension... extensions) { + String directoryName = methodName; + if (!serverId.equals("default")) { + directoryName += "_" + serverId; + } + + final Options localOptions = new WireMockRuleConfiguration(WireMockMultiServerRule.this.options, + directoryName, + extensions); + + new File(localOptions.filesRoot().getPath(), "mappings").mkdirs(); + new File(localOptions.filesRoot().getPath(), "__files").mkdirs(); + + WireMockServer server = new WireMockServer(localOptions); + this.servers.put(serverId, server); + server.start(); + + if (!serverId.equals("default")) { + WireMock.configureFor("localhost", server.port()); + } + } + + /** + * Initialize servers. + */ + protected void initializeServers() { + } + } diff --git a/src/test/java/org/kohsuke/github/WireMockRule.java b/src/test/java/org/kohsuke/github/WireMockRule.java index 66f2baf6dc..a20984a44f 100644 --- a/src/test/java/org/kohsuke/github/WireMockRule.java +++ b/src/test/java/org/kohsuke/github/WireMockRule.java @@ -41,21 +41,19 @@ */ public class WireMockRule implements MethodRule, TestRule, Container, Stubbing, Admin { - private WireMockServer wireMockServer; private boolean failOnUnmatchedRequests; + private String methodName = null; private final Options options; + private WireMockServer wireMockServer; + /** - * Gets the method name. - * - * @return the method name + * Instantiates a new wire mock rule. */ - public String getMethodName() { - return methodName; + public WireMockRule() { + this(WireMockRuleConfiguration.wireMockConfig()); } - private String methodName = null; - /** * Instantiates a new wire mock rule. * @@ -102,10 +100,23 @@ public WireMockRule(int port, Integer httpsPort) { } /** - * Instantiates a new wire mock rule. + * Adds the mock service request listener. + * + * @param listener + * the listener */ - public WireMockRule() { - this(WireMockRuleConfiguration.wireMockConfig()); + public void addMockServiceRequestListener(RequestListener listener) { + wireMockServer.addMockServiceRequestListener(listener); + } + + /** + * Adds the stub mapping. + * + * @param stubMapping + * the stub mapping + */ + public void addStubMapping(StubMapping stubMapping) { + wireMockServer.addStubMapping(stubMapping); } /** @@ -136,87 +147,44 @@ public Statement apply(final Statement base, FrameworkMethod method, Object targ return this.apply(base, method.getName()); } - private Statement apply(final Statement base, final String methodName) { - return new Statement() { - public void evaluate() throws Throwable { - WireMockRule.this.methodName = methodName; - final Options localOptions = new WireMockRuleConfiguration(WireMockRule.this.options, methodName); - - new File(localOptions.filesRoot().getPath(), "mappings").mkdirs(); - new File(localOptions.filesRoot().getPath(), "__files").mkdirs(); - - WireMockRule.this.wireMockServer = new WireMockServer(localOptions); - WireMockRule.this.start(); - WireMock.configureFor("localhost", WireMockRule.this.port()); - - try { - WireMockRule.this.before(); - base.evaluate(); - WireMockRule.this.checkForUnmatchedRequests(); - } finally { - WireMockRule.this.after(); - WireMockRule.this.stop(); - WireMockRule.this.methodName = null; - } - - } - }; - } - - private void checkForUnmatchedRequests() { - if (this.failOnUnmatchedRequests) { - List unmatchedRequests = this.findAllUnmatchedRequests(); - if (!unmatchedRequests.isEmpty()) { - List nearMisses = this.findNearMissesForAllUnmatchedRequests(); - if (nearMisses.isEmpty()) { - throw VerificationException.forUnmatchedRequests(unmatchedRequests); - } - - throw VerificationException.forUnmatchedNearMisses(nearMisses); - } - } - - } - - /** - * Before. - */ - protected void before() { - } - /** - * After. + * Base url. + * + * @return the string */ - protected void after() { + public String baseUrl() { + return wireMockServer.baseUrl(); } /** - * Load mappings using. + * Count requests matching. * - * @param mappingsLoader - * the mappings loader + * @param requestPattern + * the request pattern + * @return the verification result */ - public void loadMappingsUsing(MappingsLoader mappingsLoader) { - wireMockServer.loadMappingsUsing(mappingsLoader); + public VerificationResult countRequestsMatching(RequestPattern requestPattern) { + return wireMockServer.countRequestsMatching(requestPattern); } /** - * Gets the global settings holder. + * Edits the stub. * - * @return the global settings holder + * @param mappingBuilder + * the mapping builder */ - public GlobalSettingsHolder getGlobalSettingsHolder() { - return wireMockServer.getGlobalSettingsHolder(); + public void editStub(MappingBuilder mappingBuilder) { + wireMockServer.editStub(mappingBuilder); } /** - * Adds the mock service request listener. + * Edits the stub mapping. * - * @param listener - * the listener + * @param stubMapping + * the stub mapping */ - public void addMockServiceRequestListener(RequestListener listener) { - wireMockServer.addMockServiceRequestListener(listener); + public void editStubMapping(StubMapping stubMapping) { + wireMockServer.editStubMapping(stubMapping); } /** @@ -232,502 +200,473 @@ public void enableRecordMappings(FileSource mappingsFileSource, FileSource files } /** - * Stop. + * Find all. + * + * @param requestPatternBuilder + * the request pattern builder + * @return the list */ - public void stop() { - wireMockServer.stop(); + public List findAll(RequestPatternBuilder requestPatternBuilder) { + return wireMockServer.findAll(requestPatternBuilder); } /** - * Start. + * Find all near misses for. + * + * @param requestPatternBuilder + * the request pattern builder + * @return the list */ - public void start() { - wireMockServer.start(); + public List findAllNearMissesFor(RequestPatternBuilder requestPatternBuilder) { + return wireMockServer.findAllNearMissesFor(requestPatternBuilder); } /** - * Shutdown. + * Find all stubs by metadata. + * + * @param pattern + * the pattern + * @return the list stub mappings result */ - public void shutdown() { - wireMockServer.shutdown(); + public ListStubMappingsResult findAllStubsByMetadata(StringValuePattern pattern) { + return wireMockServer.findAllStubsByMetadata(pattern); } /** - * Port. + * Find all unmatched requests. * - * @return the int + * @return the list */ - public int port() { - return wireMockServer.port(); + public List findAllUnmatchedRequests() { + return wireMockServer.findAllUnmatchedRequests(); } /** - * Https port. + * Find near misses for. * - * @return the int + * @param loggedRequest + * the logged request + * @return the list */ - public int httpsPort() { - return wireMockServer.httpsPort(); + public List findNearMissesFor(LoggedRequest loggedRequest) { + return wireMockServer.findNearMissesFor(loggedRequest); } /** - * Url. + * Find near misses for all unmatched requests. * - * @param path - * the path - * @return the string + * @return the list */ - public String url(String path) { - return wireMockServer.url(path); + public List findNearMissesForAllUnmatchedRequests() { + return wireMockServer.findNearMissesForAllUnmatchedRequests(); } /** - * Base url. + * Find near misses for unmatched requests. * - * @return the string + * @return the find near misses result */ - public String baseUrl() { - return wireMockServer.baseUrl(); + public FindNearMissesResult findNearMissesForUnmatchedRequests() { + return wireMockServer.findNearMissesForUnmatchedRequests(); } /** - * Checks if is running. + * Find requests matching. * - * @return true, if is running + * @param requestPattern + * the request pattern + * @return the find requests result */ - public boolean isRunning() { - return wireMockServer.isRunning(); + public FindRequestsResult findRequestsMatching(RequestPattern requestPattern) { + return wireMockServer.findRequestsMatching(requestPattern); } /** - * Given that. + * Find stub mappings by metadata. * - * @param mappingBuilder - * the mapping builder - * @return the stub mapping + * @param pattern + * the pattern + * @return the list */ - public StubMapping givenThat(MappingBuilder mappingBuilder) { - return wireMockServer.givenThat(mappingBuilder); + public List findStubMappingsByMetadata(StringValuePattern pattern) { + return wireMockServer.findStubMappingsByMetadata(pattern); } /** - * Stub for. + * Find top near misses for. * - * @param mappingBuilder - * the mapping builder - * @return the stub mapping + * @param loggedRequest + * the logged request + * @return the find near misses result */ - public StubMapping stubFor(MappingBuilder mappingBuilder) { - return wireMockServer.stubFor(mappingBuilder); + public FindNearMissesResult findTopNearMissesFor(LoggedRequest loggedRequest) { + return wireMockServer.findTopNearMissesFor(loggedRequest); } /** - * Edits the stub. + * Find top near misses for. * - * @param mappingBuilder - * the mapping builder + * @param requestPattern + * the request pattern + * @return the find near misses result */ - public void editStub(MappingBuilder mappingBuilder) { - wireMockServer.editStub(mappingBuilder); + public FindNearMissesResult findTopNearMissesFor(RequestPattern requestPattern) { + return wireMockServer.findTopNearMissesFor(requestPattern); } /** - * Removes the stub. + * Find unmatched requests. * - * @param mappingBuilder - * the mapping builder + * @return the find requests result */ - public void removeStub(MappingBuilder mappingBuilder) { - wireMockServer.removeStub(mappingBuilder); + public FindRequestsResult findUnmatchedRequests() { + return wireMockServer.findUnmatchedRequests(); } /** - * Removes the stub. + * Gets the all scenarios. * - * @param stubMapping - * the stub mapping + * @return the all scenarios */ - public void removeStub(StubMapping stubMapping) { - wireMockServer.removeStub(stubMapping); + public GetScenariosResult getAllScenarios() { + return wireMockServer.getAllScenarios(); } /** - * Gets the stub mappings. + * Gets the all serve events. * - * @return the stub mappings + * @return the all serve events */ - public List getStubMappings() { - return wireMockServer.getStubMappings(); + public List getAllServeEvents() { + return wireMockServer.getAllServeEvents(); } /** - * Gets the single stub mapping. + * Gets the global settings. * - * @param id - * the id - * @return the single stub mapping + * @return the global settings */ - public StubMapping getSingleStubMapping(UUID id) { - return wireMockServer.getSingleStubMapping(id); + public GetGlobalSettingsResult getGlobalSettings() { + return wireMockServer.getGlobalSettings(); } /** - * Find stub mappings by metadata. + * Gets the global settings holder. * - * @param pattern - * the pattern - * @return the list + * @return the global settings holder */ - public List findStubMappingsByMetadata(StringValuePattern pattern) { - return wireMockServer.findStubMappingsByMetadata(pattern); + public GlobalSettingsHolder getGlobalSettingsHolder() { + return wireMockServer.getGlobalSettingsHolder(); } /** - * Removes the stub mappings by metadata. + * Gets the method name. * - * @param pattern - * the pattern + * @return the method name */ - public void removeStubMappingsByMetadata(StringValuePattern pattern) { - wireMockServer.removeStubMappingsByMetadata(pattern); + public String getMethodName() { + return methodName; } /** - * Removes the stub mapping. + * Gets the options. * - * @param stubMapping - * the stub mapping + * @return the options */ - public void removeStubMapping(StubMapping stubMapping) { - wireMockServer.removeStubMapping(stubMapping); + public Options getOptions() { + return wireMockServer.getOptions(); } /** - * Verify. + * Gets the recording status. * - * @param requestPatternBuilder - * the request pattern builder + * @return the recording status */ - public void verify(RequestPatternBuilder requestPatternBuilder) { - wireMockServer.verify(requestPatternBuilder); + public RecordingStatusResult getRecordingStatus() { + return wireMockServer.getRecordingStatus(); } /** - * Verify. + * Gets the serve events. * - * @param count - * the count - * @param requestPatternBuilder - * the request pattern builder + * @return the serve events */ - public void verify(int count, RequestPatternBuilder requestPatternBuilder) { - wireMockServer.verify(count, requestPatternBuilder); + public GetServeEventsResult getServeEvents() { + return wireMockServer.getServeEvents(); } /** - * Verify. + * Gets the serve events. * - * @param countMatchingStrategy - * the count matching strategy - * @param requestPatternBuilder - * the request pattern builder + * @param serveEventQuery + * the serve event query + * @return the serve events */ - public void verify(CountMatchingStrategy countMatchingStrategy, RequestPatternBuilder requestPatternBuilder) { - wireMockServer.verify(countMatchingStrategy, requestPatternBuilder); + public GetServeEventsResult getServeEvents(ServeEventQuery serveEventQuery) { + return wireMockServer.getServeEvents(serveEventQuery); } /** - * Find all. + * Gets the served stub. * - * @param requestPatternBuilder - * the request pattern builder - * @return the list + * @param id + * the id + * @return the served stub */ - public List findAll(RequestPatternBuilder requestPatternBuilder) { - return wireMockServer.findAll(requestPatternBuilder); + public SingleServedStubResult getServedStub(UUID id) { + return wireMockServer.getServedStub(id); } /** - * Gets the all serve events. + * Gets the single stub mapping. * - * @return the all serve events + * @param id + * the id + * @return the single stub mapping */ - public List getAllServeEvents() { - return wireMockServer.getAllServeEvents(); + public StubMapping getSingleStubMapping(UUID id) { + return wireMockServer.getSingleStubMapping(id); } /** - * Sets the global fixed delay. + * Gets the stub mapping. * - * @param milliseconds - * the new global fixed delay + * @param id + * the id + * @return the stub mapping */ - public void setGlobalFixedDelay(int milliseconds) { - wireMockServer.setGlobalFixedDelay(milliseconds); + public SingleStubMappingResult getStubMapping(UUID id) { + return wireMockServer.getStubMapping(id); } /** - * Find all unmatched requests. + * Gets the stub mappings. * - * @return the list + * @return the stub mappings */ - public List findAllUnmatchedRequests() { - return wireMockServer.findAllUnmatchedRequests(); + public List getStubMappings() { + return wireMockServer.getStubMappings(); } /** - * Find near misses for all unmatched requests. + * Given that. * - * @return the list + * @param mappingBuilder + * the mapping builder + * @return the stub mapping */ - public List findNearMissesForAllUnmatchedRequests() { - return wireMockServer.findNearMissesForAllUnmatchedRequests(); + public StubMapping givenThat(MappingBuilder mappingBuilder) { + return wireMockServer.givenThat(mappingBuilder); } /** - * Find all near misses for. + * Https port. * - * @param requestPatternBuilder - * the request pattern builder - * @return the list + * @return the int */ - public List findAllNearMissesFor(RequestPatternBuilder requestPatternBuilder) { - return wireMockServer.findAllNearMissesFor(requestPatternBuilder); + public int httpsPort() { + return wireMockServer.httpsPort(); } /** - * Find near misses for. + * Import stubs. * - * @param loggedRequest - * the logged request - * @return the list + * @param stubImport + * the stub import */ - public List findNearMissesFor(LoggedRequest loggedRequest) { - return wireMockServer.findNearMissesFor(loggedRequest); + public void importStubs(StubImport stubImport) { + wireMockServer.importStubs(stubImport); } /** - * Adds the stub mapping. + * Checks if is running. * - * @param stubMapping - * the stub mapping + * @return true, if is running */ - public void addStubMapping(StubMapping stubMapping) { - wireMockServer.addStubMapping(stubMapping); + public boolean isRunning() { + return wireMockServer.isRunning(); } /** - * Edits the stub mapping. + * List all stub mappings. * - * @param stubMapping - * the stub mapping + * @return the list stub mappings result */ - public void editStubMapping(StubMapping stubMapping) { - wireMockServer.editStubMapping(stubMapping); + public ListStubMappingsResult listAllStubMappings() { + return wireMockServer.listAllStubMappings(); } /** - * Removes the stub mapping. + * Load mappings using. * - * @param id - * the id + * @param mappingsLoader + * the mappings loader */ - public void removeStubMapping(UUID id) { - wireMockServer.removeStubMapping(id); + public void loadMappingsUsing(MappingsLoader mappingsLoader) { + wireMockServer.loadMappingsUsing(mappingsLoader); } /** - * List all stub mappings. + * Port. * - * @return the list stub mappings result + * @return the int */ - public ListStubMappingsResult listAllStubMappings() { - return wireMockServer.listAllStubMappings(); + public int port() { + return wireMockServer.port(); } /** - * Gets the stub mapping. + * Removes the serve event. * - * @param id - * the id - * @return the stub mapping - */ - public SingleStubMappingResult getStubMapping(UUID id) { - return wireMockServer.getStubMapping(id); - } - - /** - * Save mappings. + * @param uuid + * the uuid */ - public void saveMappings() { - wireMockServer.saveMappings(); + public void removeServeEvent(UUID uuid) { + wireMockServer.removeServeEvent(uuid); } /** - * Reset all. + * Removes the serve events for stubs matching metadata. + * + * @param stringValuePattern + * the string value pattern + * @return the find serve events result */ - public void resetAll() { - wireMockServer.resetAll(); + public FindServeEventsResult removeServeEventsForStubsMatchingMetadata(StringValuePattern stringValuePattern) { + return wireMockServer.removeServeEventsForStubsMatchingMetadata(stringValuePattern); } /** - * Reset requests. + * Removes the serve events matching. + * + * @param requestPattern + * the request pattern + * @return the find serve events result */ - public void resetRequests() { - wireMockServer.resetRequests(); + public FindServeEventsResult removeServeEventsMatching(RequestPattern requestPattern) { + return wireMockServer.removeServeEventsMatching(requestPattern); } /** - * Reset to default mappings. + * Removes the stub. + * + * @param mappingBuilder + * the mapping builder */ - public void resetToDefaultMappings() { - wireMockServer.resetToDefaultMappings(); + public void removeStub(MappingBuilder mappingBuilder) { + wireMockServer.removeStub(mappingBuilder); } /** - * Gets the serve events. + * Removes the stub. * - * @return the serve events + * @param stubMapping + * the stub mapping */ - public GetServeEventsResult getServeEvents() { - return wireMockServer.getServeEvents(); + public void removeStub(StubMapping stubMapping) { + wireMockServer.removeStub(stubMapping); } /** - * Gets the serve events. + * Removes the stub mapping. * - * @param serveEventQuery - * the serve event query - * @return the serve events + * @param stubMapping + * the stub mapping */ - public GetServeEventsResult getServeEvents(ServeEventQuery serveEventQuery) { - return wireMockServer.getServeEvents(serveEventQuery); + public void removeStubMapping(StubMapping stubMapping) { + wireMockServer.removeStubMapping(stubMapping); } /** - * Gets the served stub. + * Removes the stub mapping. * * @param id * the id - * @return the served stub */ - public SingleServedStubResult getServedStub(UUID id) { - return wireMockServer.getServedStub(id); + public void removeStubMapping(UUID id) { + wireMockServer.removeStubMapping(id); } /** - * Reset scenarios. - */ - public void resetScenarios() { - wireMockServer.resetScenarios(); - } - - /** - * Reset mappings. - */ - public void resetMappings() { - wireMockServer.resetMappings(); - } - - /** - * Count requests matching. + * Removes the stub mappings by metadata. * - * @param requestPattern - * the request pattern - * @return the verification result + * @param pattern + * the pattern */ - public VerificationResult countRequestsMatching(RequestPattern requestPattern) { - return wireMockServer.countRequestsMatching(requestPattern); + public void removeStubMappingsByMetadata(StringValuePattern pattern) { + wireMockServer.removeStubMappingsByMetadata(pattern); } /** - * Find requests matching. + * Removes the stubs by metadata. * - * @param requestPattern - * the request pattern - * @return the find requests result + * @param pattern + * the pattern */ - public FindRequestsResult findRequestsMatching(RequestPattern requestPattern) { - return wireMockServer.findRequestsMatching(requestPattern); + public void removeStubsByMetadata(StringValuePattern pattern) { + wireMockServer.removeStubsByMetadata(pattern); } /** - * Find unmatched requests. - * - * @return the find requests result + * Reset all. */ - public FindRequestsResult findUnmatchedRequests() { - return wireMockServer.findUnmatchedRequests(); + public void resetAll() { + wireMockServer.resetAll(); } /** - * Removes the serve event. - * - * @param uuid - * the uuid + * Reset mappings. */ - public void removeServeEvent(UUID uuid) { - wireMockServer.removeServeEvent(uuid); + public void resetMappings() { + wireMockServer.resetMappings(); } /** - * Removes the serve events matching. - * - * @param requestPattern - * the request pattern - * @return the find serve events result + * Reset requests. */ - public FindServeEventsResult removeServeEventsMatching(RequestPattern requestPattern) { - return wireMockServer.removeServeEventsMatching(requestPattern); + public void resetRequests() { + wireMockServer.resetRequests(); } /** - * Removes the serve events for stubs matching metadata. + * Reset a scenario * - * @param stringValuePattern - * the string value pattern - * @return the find serve events result + * @param name + * the name */ - public FindServeEventsResult removeServeEventsForStubsMatchingMetadata(StringValuePattern stringValuePattern) { - return wireMockServer.removeServeEventsForStubsMatchingMetadata(stringValuePattern); + public void resetScenario(String name) { + wireMockServer.resetScenario(name); } /** - * Update global settings. - * - * @param newSettings - * the new settings + * Reset scenarios. */ - public void updateGlobalSettings(GlobalSettings newSettings) { - wireMockServer.updateGlobalSettings(newSettings); + public void resetScenarios() { + wireMockServer.resetScenarios(); } /** - * Find near misses for unmatched requests. - * - * @return the find near misses result + * Reset to default mappings. */ - public FindNearMissesResult findNearMissesForUnmatchedRequests() { - return wireMockServer.findNearMissesForUnmatchedRequests(); + public void resetToDefaultMappings() { + wireMockServer.resetToDefaultMappings(); } /** - * Gets the all scenarios. - * - * @return the all scenarios + * Save mappings. */ - public GetScenariosResult getAllScenarios() { - return wireMockServer.getAllScenarios(); + public void saveMappings() { + wireMockServer.saveMappings(); } /** - * Reset a scenario + * Sets the global fixed delay. * - * @param name - * the name + * @param milliseconds + * the new global fixed delay */ - public void resetScenario(String name) { - wireMockServer.resetScenario(name); + public void setGlobalFixedDelay(int milliseconds) { + wireMockServer.setGlobalFixedDelay(milliseconds); } /** @@ -743,35 +682,55 @@ public void setScenarioState(String name, String state) { } /** - * Find top near misses for. + * Shutdown. + */ + public void shutdown() { + wireMockServer.shutdown(); + } + + /** + * Shutdown server. + */ + public void shutdownServer() { + wireMockServer.shutdownServer(); + } + + /** + * Snapshot record. * - * @param loggedRequest - * the logged request - * @return the find near misses result + * @return the snapshot record result */ - public FindNearMissesResult findTopNearMissesFor(LoggedRequest loggedRequest) { - return wireMockServer.findTopNearMissesFor(loggedRequest); + public SnapshotRecordResult snapshotRecord() { + return wireMockServer.snapshotRecord(); } /** - * Find top near misses for. + * Snapshot record. * - * @param requestPattern - * the request pattern - * @return the find near misses result + * @param spec + * the spec + * @return the snapshot record result */ - public FindNearMissesResult findTopNearMissesFor(RequestPattern requestPattern) { - return wireMockServer.findTopNearMissesFor(requestPattern); + public SnapshotRecordResult snapshotRecord(RecordSpec spec) { + return wireMockServer.snapshotRecord(spec); } /** - * Start recording. + * Snapshot record. * - * @param targetBaseUrl - * the target base url + * @param spec + * the spec + * @return the snapshot record result */ - public void startRecording(String targetBaseUrl) { - wireMockServer.startRecording(targetBaseUrl); + public SnapshotRecordResult snapshotRecord(RecordSpecBuilder spec) { + return wireMockServer.snapshotRecord(spec); + } + + /** + * Start. + */ + public void start() { + wireMockServer.start(); } /** @@ -795,107 +754,148 @@ public void startRecording(RecordSpecBuilder recordSpec) { } /** - * Stop recording. + * Start recording. * - * @return the snapshot record result + * @param targetBaseUrl + * the target base url */ - public SnapshotRecordResult stopRecording() { - return wireMockServer.stopRecording(); + public void startRecording(String targetBaseUrl) { + wireMockServer.startRecording(targetBaseUrl); } /** - * Gets the recording status. - * - * @return the recording status + * Stop. */ - public RecordingStatusResult getRecordingStatus() { - return wireMockServer.getRecordingStatus(); + public void stop() { + wireMockServer.stop(); } /** - * Snapshot record. + * Stop recording. * * @return the snapshot record result */ - public SnapshotRecordResult snapshotRecord() { - return wireMockServer.snapshotRecord(); + public SnapshotRecordResult stopRecording() { + return wireMockServer.stopRecording(); } /** - * Snapshot record. + * Stub for. * - * @param spec - * the spec - * @return the snapshot record result + * @param mappingBuilder + * the mapping builder + * @return the stub mapping */ - public SnapshotRecordResult snapshotRecord(RecordSpecBuilder spec) { - return wireMockServer.snapshotRecord(spec); + public StubMapping stubFor(MappingBuilder mappingBuilder) { + return wireMockServer.stubFor(mappingBuilder); } /** - * Snapshot record. + * Update global settings. * - * @param spec - * the spec - * @return the snapshot record result + * @param newSettings + * the new settings */ - public SnapshotRecordResult snapshotRecord(RecordSpec spec) { - return wireMockServer.snapshotRecord(spec); + public void updateGlobalSettings(GlobalSettings newSettings) { + wireMockServer.updateGlobalSettings(newSettings); } /** - * Gets the options. + * Url. * - * @return the options + * @param path + * the path + * @return the string */ - public Options getOptions() { - return wireMockServer.getOptions(); + public String url(String path) { + return wireMockServer.url(path); } /** - * Shutdown server. + * Verify. + * + * @param countMatchingStrategy + * the count matching strategy + * @param requestPatternBuilder + * the request pattern builder */ - public void shutdownServer() { - wireMockServer.shutdownServer(); + public void verify(CountMatchingStrategy countMatchingStrategy, RequestPatternBuilder requestPatternBuilder) { + wireMockServer.verify(countMatchingStrategy, requestPatternBuilder); } /** - * Find all stubs by metadata. + * Verify. * - * @param pattern - * the pattern - * @return the list stub mappings result + * @param requestPatternBuilder + * the request pattern builder */ - public ListStubMappingsResult findAllStubsByMetadata(StringValuePattern pattern) { - return wireMockServer.findAllStubsByMetadata(pattern); + public void verify(RequestPatternBuilder requestPatternBuilder) { + wireMockServer.verify(requestPatternBuilder); } /** - * Removes the stubs by metadata. + * Verify. * - * @param pattern - * the pattern + * @param count + * the count + * @param requestPatternBuilder + * the request pattern builder */ - public void removeStubsByMetadata(StringValuePattern pattern) { - wireMockServer.removeStubsByMetadata(pattern); + public void verify(int count, RequestPatternBuilder requestPatternBuilder) { + wireMockServer.verify(count, requestPatternBuilder); + } + + private Statement apply(final Statement base, final String methodName) { + return new Statement() { + public void evaluate() throws Throwable { + WireMockRule.this.methodName = methodName; + final Options localOptions = new WireMockRuleConfiguration(WireMockRule.this.options, methodName); + + new File(localOptions.filesRoot().getPath(), "mappings").mkdirs(); + new File(localOptions.filesRoot().getPath(), "__files").mkdirs(); + + WireMockRule.this.wireMockServer = new WireMockServer(localOptions); + WireMockRule.this.start(); + WireMock.configureFor("localhost", WireMockRule.this.port()); + + try { + WireMockRule.this.before(); + base.evaluate(); + WireMockRule.this.checkForUnmatchedRequests(); + } finally { + WireMockRule.this.after(); + WireMockRule.this.stop(); + WireMockRule.this.methodName = null; + } + + } + }; + } + + private void checkForUnmatchedRequests() { + if (this.failOnUnmatchedRequests) { + List unmatchedRequests = this.findAllUnmatchedRequests(); + if (!unmatchedRequests.isEmpty()) { + List nearMisses = this.findNearMissesForAllUnmatchedRequests(); + if (nearMisses.isEmpty()) { + throw VerificationException.forUnmatchedRequests(unmatchedRequests); + } + + throw VerificationException.forUnmatchedNearMisses(nearMisses); + } + } + } /** - * Import stubs. - * - * @param stubImport - * the stub import + * After. */ - public void importStubs(StubImport stubImport) { - wireMockServer.importStubs(stubImport); + protected void after() { } /** - * Gets the global settings. - * - * @return the global settings + * Before. */ - public GetGlobalSettingsResult getGlobalSettings() { - return wireMockServer.getGlobalSettings(); + protected void before() { } } diff --git a/src/test/java/org/kohsuke/github/WireMockRuleConfiguration.java b/src/test/java/org/kohsuke/github/WireMockRuleConfiguration.java index c933f885f4..1d8f2c305e 100644 --- a/src/test/java/org/kohsuke/github/WireMockRuleConfiguration.java +++ b/src/test/java/org/kohsuke/github/WireMockRuleConfiguration.java @@ -27,11 +27,29 @@ */ public class WireMockRuleConfiguration implements Options { - private final Options parent; + /** + * Options. + * + * @return the wire mock rule configuration + */ + public static WireMockRuleConfiguration options() { + return wireMockConfig(); + } + /** + * Wire mock config. + * + * @return the wire mock rule configuration + */ + public static WireMockRuleConfiguration wireMockConfig() { + return new WireMockRuleConfiguration(); + } private final String childDirectory; - private MappingsSource mappingsSource; private Map extensions = Maps.newLinkedHashMap(); + private MappingsSource mappingsSource; + + private final Options parent; + /** * Instantiates a new wire mock rule configuration. */ @@ -56,79 +74,39 @@ public class WireMockRuleConfiguration implements Options { } /** - * Wire mock config. - * - * @return the wire mock rule configuration - */ - public static WireMockRuleConfiguration wireMockConfig() { - return new WireMockRuleConfiguration(); - } - - /** - * Options. - * - * @return the wire mock rule configuration - */ - public static WireMockRuleConfiguration options() { - return wireMockConfig(); - } - - /** - * For child path. - * - * @param childPath - * the child path - * @return the wire mock rule configuration - */ - public WireMockRuleConfiguration forChildPath(String childPath) { - return new WireMockRuleConfiguration(this, childPath); - } - - private MappingsSource getMappingsSource() { - if (this.mappingsSource == null) { - this.mappingsSource = new JsonFileMappingsSource(this.filesRoot().child("mappings")); - } - - return this.mappingsSource; - } - - /** - * Files root. + * Bind address. * - * @return the file source + * @return the string */ - public FileSource filesRoot() { - return childDirectory != null ? parent.filesRoot().child(childDirectory) : parent.filesRoot(); + public String bindAddress() { + return parent.bindAddress(); } /** - * Mappings loader. + * Browser proxy settings. * - * @return the mappings loader + * @return the browser proxy settings */ - public MappingsLoader mappingsLoader() { - return this.getMappingsSource(); + public BrowserProxySettings browserProxySettings() { + return parent.browserProxySettings(); } /** - * Mappings saver. + * Browser proxying enabled. * - * @return the mappings saver + * @return true, if successful */ - public MappingsSaver mappingsSaver() { - return this.getMappingsSource(); + public boolean browserProxyingEnabled() { + return parent.browserProxyingEnabled(); } /** - * Mapping source. + * Container threads. * - * @param mappingsSource - * the mappings source - * @return the wire mock rule configuration + * @return the int */ - public WireMockRuleConfiguration mappingSource(MappingsSource mappingsSource) { - this.mappingsSource = mappingsSource; - return this; + public int containerThreads() { + return parent.containerThreads(); } /** @@ -147,123 +125,143 @@ public Map extensionsOfType(Class extensionT return result; } + /** + * Files root. + * + * @return the file source + */ + public FileSource filesRoot() { + return childDirectory != null ? parent.filesRoot().child(childDirectory) : parent.filesRoot(); + } + + /** + * For child path. + * + * @param childPath + * the child path + * @return the wire mock rule configuration + */ + public WireMockRuleConfiguration forChildPath(String childPath) { + return new WireMockRuleConfiguration(this, childPath); + } + // Simple wrappers /** - * Port number. + * Gets the admin authenticator. * - * @return the int + * @return the admin authenticator */ - public int portNumber() { - return parent.portNumber(); + public Authenticator getAdminAuthenticator() { + return parent.getAdminAuthenticator(); } /** - * Gets the http disabled. + * Gets the asynchronous response settings. * - * @return the http disabled + * @return the asynchronous response settings */ - public boolean getHttpDisabled() { - return parent.getHttpDisabled(); + public AsynchronousResponseSettings getAsynchronousResponseSettings() { + return parent.getAsynchronousResponseSettings(); } /** - * Container threads. + * Gets the chunked encoding policy. * - * @return the int + * @return the chunked encoding policy */ - public int containerThreads() { - return parent.containerThreads(); + public ChunkedEncodingPolicy getChunkedEncodingPolicy() { + return parent.getChunkedEncodingPolicy(); } /** - * Https settings. + * Gets the data truncation settings. * - * @return the https settings + * @return the data truncation settings */ - public HttpsSettings httpsSettings() { - return parent.httpsSettings(); + public DataTruncationSettings getDataTruncationSettings() { + return parent.getDataTruncationSettings(); } /** - * Jetty settings. + * Gets the disable optimize xml factories loading. * - * @return the jetty settings + * @return the disable optimize xml factories loading */ - public JettySettings jettySettings() { - return parent.jettySettings(); + public boolean getDisableOptimizeXmlFactoriesLoading() { + return parent.getDisableOptimizeXmlFactoriesLoading(); } /** - * Browser proxying enabled. + * Gets the disable strict http headers. * - * @return true, if successful + * @return the disable strict http headers */ - public boolean browserProxyingEnabled() { - return parent.browserProxyingEnabled(); + public boolean getDisableStrictHttpHeaders() { + return parent.getDisableStrictHttpHeaders(); } /** - * Browser proxy settings. + * Gets the gzip disabled. * - * @return the browser proxy settings + * @return the gzip disabled */ - public BrowserProxySettings browserProxySettings() { - return parent.browserProxySettings(); + public boolean getGzipDisabled() { + return parent.getGzipDisabled(); } /** - * Proxy via. + * Gets the http disabled. * - * @return the proxy settings + * @return the http disabled */ - public ProxySettings proxyVia() { - return parent.proxyVia(); + public boolean getHttpDisabled() { + return parent.getHttpDisabled(); } /** - * Notifier. + * Gets the https required for admin api. * - * @return the notifier + * @return the https required for admin api */ - public Notifier notifier() { - return parent.notifier(); + public boolean getHttpsRequiredForAdminApi() { + return parent.getHttpsRequiredForAdminApi(); } /** - * Request journal disabled. + * Gets the not matched renderer. * - * @return true, if successful + * @return the not matched renderer */ - public boolean requestJournalDisabled() { - return parent.requestJournalDisabled(); + public NotMatchedRenderer getNotMatchedRenderer() { + return parent.getNotMatchedRenderer(); } /** - * Max request journal entries. + * Gets the network address rules. * - * @return the optional + * @return the network address rules */ - public Optional maxRequestJournalEntries() { - return parent.maxRequestJournalEntries(); + public NetworkAddressRules getProxyTargetRules() { + return parent.getProxyTargetRules(); } /** - * Bind address. + * Gets the stub cors enabled. * - * @return the string + * @return the stub cors enabled */ - public String bindAddress() { - return parent.bindAddress(); + public boolean getStubCorsEnabled() { + return parent.getStubCorsEnabled(); } /** - * Matching headers. + * Gets the stub request logging disabled. * - * @return the list + * @return the stub request logging disabled */ - public List matchingHeaders() { - return parent.matchingHeaders(); + public boolean getStubRequestLoggingDisabled() { + return parent.getStubRequestLoggingDisabled(); } /** @@ -276,155 +274,157 @@ public HttpServerFactory httpServerFactory() { } /** - * Thread pool factory. + * Https settings. * - * @return the thread pool factory + * @return the https settings */ - public ThreadPoolFactory threadPoolFactory() { - return parent.threadPoolFactory(); + public HttpsSettings httpsSettings() { + return parent.httpsSettings(); } /** - * Should preserve host header. + * Jetty settings. * - * @return true, if successful + * @return the jetty settings */ - public boolean shouldPreserveHostHeader() { - return parent.shouldPreserveHostHeader(); + public JettySettings jettySettings() { + return parent.jettySettings(); } /** - * Proxy host header. + * Mapping source. * - * @return the string + * @param mappingsSource + * the mappings source + * @return the wire mock rule configuration */ - public String proxyHostHeader() { - return parent.proxyHostHeader(); + public WireMockRuleConfiguration mappingSource(MappingsSource mappingsSource) { + this.mappingsSource = mappingsSource; + return this; } /** - * Network traffic listener. + * Mappings loader. * - * @return the wiremock network traffic listener + * @return the mappings loader */ - public WiremockNetworkTrafficListener networkTrafficListener() { - return parent.networkTrafficListener(); + public MappingsLoader mappingsLoader() { + return this.getMappingsSource(); } /** - * Gets the admin authenticator. + * Mappings saver. * - * @return the admin authenticator + * @return the mappings saver */ - public Authenticator getAdminAuthenticator() { - return parent.getAdminAuthenticator(); + public MappingsSaver mappingsSaver() { + return this.getMappingsSource(); } /** - * Gets the https required for admin api. + * Matching headers. * - * @return the https required for admin api + * @return the list */ - public boolean getHttpsRequiredForAdminApi() { - return parent.getHttpsRequiredForAdminApi(); + public List matchingHeaders() { + return parent.matchingHeaders(); } /** - * Gets the not matched renderer. + * Max request journal entries. * - * @return the not matched renderer + * @return the optional */ - public NotMatchedRenderer getNotMatchedRenderer() { - return parent.getNotMatchedRenderer(); + public Optional maxRequestJournalEntries() { + return parent.maxRequestJournalEntries(); } /** - * Gets the asynchronous response settings. + * Network traffic listener. * - * @return the asynchronous response settings + * @return the wiremock network traffic listener */ - public AsynchronousResponseSettings getAsynchronousResponseSettings() { - return parent.getAsynchronousResponseSettings(); + public WiremockNetworkTrafficListener networkTrafficListener() { + return parent.networkTrafficListener(); } /** - * Gets the chunked encoding policy. + * Notifier. * - * @return the chunked encoding policy + * @return the notifier */ - public ChunkedEncodingPolicy getChunkedEncodingPolicy() { - return parent.getChunkedEncodingPolicy(); + public Notifier notifier() { + return parent.notifier(); } /** - * Gets the gzip disabled. + * Port number. * - * @return the gzip disabled + * @return the int */ - public boolean getGzipDisabled() { - return parent.getGzipDisabled(); + public int portNumber() { + return parent.portNumber(); } /** - * Gets the stub request logging disabled. + * Proxy host header. * - * @return the stub request logging disabled + * @return the string */ - public boolean getStubRequestLoggingDisabled() { - return parent.getStubRequestLoggingDisabled(); + public String proxyHostHeader() { + return parent.proxyHostHeader(); } /** - * Gets the stub cors enabled. + * Proxy via. * - * @return the stub cors enabled + * @return the proxy settings */ - public boolean getStubCorsEnabled() { - return parent.getStubCorsEnabled(); + public ProxySettings proxyVia() { + return parent.proxyVia(); } /** - * Timeout. + * Request journal disabled. * - * @return the long + * @return true, if successful */ - public long timeout() { - return parent.timeout(); + public boolean requestJournalDisabled() { + return parent.requestJournalDisabled(); } /** - * Gets the disable optimize xml factories loading. + * Should preserve host header. * - * @return the disable optimize xml factories loading + * @return true, if successful */ - public boolean getDisableOptimizeXmlFactoriesLoading() { - return parent.getDisableOptimizeXmlFactoriesLoading(); + public boolean shouldPreserveHostHeader() { + return parent.shouldPreserveHostHeader(); } /** - * Gets the disable strict http headers. + * Thread pool factory. * - * @return the disable strict http headers + * @return the thread pool factory */ - public boolean getDisableStrictHttpHeaders() { - return parent.getDisableStrictHttpHeaders(); + public ThreadPoolFactory threadPoolFactory() { + return parent.threadPoolFactory(); } /** - * Gets the data truncation settings. + * Timeout. * - * @return the data truncation settings + * @return the long */ - public DataTruncationSettings getDataTruncationSettings() { - return parent.getDataTruncationSettings(); + public long timeout() { + return parent.timeout(); } - /** - * Gets the network address rules. - * - * @return the network address rules - */ - public NetworkAddressRules getProxyTargetRules() { - return parent.getProxyTargetRules(); + private MappingsSource getMappingsSource() { + if (this.mappingsSource == null) { + this.mappingsSource = new JsonFileMappingsSource(this.filesRoot().child("mappings")); + } + + return this.mappingsSource; } } diff --git a/src/test/java/org/kohsuke/github/WireMockStatusReporterTest.java b/src/test/java/org/kohsuke/github/WireMockStatusReporterTest.java index bf9f63371e..bdac794e44 100644 --- a/src/test/java/org/kohsuke/github/WireMockStatusReporterTest.java +++ b/src/test/java/org/kohsuke/github/WireMockStatusReporterTest.java @@ -25,58 +25,6 @@ public class WireMockStatusReporterTest extends AbstractGitHubWireMockTest { public WireMockStatusReporterTest() { } - /** - * User when proxying auth correctly configured. - * - * @throws Exception - * the exception - */ - @Test - public void user_whenProxying_AuthCorrectlyConfigured() throws Exception { - snapshotNotAllowed(); - requireProxy("Tests proper configuration when proxying."); - - verifyAuthenticated(gitHub); - - assertThat(gitHub.getClient().getLogin(), not(equalTo(STUBBED_USER_LOGIN))); - - // If this user query fails, either the proxying config has broken (unlikely) - // or your auth settings are not being retrieved from the environment. - // Check your settings. - GHUser user = gitHub.getMyself(); - assertThat(user.getLogin(), notNullValue()); - - System.out.println(); - System.out.println( - "WireMockStatusReporterTest: GitHub proxying and user auth correctly configured for user login: " - + user.getLogin()); - System.out.println(); - } - - /** - * User when not proxying stubbed. - * - * @throws Exception - * the exception - */ - @Test - public void user_whenNotProxying_Stubbed() throws Exception { - snapshotNotAllowed(); - - assumeFalse("Test only valid when not proxying", mockGitHub.isUseProxy()); - - verifyAuthenticated(gitHub); - assertThat(gitHub.getClient().getLogin(), equalTo(STUBBED_USER_LOGIN)); - - GHUser user = gitHub.getMyself(); - // NOTE: the stubbed user does not have to match the login provided from the github object - // github.login is literally just a placeholder when mocking - assertThat(user.getLogin(), not(equalTo(STUBBED_USER_LOGIN))); - assertThat(user.getLogin(), equalTo("stubbed-user-login")); - - // System.out.println("GitHub proxying and user auth correctly configured for user login: " + user.getLogin()); - } - /** * Basic behaviors when not proxying. * @@ -156,6 +104,58 @@ public void BasicBehaviors_whenProxying() throws Exception { "{\"message\":\"Not Found\",\"documentation_url\":\"https://docs.github.com/rest/repos/repos#get-a-repository\"}")); } + /** + * User when not proxying stubbed. + * + * @throws Exception + * the exception + */ + @Test + public void user_whenNotProxying_Stubbed() throws Exception { + snapshotNotAllowed(); + + assumeFalse("Test only valid when not proxying", mockGitHub.isUseProxy()); + + verifyAuthenticated(gitHub); + assertThat(gitHub.getClient().getLogin(), equalTo(STUBBED_USER_LOGIN)); + + GHUser user = gitHub.getMyself(); + // NOTE: the stubbed user does not have to match the login provided from the github object + // github.login is literally just a placeholder when mocking + assertThat(user.getLogin(), not(equalTo(STUBBED_USER_LOGIN))); + assertThat(user.getLogin(), equalTo("stubbed-user-login")); + + // System.out.println("GitHub proxying and user auth correctly configured for user login: " + user.getLogin()); + } + + /** + * User when proxying auth correctly configured. + * + * @throws Exception + * the exception + */ + @Test + public void user_whenProxying_AuthCorrectlyConfigured() throws Exception { + snapshotNotAllowed(); + requireProxy("Tests proper configuration when proxying."); + + verifyAuthenticated(gitHub); + + assertThat(gitHub.getClient().getLogin(), not(equalTo(STUBBED_USER_LOGIN))); + + // If this user query fails, either the proxying config has broken (unlikely) + // or your auth settings are not being retrieved from the environment. + // Check your settings. + GHUser user = gitHub.getMyself(); + assertThat(user.getLogin(), notNullValue()); + + System.out.println(); + System.out.println( + "WireMockStatusReporterTest: GitHub proxying and user auth correctly configured for user login: " + + user.getLogin()); + System.out.println(); + } + /** * When snapshot ensure proxy. */ diff --git a/src/test/java/org/kohsuke/github/connector/GitHubConnectorResponseTest.java b/src/test/java/org/kohsuke/github/connector/GitHubConnectorResponseTest.java new file mode 100644 index 0000000000..daef1d758f --- /dev/null +++ b/src/test/java/org/kohsuke/github/connector/GitHubConnectorResponseTest.java @@ -0,0 +1,223 @@ +package org.kohsuke.github.connector; + +import com.fasterxml.jackson.databind.util.ByteBufferBackedInputStream; +import org.apache.commons.io.IOUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.Assert; +import org.junit.Test; +import org.kohsuke.github.AbstractGitHubWireMockTest; +import org.kohsuke.github.connector.GitHubConnectorResponse.ByteArrayResponse; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.isA; + +/** + * Test GitHubConnectorResponse + */ +public class GitHubConnectorResponseTest extends AbstractGitHubWireMockTest { + + // Extend ByteArrayResponse to preserve test coverage + private static class CustomBodyGitHubConnectorResponse extends ByteArrayResponse { + private final InputStream stream; + + CustomBodyGitHubConnectorResponse(int statusCode, InputStream stream) { + super(EMPTY_REQUEST, statusCode, new HashMap<>()); + this.stream = stream; + } + + @Override + protected InputStream rawBodyStream() throws IOException { + return stream; + } + } + + /** + * Empty request for response testing. + */ + public static final GitHubConnectorRequest EMPTY_REQUEST = new GitHubConnectorRequest() { + @NotNull @Override + public Map> allHeaders() { + return null; + } + + @Nullable @Override + public InputStream body() { + return null; + } + + @Nullable @Override + public String contentType() { + return null; + } + + @Override + public boolean hasBody() { + return false; + } + + @Nullable @Override + public String header(String name) { + return null; + } + + @NotNull @Override + public String method() { + return null; + } + + @NotNull @Override + public URL url() { + return null; + } + }; + + /** + * Instantiates a new GitHubConnectorResponseTest. + */ + public GitHubConnectorResponseTest() { + } + + /** + * Test forced rereadable body stream. + * + * @throws Exception + * for failures + */ + @Test + public void tesBodyStream_forced() throws Exception { + Exception e; + GitHubConnectorResponse response = new CustomBodyGitHubConnectorResponse(200, + new ByteBufferBackedInputStream(ByteBuffer.wrap("Hello!".getBytes(StandardCharsets.UTF_8)))); + // 200 status would be streamed body, force to buffered + response.setBodyStreamRereadable(); + + InputStream stream = response.bodyStream(); + assertThat(stream, isA(ByteArrayInputStream.class)); + String bodyString = IOUtils.toString(stream, StandardCharsets.UTF_8); + assertThat(bodyString, equalTo("Hello!")); + + // Buffered response can be read multiple times + bodyString = IOUtils.toString(response.bodyStream(), StandardCharsets.UTF_8); + assertThat(bodyString, equalTo("Hello!")); + + response.close(); + e = Assert.assertThrows(IOException.class, () -> response.bodyStream()); + assertThat(e.getMessage(), equalTo("Response is closed")); + } + + /** + * Test rereadable body stream. + * + * @throws Exception + * for failures + */ + @Test + public void tesBodyStream_rereadable() throws Exception { + Exception e; + GitHubConnectorResponse response = new CustomBodyGitHubConnectorResponse(404, + new ByteBufferBackedInputStream(ByteBuffer.wrap("Hello!".getBytes(StandardCharsets.UTF_8)))); + InputStream stream = response.bodyStream(); + assertThat(stream, isA(ByteArrayInputStream.class)); + String bodyString = IOUtils.toString(stream, StandardCharsets.UTF_8); + assertThat(bodyString, equalTo("Hello!")); + + // Buffered response can be read multiple times + bodyString = IOUtils.toString(response.bodyStream(), StandardCharsets.UTF_8); + assertThat(bodyString, equalTo("Hello!")); + + // should have no effect if already rereadable + response.setBodyStreamRereadable(); + + response.close(); + e = Assert.assertThrows(IOException.class, () -> response.bodyStream()); + assertThat(e.getMessage(), equalTo("Response is closed")); + } + + /** + * Test basic body stream. + * + * @throws Exception + * for failures + */ + @Test + public void testBodyStream() throws Exception { + Exception e; + GitHubConnectorResponse response = new CustomBodyGitHubConnectorResponse(200, + new ByteBufferBackedInputStream(ByteBuffer.wrap("Hello!".getBytes(StandardCharsets.UTF_8)))); + InputStream stream = response.bodyStream(); + assertThat(stream, isA(ByteBufferBackedInputStream.class)); + String bodyString = IOUtils.toString(stream, StandardCharsets.UTF_8); + assertThat(bodyString, equalTo("Hello!")); + + // Cannot change to rereadable + e = Assert.assertThrows(RuntimeException.class, () -> response.setBodyStreamRereadable()); + assertThat(e.getMessage(), equalTo("bodyStream() already called in read-once mode")); + + e = Assert.assertThrows(IOException.class, () -> response.bodyStream()); + assertThat(e.getMessage(), equalTo("Response body not rereadable")); + response.close(); + e = Assert.assertThrows(IOException.class, () -> response.bodyStream()); + assertThat(e.getMessage(), equalTo("Response is closed")); + } + + /** + * Test null body stream. + * + * @throws Exception + * for failures + */ + @Test + public void testBodyStream_null() throws Exception { + Exception e; + GitHubConnectorResponse response = new CustomBodyGitHubConnectorResponse(200, null); + e = Assert.assertThrows(IOException.class, () -> response.bodyStream()); + assertThat(e.getMessage(), equalTo("Response body missing, stream null")); + + // Cannot change to rereadable + e = Assert.assertThrows(RuntimeException.class, () -> response.setBodyStreamRereadable()); + assertThat(e.getMessage(), equalTo("bodyStream() already called in read-once mode")); + + e = Assert.assertThrows(IOException.class, () -> response.bodyStream()); + assertThat(e.getMessage(), equalTo("Response body not rereadable")); + + response.close(); + e = Assert.assertThrows(IOException.class, () -> response.bodyStream()); + assertThat(e.getMessage(), equalTo("Response is closed")); + } + + /** + * Test null rereadable body stream. + * + * @throws Exception + * for failures + */ + @Test + public void testBodyStream_null_buffered() throws Exception { + Exception e; + GitHubConnectorResponse response = new CustomBodyGitHubConnectorResponse(404, null); + e = Assert.assertThrows(IOException.class, () -> response.bodyStream()); + assertThat(e.getMessage(), equalTo("Response body missing, stream null")); + // Buffered response can be read multiple times + e = Assert.assertThrows(IOException.class, () -> response.bodyStream()); + assertThat(e.getMessage(), equalTo("Response body missing, stream null")); + + // force should have no effect after first read attempt + response.setBodyStreamRereadable(); + + response.close(); + e = Assert.assertThrows(IOException.class, () -> response.bodyStream()); + assertThat(e.getMessage(), equalTo("Response is closed")); + } + +} diff --git a/src/test/java/org/kohsuke/github/extras/authorization/AuthorizationTokenRefreshTest.java b/src/test/java/org/kohsuke/github/extras/authorization/AuthorizationTokenRefreshTest.java index 00b268912b..e090b46329 100644 --- a/src/test/java/org/kohsuke/github/extras/authorization/AuthorizationTokenRefreshTest.java +++ b/src/test/java/org/kohsuke/github/extras/authorization/AuthorizationTokenRefreshTest.java @@ -14,6 +14,19 @@ */ public class AuthorizationTokenRefreshTest extends AbstractGitHubWireMockTest { + static class RefreshingAuthorizationProvider implements AuthorizationProvider { + private boolean used = false; + + @Override + public String getEncodedAuthorization() { + if (used) { + return "refreshed token"; + } + used = true; + return "original token"; + } + } + /** * Instantiates a new test. */ @@ -21,16 +34,6 @@ public AuthorizationTokenRefreshTest() { useDefaultGitHub = false; } - /** - * Gets the wire mock options. - * - * @return the wire mock options - */ - @Override - protected WireMockConfiguration getWireMockOptions() { - return super.getWireMockOptions().extensions(templating.newResponseTransformer()); - } - /** * Retried request should get new token when the old one expires. * @@ -64,16 +67,13 @@ public void testNotNewWhenOldOneIsStillValid() throws IOException { assertThat("Usernames match", "kohsuke".equals(kohsuke.getLogin())); } - static class RefreshingAuthorizationProvider implements AuthorizationProvider { - private boolean used = false; - - @Override - public String getEncodedAuthorization() { - if (used) { - return "refreshed token"; - } - used = true; - return "original token"; - } + /** + * Gets the wire mock options. + * + * @return the wire mock options + */ + @Override + protected WireMockConfiguration getWireMockOptions() { + return super.getWireMockOptions().extensions(templating.newResponseTransformer()); } } diff --git a/src/test/java/org/kohsuke/github/extras/authorization/JWTTokenProviderTest.java b/src/test/java/org/kohsuke/github/extras/authorization/JWTTokenProviderTest.java index 4b98cc18c5..70a73fabb1 100644 --- a/src/test/java/org/kohsuke/github/extras/authorization/JWTTokenProviderTest.java +++ b/src/test/java/org/kohsuke/github/extras/authorization/JWTTokenProviderTest.java @@ -32,31 +32,13 @@ */ public class JWTTokenProviderTest extends AbstractGHAppInstallationTest { - /** - * Create default JWTTokenProviderTest instance - */ - public JWTTokenProviderTest() { - } - - private static String TEST_APP_ID_2 = "83009"; private static String PRIVATE_KEY_FILE_APP_2 = "/ghapi-test-app-2.private-key.pem"; + private static String TEST_APP_ID_2 = "83009"; /** - * Test caching valid authorization. - * - * @throws IOException - * Signals that an I/O exception has occurred. + * Create default JWTTokenProviderTest instance */ - @Test - public void testCachingValidAuthorization() throws IOException { - assertThat(jwtProvider1, instanceOf(JWTTokenProvider.class)); - JWTTokenProvider provider = (JWTTokenProvider) jwtProvider1; - - assertThat(provider.isNotValid(), is(true)); - String authorization = provider.getEncodedAuthorization(); - assertThat(provider.isNotValid(), is(false)); - String authorizationRefresh = provider.getEncodedAuthorization(); - assertThat(authorizationRefresh, sameInstance(authorization)); + public JWTTokenProviderTest() { } /** @@ -83,6 +65,24 @@ public void testAuthorizationHeaderPattern() throws GeneralSecurityException, IO gh.getApp(); } + /** + * Test caching valid authorization. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testCachingValidAuthorization() throws IOException { + assertThat(jwtProvider1, instanceOf(JWTTokenProvider.class)); + JWTTokenProvider provider = (JWTTokenProvider) jwtProvider1; + + assertThat(provider.isNotValid(), is(true)); + String authorization = provider.getEncodedAuthorization(); + assertThat(provider.isNotValid(), is(false)); + String authorizationRefresh = provider.getEncodedAuthorization(); + assertThat(authorizationRefresh, sameInstance(authorization)); + } + /** * Test issued at skew. * diff --git a/src/test/java/org/kohsuke/github/extras/okhttp3/GitHubCachingTest.java b/src/test/java/org/kohsuke/github/extras/okhttp3/GitHubCachingTest.java index f66717f31a..334b7e1052 100644 --- a/src/test/java/org/kohsuke/github/extras/okhttp3/GitHubCachingTest.java +++ b/src/test/java/org/kohsuke/github/extras/okhttp3/GitHubCachingTest.java @@ -19,8 +19,6 @@ import java.io.File; import java.io.IOException; -import static org.junit.Assert.fail; - // TODO: Auto-generated Javadoc /** * Test showing the behavior of OkHttpGitHubConnector cache with GitHub 404 responses. @@ -29,27 +27,20 @@ */ public class GitHubCachingTest extends AbstractGitHubWireMockTest { - /** - * Instantiates a new git hub caching test. - */ - public GitHubCachingTest() { - useDefaultGitHub = false; + private static int clientCount = 0; + + private static GHRepository getRepository(GitHub gitHub) throws IOException { + return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); } /** The test ref name. */ String testRefName = "heads/test/content_ref_cache"; /** - * Gets the wire mock options. - * - * @return the wire mock options + * Instantiates a new git hub caching test. */ - @Override - protected WireMockConfiguration getWireMockOptions() { - return super.getWireMockOptions() - // Use the same data files as the 2.x test - .usingFilesUnderDirectory(baseRecordPath.replace("/okhttp3/", "/")) - .extensions(templating.newResponseTransformer()); + public GitHubCachingTest() { + useDefaultGitHub = false; } /** @@ -187,8 +178,6 @@ public void testCached404() throws Exception { repo.getRef(testRefName); } - private static int clientCount = 0; - private OkHttpClient createClient(boolean useCache) throws IOException { OkHttpClient.Builder builder = new OkHttpClient().newBuilder(); @@ -205,8 +194,17 @@ private OkHttpClient createClient(boolean useCache) throws IOException { return builder.build(); } - private static GHRepository getRepository(GitHub gitHub) throws IOException { - return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); + /** + * Gets the wire mock options. + * + * @return the wire mock options + */ + @Override + protected WireMockConfiguration getWireMockOptions() { + return super.getWireMockOptions() + // Use the same data files as the 2.x test + .usingFilesUnderDirectory(baseRecordPath.replace("/okhttp3/", "/")) + .extensions(templating.newResponseTransformer()); } } diff --git a/src/test/java/org/kohsuke/github/extras/okhttp3/OkHttpGitHubConnectorTest.java b/src/test/java/org/kohsuke/github/extras/okhttp3/OkHttpGitHubConnectorTest.java index fcca58b931..a204fe6e2e 100644 --- a/src/test/java/org/kohsuke/github/extras/okhttp3/OkHttpGitHubConnectorTest.java +++ b/src/test/java/org/kohsuke/github/extras/okhttp3/OkHttpGitHubConnectorTest.java @@ -45,76 +45,36 @@ */ public class OkHttpGitHubConnectorTest extends AbstractGitHubWireMockTest { - /** - * Instantiates a new ok http git hub connector test. - */ - public OkHttpGitHubConnectorTest() { - useDefaultGitHub = false; - } + private static int defaultNetworkRequestCount = 16; private static int defaultRateLimitUsed = 17; - private static int okhttpRateLimitUsed = 17; - private static int maxAgeZeroRateLimitUsed = 7; - private static int maxAgeThreeRateLimitUsed = 7; + private static int maxAgeNoneHitCount = 11; + private static int maxAgeNoneNetworkRequestCount = 5; private static int maxAgeNoneRateLimitUsed = 4; + private static int maxAgeThreeHitCount = 10; - private static int userRequestCount = 0; - - private static int defaultNetworkRequestCount = 16; - private static int okhttpNetworkRequestCount = 16; - private static int maxAgeZeroNetworkRequestCount = 16; private static int maxAgeThreeNetworkRequestCount = 9; - private static int maxAgeNoneNetworkRequestCount = 5; + private static int maxAgeThreeRateLimitUsed = 7; private static int maxAgeZeroHitCount = 10; - private static int maxAgeThreeHitCount = 10; - private static int maxAgeNoneHitCount = 11; - - private GHRateLimit rateLimitBefore; - private Cache cache = null; + private static int maxAgeZeroNetworkRequestCount = 16; + private static int maxAgeZeroRateLimitUsed = 7; + private static int okhttpNetworkRequestCount = 16; - /** - * Gets the wire mock options. - * - * @return the wire mock options - */ - @Override - protected WireMockConfiguration getWireMockOptions() { - return super.getWireMockOptions() - // Use the same data files as the 2.x test - .usingFilesUnderDirectory(baseRecordPath.replace("/okhttp3/OkHttpGitHubConnector", "/OkHttpConnector")) - .extensions(templating.newResponseTransformer()); + private static int okhttpRateLimitUsed = 17; + private static int userRequestCount = 0; + private static GHRepository getRepository(GitHub gitHub) throws IOException { + return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); } - /** - * Setup repo. - * - * @throws Exception - * the exception - */ - @Before - public void setupRepo() throws Exception { - if (mockGitHub.isUseProxy()) { - GHRepository repo = getRepository(getNonRecordingGitHub()); - repo.setDescription("Resetting"); - - // Let things settle a bit between tests when working against the live site - Thread.sleep(5000); - userRequestCount = 1; - } - } + private Cache cache = null; + private GHRateLimit rateLimitBefore; /** - * Delete cache. - * - * @throws IOException - * Signals that an I/O exception has occurred. + * Instantiates a new ok http git hub connector test. */ - @After - public void deleteCache() throws IOException { - if (cache != null) { - cache.delete(); - } + public OkHttpGitHubConnectorTest() { + useDefaultGitHub = false; } /** @@ -138,15 +98,19 @@ public void DefaultConnector() throws Exception { } /** - * Ok http connector no cache. + * Ok http connector cache max age default zero. * * @throws Exception * the exception */ @Test - public void OkHttpConnector_NoCache() throws Exception { + public void OkHttpConnector_Cache_MaxAgeDefault_Zero() throws Exception { + // The responses were recorded from github, but the Date headers + // have been templated to make caching behavior work as expected. + // This is reasonable as long as the number of network requests matches up. + snapshotNotAllowed(); - OkHttpClient client = createClient(false); + OkHttpClient client = createClient(true); OkHttpGitHubConnector connector = new OkHttpGitHubConnector(client); this.gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()) @@ -156,12 +120,12 @@ public void OkHttpConnector_NoCache() throws Exception { doTestActions(); // Testing behavior after change - // Uncached okhttp connection gets updated correctly but at cost of rate limit + // NOTE: max-age=0 produces the same result at uncached without added rate-limit use. assertThat(getRepository(gitHub).getDescription(), is("Tricky")); - checkRequestAndLimit(okhttpNetworkRequestCount, okhttpRateLimitUsed); + checkRequestAndLimit(maxAgeZeroNetworkRequestCount, maxAgeZeroRateLimitUsed); - assertThat("Cache", cache, is(nullValue())); + assertThat("getHitCount", cache.hitCount(), is(maxAgeZeroHitCount)); } /** @@ -230,19 +194,15 @@ public void OkHttpConnector_Cache_MaxAge_Three() throws Exception { } /** - * Ok http connector cache max age default zero. + * Ok http connector no cache. * * @throws Exception * the exception */ @Test - public void OkHttpConnector_Cache_MaxAgeDefault_Zero() throws Exception { - // The responses were recorded from github, but the Date headers - // have been templated to make caching behavior work as expected. - // This is reasonable as long as the number of network requests matches up. - snapshotNotAllowed(); + public void OkHttpConnector_NoCache() throws Exception { - OkHttpClient client = createClient(true); + OkHttpClient client = createClient(false); OkHttpGitHubConnector connector = new OkHttpGitHubConnector(client); this.gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()) @@ -252,12 +212,43 @@ public void OkHttpConnector_Cache_MaxAgeDefault_Zero() throws Exception { doTestActions(); // Testing behavior after change - // NOTE: max-age=0 produces the same result at uncached without added rate-limit use. + // Uncached okhttp connection gets updated correctly but at cost of rate limit assertThat(getRepository(gitHub).getDescription(), is("Tricky")); - checkRequestAndLimit(maxAgeZeroNetworkRequestCount, maxAgeZeroRateLimitUsed); + checkRequestAndLimit(okhttpNetworkRequestCount, okhttpRateLimitUsed); - assertThat("getHitCount", cache.hitCount(), is(maxAgeZeroHitCount)); + assertThat("Cache", cache, is(nullValue())); + } + + /** + * Delete cache. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @After + public void deleteCache() throws IOException { + if (cache != null) { + cache.delete(); + } + } + + /** + * Setup repo. + * + * @throws Exception + * the exception + */ + @Before + public void setupRepo() throws Exception { + if (mockGitHub.isUseProxy()) { + GHRepository repo = getRepository(getNonRecordingGitHub()); + repo.setDescription("Resetting"); + + // Let things settle a bit between tests when working against the live site + Thread.sleep(5000); + userRequestCount = 1; + } } private void checkRequestAndLimit(int networkRequestCount, int rateLimitUsed) { @@ -271,10 +262,6 @@ private void checkRequestAndLimit(int networkRequestCount, int rateLimitUsed) { } - private int getRequestCount() { - return mockGitHub.apiServer().countRequestsMatching(RequestPatternBuilder.allRequests().build()).getCount(); - } - private OkHttpClient createClient(boolean useCache) throws IOException { OkHttpClient.Builder builder = new OkHttpClient().newBuilder(); @@ -323,6 +310,10 @@ private void doTestActions() throws Exception { pollForChange("Tricky"); } + private int getRequestCount() { + return mockGitHub.apiServer().countRequestsMatching(RequestPatternBuilder.allRequests().build()).getCount(); + } + private void pollForChange(String name) throws IOException, InterruptedException { getRepository(gitHub).getDescription(); Thread.sleep(500); @@ -340,8 +331,17 @@ private void pollForChange(String name) throws IOException, InterruptedException } } - private static GHRepository getRepository(GitHub gitHub) throws IOException { - return gitHub.getOrganization("hub4j-test-org").getRepository("github-api"); + /** + * Gets the wire mock options. + * + * @return the wire mock options + */ + @Override + protected WireMockConfiguration getWireMockOptions() { + return super.getWireMockOptions() + // Use the same data files as the 2.x test + .usingFilesUnderDirectory(baseRecordPath.replace("/okhttp3/OkHttpGitHubConnector", "/OkHttpConnector")) + .extensions(templating.newResponseTransformer()); } } diff --git a/src/test/java/org/kohsuke/github/internal/EnumUtilsTest.java b/src/test/java/org/kohsuke/github/internal/EnumUtilsTest.java index 5169e947a5..88df55a576 100644 --- a/src/test/java/org/kohsuke/github/internal/EnumUtilsTest.java +++ b/src/test/java/org/kohsuke/github/internal/EnumUtilsTest.java @@ -11,6 +11,10 @@ */ public class EnumUtilsTest { + private enum TestEnum { + UNKNOWN, VALUE_1, VALUE_2; + } + /** * Create default EnumUtilsTest instance */ @@ -36,8 +40,4 @@ public void testGetEnum() { assertThat(EnumUtils.getNullableEnumOrDefault(TestEnum.class, "vAlUe_2", TestEnum.UNKNOWN), equalTo(TestEnum.VALUE_2)); } - - private enum TestEnum { - VALUE_1, VALUE_2, UNKNOWN; - } } diff --git a/src/test/java/org/kohsuke/github/internal/graphql/response/GHGraphQLResponseMockTest.java b/src/test/java/org/kohsuke/github/internal/graphql/response/GHGraphQLResponseMockTest.java new file mode 100644 index 0000000000..965e8fc7c6 --- /dev/null +++ b/src/test/java/org/kohsuke/github/internal/graphql/response/GHGraphQLResponseMockTest.java @@ -0,0 +1,73 @@ +package org.kohsuke.github.internal.graphql.response; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.json.JsonMapper; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; + +/** + * Test GHGraphQLResponse's methods + */ +class GHGraphQLResponseMockTest { + + private GHGraphQLResponse convertJsonToGraphQLResponse(String json) throws JsonProcessingException { + JsonMapper mapper = JsonMapper.builder().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).build(); + + ObjectReader objectReader = mapper.reader(); + JavaType javaType = objectReader.getTypeFactory() + .constructParametricType(GHGraphQLResponse.class, Object.class); + + return objectReader.forType(javaType).readValue(json); + } + + /** + * Test get data throws exception when response means error + * + * @throws JsonProcessingException + * Json parse exception + * + */ + @Test + void getDataFailure() throws JsonProcessingException { + String graphQLErrorResponse = "{\"data\": {\"enablePullRequestAutoMerge\": null},\"errors\": [{\"type\": " + + "\"UNPROCESSABLE\",\"path\": [\"enablePullRequestAutoMerge\"],\"locations\": [{\"line\": 2," + + "\"column\": 5}],\"message\": \"hub4j does not have a verified email, which is required to enable " + + "auto-merging.\"}]}"; + + GHGraphQLResponse response = convertJsonToGraphQLResponse(graphQLErrorResponse); + + try { + response.getData(); + } catch (RuntimeException e) { + assertThat(e.getMessage(), containsString("Response not successful, data invalid")); + } + } + + /** + * Test getErrorMessages throws exception when response means not error + * + * @throws JsonProcessingException + * Json parse exception + */ + @Test + void getErrorMessagesFailure() throws JsonProcessingException { + String graphQLSuccessResponse = "{\"data\": {\"repository\": {\"pullRequest\": {\"id\": " + + "\"PR_TEMP_GRAPHQL_ID\"}}}}"; + + GHGraphQLResponse response = convertJsonToGraphQLResponse(graphQLSuccessResponse); + + List errorMessages = response.getErrorMessages(); + + assertThat(errorMessages, is(empty())); + } + +} diff --git a/src/test/resources/no-reflect-and-serialization-list b/src/test/resources/no-reflect-and-serialization-list index 4e248c490c..4ad893272c 100644 --- a/src/test/resources/no-reflect-and-serialization-list +++ b/src/test/resources/no-reflect-and-serialization-list @@ -82,6 +82,5 @@ org.kohsuke.github.internal.DefaultGitHubConnector org.kohsuke.github.internal.EnumUtils org.kohsuke.github.internal.Previews org.kohsuke.github.EnterpriseManagedSupport -org.kohsuke.github.GHAutolink org.kohsuke.github.GHAutolinkBuilder org.kohsuke.github.GHRepositoryForkBuilder \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/10-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/10-notifications.json index b16ad3007a..7304188eb8 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/10-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/10-notifications.json @@ -2,7 +2,7 @@ "id": "a979348d-c6be-4cb7-8877-7c42a6f013ae", "name": "notifications", "request": { - "url": "/notifications?all=true&page=9", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=9", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F628:BFE1F0:5DB3A14D", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "a979348d-c6be-4cb7-8877-7c42a6f013ae", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/11-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/11-notifications.json index 85f3b573c0..c02ba6ccd0 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/11-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/11-notifications.json @@ -2,7 +2,7 @@ "id": "89714ed3-235b-4914-86a8-44ad66d59f30", "name": "notifications", "request": { - "url": "/notifications?all=true&page=10", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=10", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F646:BFE217:5DB3A14D", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "89714ed3-235b-4914-86a8-44ad66d59f30", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/12-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/12-notifications.json index 8907d54645..84c0a42873 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/12-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/12-notifications.json @@ -2,7 +2,7 @@ "id": "2b718339-36d3-4c6b-9484-79cdd79a79e4", "name": "notifications", "request": { - "url": "/notifications?all=true&page=11", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=11", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F657:BFE22B:5DB3A14D", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "2b718339-36d3-4c6b-9484-79cdd79a79e4", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/13-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/13-notifications.json index e6b46f7ecb..3481d18741 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/13-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/13-notifications.json @@ -2,7 +2,7 @@ "id": "989db4b3-8dde-4065-b4ef-6a2d90a2753a", "name": "notifications", "request": { - "url": "/notifications?all=true&page=12", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=12", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F661:BFE238:5DB3A14E", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "989db4b3-8dde-4065-b4ef-6a2d90a2753a", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/14-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/14-notifications.json index bd27626d4e..08f647fcad 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/14-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/14-notifications.json @@ -2,7 +2,7 @@ "id": "50b907ef-a983-4cc9-bfd2-e2ba76eae729", "name": "notifications", "request": { - "url": "/notifications?all=true&page=13", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=13", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F66D:BFE242:5DB3A14E", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "50b907ef-a983-4cc9-bfd2-e2ba76eae729", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/15-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/15-notifications.json index 2086a8fdbd..139228796a 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/15-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/15-notifications.json @@ -2,7 +2,7 @@ "id": "f2648b73-4af1-4be3-a2a4-9edc712c5d59", "name": "notifications", "request": { - "url": "/notifications?all=true&page=14", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=14", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F679:BFE254:5DB3A14E", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "f2648b73-4af1-4be3-a2a4-9edc712c5d59", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/16-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/16-notifications.json index 60d836dec6..596d062a2b 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/16-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/16-notifications.json @@ -2,7 +2,7 @@ "id": "ee5a6c9f-da3a-47e7-a393-b403e82ae5d9", "name": "notifications", "request": { - "url": "/notifications?all=true&page=15", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=15", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F689:BFE266:5DB3A14E", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "ee5a6c9f-da3a-47e7-a393-b403e82ae5d9", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/17-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/17-notifications.json index 25cf77ff48..2b3fcaa99d 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/17-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/17-notifications.json @@ -2,7 +2,7 @@ "id": "a41aeecf-7097-4ac6-b857-ab14797afe0a", "name": "notifications", "request": { - "url": "/notifications?all=true&page=16", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=16", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F69E:BFE27A:5DB3A14F", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "a41aeecf-7097-4ac6-b857-ab14797afe0a", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/18-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/18-notifications.json index 42c582816a..3e04973df2 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/18-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/18-notifications.json @@ -2,7 +2,7 @@ "id": "e1d519f7-9bd2-4fcd-a288-2391944ec7ca", "name": "notifications", "request": { - "url": "/notifications?all=true&page=17", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=17", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F6B4:BFE291:5DB3A14F", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "e1d519f7-9bd2-4fcd-a288-2391944ec7ca", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/19-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/19-notifications.json index 5bc7d00622..a2bc722b01 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/19-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/19-notifications.json @@ -2,7 +2,7 @@ "id": "1b694852-8043-418c-a76e-39370f22db96", "name": "notifications", "request": { - "url": "/notifications?all=true&page=18", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=18", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F6CA:BFE2B2:5DB3A14F", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "1b694852-8043-418c-a76e-39370f22db96", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/2-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/2-notifications.json index 1b160a9c8b..46240a1d92 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/2-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/2-notifications.json @@ -2,7 +2,7 @@ "id": "d85867b0-1efe-43f5-bdf4-1b9aef03ef55", "name": "notifications", "request": { - "url": "/notifications?all=true", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F5C2:BFE16E:5DB3A14B", - "Link": "; rel=\"next\", ; rel=\"last\"" + "Link": "; rel=\"next\", ; rel=\"last\"" } }, "uuid": "d85867b0-1efe-43f5-bdf4-1b9aef03ef55", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/20-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/20-notifications.json index d99f71b368..5d4bc6c3df 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/20-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/20-notifications.json @@ -2,7 +2,7 @@ "id": "48908278-ce2f-4cec-8662-6f4ca3d81226", "name": "notifications", "request": { - "url": "/notifications?all=true&page=19", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=19", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F6E4:BFE2D4:5DB3A150", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "48908278-ce2f-4cec-8662-6f4ca3d81226", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/21-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/21-notifications.json index 0d01191175..6e011fce6f 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/21-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/21-notifications.json @@ -2,7 +2,7 @@ "id": "42be6527-0570-4353-b42f-d0cae80258e3", "name": "notifications", "request": { - "url": "/notifications?all=true&page=20", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=20", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F70C:BFE300:5DB3A150", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "42be6527-0570-4353-b42f-d0cae80258e3", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/22-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/22-notifications.json index 0578186986..bcaf874f82 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/22-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/22-notifications.json @@ -2,7 +2,7 @@ "id": "5720c49c-c69b-495b-b7e6-b885d88c10b1", "name": "notifications", "request": { - "url": "/notifications?all=true&page=21", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=21", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F71C:BFE311:5DB3A150", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "5720c49c-c69b-495b-b7e6-b885d88c10b1", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/23-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/23-notifications.json index 28bdd8d595..11e575c017 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/23-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/23-notifications.json @@ -2,7 +2,7 @@ "id": "045c369a-0818-455a-afe1-3ae9ec919af2", "name": "notifications", "request": { - "url": "/notifications?all=true&page=22", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=22", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F731:BFE31F:5DB3A151", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "045c369a-0818-455a-afe1-3ae9ec919af2", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/24-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/24-notifications.json index fb5ac17e0f..450bd8797d 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/24-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/24-notifications.json @@ -2,7 +2,7 @@ "id": "bfc7733f-6dff-4675-81e9-926103c40b83", "name": "notifications", "request": { - "url": "/notifications?all=true&page=23", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=23", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F73A:BFE332:5DB3A151", - "Link": "; rel=\"prev\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"first\"" } }, "uuid": "bfc7733f-6dff-4675-81e9-926103c40b83", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/3-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/3-notifications.json index 686668959e..963a173b9a 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/3-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/3-notifications.json @@ -2,7 +2,7 @@ "id": "d9617266-1ca6-44b2-b495-52c1f3be4b91", "name": "notifications", "request": { - "url": "/notifications?all=true&page=2", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=2", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F5D0:BFE187:5DB3A14B", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "d9617266-1ca6-44b2-b495-52c1f3be4b91", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/4-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/4-notifications.json index 590392dc5a..bfd2be216d 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/4-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/4-notifications.json @@ -2,7 +2,7 @@ "id": "6dea5253-3aa2-4484-b97a-effcad5c6ebd", "name": "notifications", "request": { - "url": "/notifications?all=true&page=3", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=3", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F5DC:BFE198:5DB3A14B", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "6dea5253-3aa2-4484-b97a-effcad5c6ebd", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/5-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/5-notifications.json index a12e55a622..4f60ae1eb3 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/5-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/5-notifications.json @@ -2,7 +2,7 @@ "id": "a8e9449d-b78c-4e46-801e-59fc459920d3", "name": "notifications", "request": { - "url": "/notifications?all=true&page=4", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=4", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F5E4:BFE1A3:5DB3A14C", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "a8e9449d-b78c-4e46-801e-59fc459920d3", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/6-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/6-notifications.json index 4c448dd5ba..c6b6c09cb9 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/6-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/6-notifications.json @@ -2,7 +2,7 @@ "id": "2658e99a-6619-4b0b-b70f-814a0841839e", "name": "notifications", "request": { - "url": "/notifications?all=true&page=5", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=5", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F5F0:BFE1B4:5DB3A14C", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "2658e99a-6619-4b0b-b70f-814a0841839e", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/7-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/7-notifications.json index 038b30ff7b..7f78aac737 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/7-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/7-notifications.json @@ -2,7 +2,7 @@ "id": "f2524684-5156-4db6-97fa-10dedac5f779", "name": "notifications", "request": { - "url": "/notifications?all=true&page=6", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=6", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F5FD:BFE1C1:5DB3A14C", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "f2524684-5156-4db6-97fa-10dedac5f779", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/8-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/8-notifications.json index 4a245443ff..6b084811db 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/8-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/8-notifications.json @@ -2,7 +2,7 @@ "id": "9437189d-2f1b-47de-898d-66fde88ef05b", "name": "notifications", "request": { - "url": "/notifications?all=true&page=7", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=7", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F60A:BFE1CD:5DB3A14C", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "9437189d-2f1b-47de-898d-66fde88ef05b", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/9-notifications.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/9-notifications.json index 3a479236b2..b2c14fe37e 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/9-notifications.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/notifications/mappings/9-notifications.json @@ -2,7 +2,7 @@ "id": "1de52522-e900-4b19-90cd-758573c2349a", "name": "notifications", "request": { - "url": "/notifications?all=true&page=8", + "url": "/notifications?all=true&since=1970-01-01T00%3A00%3A00Z&page=8", "method": "GET", "headers": { "Accept": { @@ -41,7 +41,7 @@ "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", "X-GitHub-Request-Id": "CB13:833E:A1F612:BFE1D9:5DB3A14C", - "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" + "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\", ; rel=\"first\"" } }, "uuid": "1de52522-e900-4b19-90cd-758573c2349a", diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchIssuesOnly/__files/1-user.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchIssuesOnly/__files/1-user.json new file mode 100644 index 0000000000..fbc5eae788 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchIssuesOnly/__files/1-user.json @@ -0,0 +1,36 @@ +{ + "login": "Anonycoders", + "id": 40047636, + "node_id": "MDQ6VXNlcjQwMDQ3NjM2", + "avatar_url": "https://avatars.githubusercontent.com/u/40047636?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Anonycoders", + "html_url": "https://github.com/Anonycoders", + "followers_url": "https://api.github.com/users/Anonycoders/followers", + "following_url": "https://api.github.com/users/Anonycoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Anonycoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Anonycoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Anonycoders/subscriptions", + "organizations_url": "https://api.github.com/users/Anonycoders/orgs", + "repos_url": "https://api.github.com/users/Anonycoders/repos", + "events_url": "https://api.github.com/users/Anonycoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Anonycoders/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false, + "name": "Sorena Sarabadani", + "company": "@Adevinta", + "blog": "", + "location": "Berlin, Germany", + "email": "sorena.sarabadani@gmail.com", + "hireable": null, + "bio": "Ex-Shopifyer - Adevinta/Kleinanzeigen", + "twitter_username": "sorena_s", + "notification_email": "sorena.sarabadani@gmail.com", + "public_repos": 12, + "public_gists": 0, + "followers": 38, + "following": 4, + "created_at": "2018-06-08T02:07:15Z", + "updated_at": "2026-01-24T22:07:12Z" +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchIssuesOnly/__files/2-search_issues.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchIssuesOnly/__files/2-search_issues.json new file mode 100644 index 0000000000..74d8e9279d --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchIssuesOnly/__files/2-search_issues.json @@ -0,0 +1,2430 @@ +{ + "total_count": 553, + "incomplete_results": false, + "items": [ + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2150", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2150/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2150/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2150/events", + "html_url": "https://github.com/hub4j/github-api/issues/2150", + "id": 3495762524, + "node_id": "I_kwDOAAlq-s7QXRpc", + "number": 2150, + "title": "Add new DYNAMIC event to GHEvent enum", + "user": { + "login": "kkroner8451", + "id": 14809736, + "node_id": "MDQ6VXNlcjE0ODA5NzM2", + "avatar_url": "https://avatars.githubusercontent.com/u/14809736?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/kkroner8451", + "html_url": "https://github.com/kkroner8451", + "followers_url": "https://api.github.com/users/kkroner8451/followers", + "following_url": "https://api.github.com/users/kkroner8451/following{/other_user}", + "gists_url": "https://api.github.com/users/kkroner8451/gists{/gist_id}", + "starred_url": "https://api.github.com/users/kkroner8451/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/kkroner8451/subscriptions", + "organizations_url": "https://api.github.com/users/kkroner8451/orgs", + "repos_url": "https://api.github.com/users/kkroner8451/repos", + "events_url": "https://api.github.com/users/kkroner8451/events{/privacy}", + "received_events_url": "https://api.github.com/users/kkroner8451/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2025-10-08T14:45:59Z", + "updated_at": "2025-10-23T00:51:33Z", + "closed_at": "2025-10-23T00:51:33Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "GitHub now has a new event of \"dynamic\" on workflow runs. I can't find any published docs, but looking at the data it appears to be dynamic runs of workflows for things like Dependabot, etc. The results is when `GHWorkflowRun` maps `event` field in `getEvent()` method to the `GHEvent` enum it ends up being UNKNOWN and logs a warning in the `EnumUtils` class that the value is unknown. DYNAMIC should be added to the GHEvent enum to minimize log warnings and add clarity for known (even if not published) possible values.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2150/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/hub4j/github-api/issues/2150/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2144", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2144/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2144/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2144/events", + "html_url": "https://github.com/hub4j/github-api/issues/2144", + "id": 3450064053, + "node_id": "I_kwDOAAlq-s7No8y1", + "number": 2144, + "title": "Unbridged Artifact for 1.330", + "user": { + "login": "gilday", + "id": 1431609, + "node_id": "MDQ6VXNlcjE0MzE2MDk=", + "avatar_url": "https://avatars.githubusercontent.com/u/1431609?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gilday", + "html_url": "https://github.com/gilday", + "followers_url": "https://api.github.com/users/gilday/followers", + "following_url": "https://api.github.com/users/gilday/following{/other_user}", + "gists_url": "https://api.github.com/users/gilday/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gilday/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gilday/subscriptions", + "organizations_url": "https://api.github.com/users/gilday/orgs", + "repos_url": "https://api.github.com/users/gilday/repos", + "events_url": "https://api.github.com/users/gilday/events{/privacy}", + "received_events_url": "https://api.github.com/users/gilday/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-09-24T16:05:57Z", + "updated_at": "2025-10-23T17:46:14Z", + "closed_at": "2025-10-23T17:46:14Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "Could you please release the `github-api-unbridged` artifact for version 1.330? This would allow projects using Mockito in their test suites to upgrade to the Jackson-compatible version while maintaining test functionality.\n\n## Current Situation\n- ✅ github-api:1.330 (bridged) - Released and available\n- ❌ github-api-unbridged:1.330 - Not yet released\n- 🔧 Tests fail with the bridged version due to Mockito incompatibilities\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2144/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/hub4j/github-api/issues/2144/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2140", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2140/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2140/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2140/events", + "html_url": "https://github.com/hub4j/github-api/issues/2140", + "id": 3379331716, + "node_id": "I_kwDOAAlq-s7JbIKE", + "number": 2140, + "title": "NoClassDefFoundError when using Jackson 2.20", + "user": { + "login": "sfc-gh-pvillard", + "id": 189795559, + "node_id": "U_kgDOC1AM5w", + "avatar_url": "https://avatars.githubusercontent.com/u/189795559?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sfc-gh-pvillard", + "html_url": "https://github.com/sfc-gh-pvillard", + "followers_url": "https://api.github.com/users/sfc-gh-pvillard/followers", + "following_url": "https://api.github.com/users/sfc-gh-pvillard/following{/other_user}", + "gists_url": "https://api.github.com/users/sfc-gh-pvillard/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sfc-gh-pvillard/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sfc-gh-pvillard/subscriptions", + "organizations_url": "https://api.github.com/users/sfc-gh-pvillard/orgs", + "repos_url": "https://api.github.com/users/sfc-gh-pvillard/repos", + "events_url": "https://api.github.com/users/sfc-gh-pvillard/events{/privacy}", + "received_events_url": "https://api.github.com/users/sfc-gh-pvillard/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 3, + "created_at": "2025-09-03T10:45:15Z", + "updated_at": "2025-09-04T17:48:24Z", + "closed_at": "2025-09-03T13:08:25Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "````java\njava.lang.NoClassDefFoundError: Could not initialize class org.kohsuke.github.GitHubClient\n at org.kohsuke.github.GitHub.(GitHub.java:137)\n at org.kohsuke.github.GitHubBuilder.build(GitHubBuilder.java:509)\n at ...\nCaused by: java.lang.ExceptionInInitializerError: Exception java.lang.NoSuchFieldError: Class com.fasterxml.jackson.databind.PropertyNamingStrategy does not have member field 'com.fasterxml.jackson.databind.PropertyNamingStrategy SNAKE_CASE' [in thread \"ForkJoinPool-1-worker-2\"]\n at org.kohsuke.github.GitHubClient.(GitHubClient.java:92)\n ... 15 common frames omitted\n````\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2140/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/hub4j/github-api/issues/2140/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2137", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2137/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2137/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2137/events", + "html_url": "https://github.com/hub4j/github-api/issues/2137", + "id": 3373441552, + "node_id": "I_kwDOAAlq-s7JEqIQ", + "number": 2137, + "title": "Runtime errors when using jackson 2.20.0", + "user": { + "login": "ketan", + "id": 10598, + "node_id": "MDQ6VXNlcjEwNTk4", + "avatar_url": "https://avatars.githubusercontent.com/u/10598?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/ketan", + "html_url": "https://github.com/ketan", + "followers_url": "https://api.github.com/users/ketan/followers", + "following_url": "https://api.github.com/users/ketan/following{/other_user}", + "gists_url": "https://api.github.com/users/ketan/gists{/gist_id}", + "starred_url": "https://api.github.com/users/ketan/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/ketan/subscriptions", + "organizations_url": "https://api.github.com/users/ketan/orgs", + "repos_url": "https://api.github.com/users/ketan/repos", + "events_url": "https://api.github.com/users/ketan/events{/privacy}", + "received_events_url": "https://api.github.com/users/ketan/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2025-09-01T17:51:50Z", + "updated_at": "2025-09-02T19:22:33Z", + "closed_at": "2025-09-02T19:22:33Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "**Environment**\n\n* Jackson databind - `2.20.0`\n* org.kohsuke:github-api `1.329`\n\nWhen running with this combination, there's a runtime error when loading GitHubClient. In particular, `PropertyNamingStrategy.SNAKE_CASE` seems to have been removed in jackson databind `2.20.0` as part of https://github.com/FasterXML/jackson-databind/commit/4d2083160fef06e6063a3082f0fdaab8c2803793. https://github.com/FasterXML/jackson-databind/issues/4136 contains the discussion around it.\n\nThe suggestion is to use `PropertyNamingStrategies#SNAKE_CASE` instead.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2137/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/hub4j/github-api/issues/2137/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2128", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2128/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2128/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2128/events", + "html_url": "https://github.com/hub4j/github-api/issues/2128", + "id": 3364033362, + "node_id": "I_kwDOAAlq-s7IgxNS", + "number": 2128, + "title": "GHRepository#getIssues() takes 30s", + "user": { + "login": "Alathreon", + "id": 45936420, + "node_id": "MDQ6VXNlcjQ1OTM2NDIw", + "avatar_url": "https://avatars.githubusercontent.com/u/45936420?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Alathreon", + "html_url": "https://github.com/Alathreon", + "followers_url": "https://api.github.com/users/Alathreon/followers", + "following_url": "https://api.github.com/users/Alathreon/following{/other_user}", + "gists_url": "https://api.github.com/users/Alathreon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Alathreon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Alathreon/subscriptions", + "organizations_url": "https://api.github.com/users/Alathreon/orgs", + "repos_url": "https://api.github.com/users/Alathreon/repos", + "events_url": "https://api.github.com/users/Alathreon/events{/privacy}", + "received_events_url": "https://api.github.com/users/Alathreon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-08-28T16:54:27Z", + "updated_at": "2025-08-30T20:45:46Z", + "closed_at": "2025-08-30T20:45:46Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "**Describe the bug**\nA clear and concise description of what the bug is.\nI am using the method GHRepository#getIssues() to get all issues for auto complete purpose in a discord bot, but the problem is that there are more than 1000 issues and fetching them all takes 28s...\nIt could be partially patched by allowing the client to set a page size to large numbers, so it doesn't need to do a HTTP call 44 times.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n- Have a repository with 1000+ issues\n- Call getIssues() with no filter\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\nIt should take much less time.\n\n**Desktop (please complete the following information):**\n- Version 1.329\n- Tested in many Windows/Linux distributions\n\n**Additional context**\nAdd any other context about the problem here.\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2128/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/hub4j/github-api/issues/2128/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2112", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2112/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2112/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2112/events", + "html_url": "https://github.com/hub4j/github-api/issues/2112", + "id": 3218889361, + "node_id": "I_kwDOAAlq-s6_3FqR", + "number": 2112, + "title": "I am using `v2.0-rc.3` and when starting an application I get a load of warnings regarding annotation used in the github-api library.", + "user": { + "login": "HerrDerb", + "id": 7398256, + "node_id": "MDQ6VXNlcjczOTgyNTY=", + "avatar_url": "https://avatars.githubusercontent.com/u/7398256?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/HerrDerb", + "html_url": "https://github.com/HerrDerb", + "followers_url": "https://api.github.com/users/HerrDerb/followers", + "following_url": "https://api.github.com/users/HerrDerb/following{/other_user}", + "gists_url": "https://api.github.com/users/HerrDerb/gists{/gist_id}", + "starred_url": "https://api.github.com/users/HerrDerb/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/HerrDerb/subscriptions", + "organizations_url": "https://api.github.com/users/HerrDerb/orgs", + "repos_url": "https://api.github.com/users/HerrDerb/repos", + "events_url": "https://api.github.com/users/HerrDerb/events{/privacy}", + "received_events_url": "https://api.github.com/users/HerrDerb/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2025-07-10T11:04:06Z", + "updated_at": "2025-07-23T23:42:08Z", + "closed_at": "2025-07-23T23:42:08Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "I am using `v2.0-rc.3` and when starting an application I get a load of warnings regarding annotation used in the github-api library.\n\n```\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GitHub.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings': class file for edu.umd.cs.findbugs.annotations.SuppressFBWarnings not found\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GitHub.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GitHub.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GitHub.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHArtifact.class): warning: Cannot find annotation method 'value()' in type 'WithBridgeMethods': class file for com.infradna.tool.bridge_method_injector.WithBridgeMethods not found\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHArtifact.class): warning: Cannot find annotation method 'adapterMethod()' in type 'WithBridgeMethods'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHArtifact.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHArtifact.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'value()' in type 'WithBridgeMethods'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'adapterMethod()' in type 'WithBridgeMethods'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GitHub.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings': class file for edu.umd.cs.findbugs.annotations.SuppressFBWarnings not found\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GitHub.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\n...\n```\n\nTo avoid this one needs to add `spotbugs-annotations` and `bridge-method-annotation` to each project.\nAdditionally `bridge-method-annotation` is not even hosted on maven central and a third party repository also needs to be added. This is too much overhead just to avoid warnings. Therefor by adding the deps as `runtime` solves this issue for all users of the github library \n\n_Originally posted by @HerrDerb in https://github.com/hub4j/github-api/discussions/2090_", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2112/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/hub4j/github-api/issues/2112/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2111", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2111/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2111/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2111/events", + "html_url": "https://github.com/hub4j/github-api/issues/2111", + "id": 3218887702, + "node_id": "I_kwDOAAlq-s6_3FQW", + "number": 2111, + "title": "Inlcude optional dependencies to avoid warnings", + "user": { + "login": "HerrDerb", + "id": 7398256, + "node_id": "MDQ6VXNlcjczOTgyNTY=", + "avatar_url": "https://avatars.githubusercontent.com/u/7398256?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/HerrDerb", + "html_url": "https://github.com/HerrDerb", + "followers_url": "https://api.github.com/users/HerrDerb/followers", + "following_url": "https://api.github.com/users/HerrDerb/following{/other_user}", + "gists_url": "https://api.github.com/users/HerrDerb/gists{/gist_id}", + "starred_url": "https://api.github.com/users/HerrDerb/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/HerrDerb/subscriptions", + "organizations_url": "https://api.github.com/users/HerrDerb/orgs", + "repos_url": "https://api.github.com/users/HerrDerb/repos", + "events_url": "https://api.github.com/users/HerrDerb/events{/privacy}", + "received_events_url": "https://api.github.com/users/HerrDerb/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-07-10T11:03:38Z", + "updated_at": "2025-10-23T18:09:10Z", + "closed_at": "2025-10-23T18:09:10Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "I am using `v2.0-rc.3` and when starting an application I get a load of warnings regarding annotation used in the github-api library.\r\n\r\n```\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHArtifact.class): warning: Cannot find annotation method 'value()' in type 'WithBridgeMethods': class file for com.infradna.tool.bridge_method_injector.WithBridgeMethods not found\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHArtifact.class): warning: Cannot find annotation method 'adapterMethod()' in type 'WithBridgeMethods'\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHArtifact.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings': class file for edu.umd.cs.findbugs.annotations.SuppressFBWarnings not found\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHArtifact.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'value()' in type 'WithBridgeMethods'\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'adapterMethod()' in type 'WithBridgeMethods'\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings'\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings'\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings'\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\r\n...\r\n```\r\nTo avoid this warnings, one needs to add the required dependencies \r\n```\r\n- com.infradna.tool:bridge-method-annotation\r\n- com.github.spotbugs:spotbugs-annotations\r\n```\r\nto each project. Additionally the `bridge-method-annotation` dependency doesn't even exist on maven central and a thirdparty repo needs to be added to the project which is a massive overhead to only avoid warnings.\r\n\r\nBy adding the dependencies as `runtime` this solves the problem for all users of this library\r\n_Originally posted by @HerrDerb in https://github.com/hub4j/github-api/discussions/2090_", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2111/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/hub4j/github-api/issues/2111/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2073", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2073/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2073/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2073/events", + "html_url": "https://github.com/hub4j/github-api/issues/2073", + "id": 2950997416, + "node_id": "I_kwDOAAlq-s6v5KWo", + "number": 2073, + "title": "Replace methods which return `Date` with `Instant` or `ZonedDateTime` for 2.x", + "user": { + "login": "solonovamax", + "id": 46940694, + "node_id": "MDQ6VXNlcjQ2OTQwNjk0", + "avatar_url": "https://avatars.githubusercontent.com/u/46940694?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/solonovamax", + "html_url": "https://github.com/solonovamax", + "followers_url": "https://api.github.com/users/solonovamax/followers", + "following_url": "https://api.github.com/users/solonovamax/following{/other_user}", + "gists_url": "https://api.github.com/users/solonovamax/gists{/gist_id}", + "starred_url": "https://api.github.com/users/solonovamax/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/solonovamax/subscriptions", + "organizations_url": "https://api.github.com/users/solonovamax/orgs", + "repos_url": "https://api.github.com/users/solonovamax/repos", + "events_url": "https://api.github.com/users/solonovamax/events{/privacy}", + "received_events_url": "https://api.github.com/users/solonovamax/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1662551322, + "node_id": "MDU6TGFiZWwxNjYyNTUxMzIy", + "url": "https://api.github.com/repos/hub4j/github-api/labels/enhancement", + "name": "enhancement", + "color": "0e8a16", + "default": true, + "description": "" + }, + { + "id": 1780165359, + "node_id": "MDU6TGFiZWwxNzgwMTY1MzU5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/breaking%20change", + "name": "breaking change", + "color": "b60205", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-03-26T23:38:02Z", + "updated_at": "2025-04-11T07:02:09Z", + "closed_at": "2025-04-11T07:02:09Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "There are multiple methods which return `Date` such as `GHObject#getUpdatedAt()`.\nthe old java `Date` api should really be replaced with one of the following:\n- `Instant` (imo this would be the best pick)\n- `LocalDateTime`\n- `ZonedDateTime`\n\nsince a 2.x version is currently being made, now would be the best time to replace it.\n\nit is recommended to avoid the old date-time api and instead use the newer api. many of the methods on `Date` are even marked as deprecated.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2073/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/hub4j/github-api/issues/2073/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2061", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2061/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2061/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2061/events", + "html_url": "https://github.com/hub4j/github-api/issues/2061", + "id": 2923132207, + "node_id": "I_kwDOAAlq-s6uO3Uv", + "number": 2061, + "title": "`GHIssue#isPullRequest` is false despite being a PR", + "user": { + "login": "Haarolean", + "id": 1494347, + "node_id": "MDQ6VXNlcjE0OTQzNDc=", + "avatar_url": "https://avatars.githubusercontent.com/u/1494347?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Haarolean", + "html_url": "https://github.com/Haarolean", + "followers_url": "https://api.github.com/users/Haarolean/followers", + "following_url": "https://api.github.com/users/Haarolean/following{/other_user}", + "gists_url": "https://api.github.com/users/Haarolean/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Haarolean/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Haarolean/subscriptions", + "organizations_url": "https://api.github.com/users/Haarolean/orgs", + "repos_url": "https://api.github.com/users/Haarolean/repos", + "events_url": "https://api.github.com/users/Haarolean/events{/privacy}", + "received_events_url": "https://api.github.com/users/Haarolean/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 265902919, + "node_id": "MDU6TGFiZWwyNjU5MDI5MTk=", + "url": "https://api.github.com/repos/hub4j/github-api/labels/bug", + "name": "bug", + "color": "e11d21", + "default": true, + "description": null + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2025-03-16T15:23:51Z", + "updated_at": "2026-02-10T07:49:35Z", + "closed_at": "2026-02-10T07:49:35Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "**Describe the bug**\n`isPullRequest` in GHIssue returns false due to `pull_request == null` condition. The PR object was acquired via `payload.getPullRequest()` method of `GHEventPayload.PullRequest` class.\n\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Get an instance of `GHEventPayload.PullRequest` event\n2. Get the pr entity via `payload.getPullRequest()`\n3. Observe that despite object being an instance of `GHPullRequest` the return value of `isPullRequest` method is false.\n\n**Expected behavior**\nexpected true", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2061/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/hub4j/github-api/issues/2061/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2057", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2057/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2057/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2057/events", + "html_url": "https://github.com/hub4j/github-api/issues/2057", + "id": 2913719792, + "node_id": "I_kwDOAAlq-s6tq9Xw", + "number": 2057, + "title": "Comments seem to be stripped?", + "user": { + "login": "koppor", + "id": 1366654, + "node_id": "MDQ6VXNlcjEzNjY2NTQ=", + "avatar_url": "https://avatars.githubusercontent.com/u/1366654?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/koppor", + "html_url": "https://github.com/koppor", + "followers_url": "https://api.github.com/users/koppor/followers", + "following_url": "https://api.github.com/users/koppor/following{/other_user}", + "gists_url": "https://api.github.com/users/koppor/gists{/gist_id}", + "starred_url": "https://api.github.com/users/koppor/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/koppor/subscriptions", + "organizations_url": "https://api.github.com/users/koppor/orgs", + "repos_url": "https://api.github.com/users/koppor/repos", + "events_url": "https://api.github.com/users/koppor/events{/privacy}", + "received_events_url": "https://api.github.com/users/koppor/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-03-12T11:56:46Z", + "updated_at": "2025-03-12T13:08:56Z", + "closed_at": "2025-03-12T13:08:55Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "https://github.com/JabRef/jabref/pull/12710#pullrequestreview-2678113413\n\n![Image](https://github.com/user-attachments/assets/1e135941-65b8-410d-ae19-04f40d1786db)\n\nDebug of `comment.getBody();` does not include this text:\n\n![Image](https://github.com/user-attachments/assets/c1530e54-67ab-4520-b269-1a70c1065dd1)\n\norg.kohsuke.github.GHIssueComment#update looks good - is it a GitHub API issue.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2057/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/hub4j/github-api/issues/2057/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2040", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2040/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2040/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2040/events", + "html_url": "https://github.com/hub4j/github-api/issues/2040", + "id": 2869668624, + "node_id": "I_kwDOAAlq-s6rC6sQ", + "number": 2040, + "title": "Status of 2.x stream", + "user": { + "login": "nedtwigg", + "id": 2924992, + "node_id": "MDQ6VXNlcjI5MjQ5OTI=", + "avatar_url": "https://avatars.githubusercontent.com/u/2924992?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/nedtwigg", + "html_url": "https://github.com/nedtwigg", + "followers_url": "https://api.github.com/users/nedtwigg/followers", + "following_url": "https://api.github.com/users/nedtwigg/following{/other_user}", + "gists_url": "https://api.github.com/users/nedtwigg/gists{/gist_id}", + "starred_url": "https://api.github.com/users/nedtwigg/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/nedtwigg/subscriptions", + "organizations_url": "https://api.github.com/users/nedtwigg/orgs", + "repos_url": "https://api.github.com/users/nedtwigg/repos", + "events_url": "https://api.github.com/users/nedtwigg/events{/privacy}", + "received_events_url": "https://api.github.com/users/nedtwigg/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1664647346, + "node_id": "MDU6TGFiZWwxNjY0NjQ3MzQ2", + "url": "https://api.github.com/repos/hub4j/github-api/labels/task", + "name": "task", + "color": "bfdadc", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 6, + "created_at": "2025-02-21T17:51:09Z", + "updated_at": "2025-03-23T06:48:12Z", + "closed_at": "2025-03-23T06:48:12Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "Hello! Thanks very much for maintaining this great library! The changes coming in 2.x seem good, I'm eager to be an early adopter of it.\n\nDo you have rough ideas around\n\n- what might be broken in the 2.x train\n- what might still change in the 2.x train\n- how long until the 2.x train is out of beta\n\nNot looking for hard commitments or anything, just your general vibe.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2040/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/hub4j/github-api/issues/2040/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2039", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2039/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2039/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2039/events", + "html_url": "https://github.com/hub4j/github-api/issues/2039", + "id": 2869632221, + "node_id": "I_kwDOAAlq-s6rCxzd", + "number": 2039, + "title": "Allow a GHPullRequest to set auto-merge", + "user": { + "login": "roxspring", + "id": 783694, + "node_id": "MDQ6VXNlcjc4MzY5NA==", + "avatar_url": "https://avatars.githubusercontent.com/u/783694?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/roxspring", + "html_url": "https://github.com/roxspring", + "followers_url": "https://api.github.com/users/roxspring/followers", + "following_url": "https://api.github.com/users/roxspring/following{/other_user}", + "gists_url": "https://api.github.com/users/roxspring/gists{/gist_id}", + "starred_url": "https://api.github.com/users/roxspring/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/roxspring/subscriptions", + "organizations_url": "https://api.github.com/users/roxspring/orgs", + "repos_url": "https://api.github.com/users/roxspring/repos", + "events_url": "https://api.github.com/users/roxspring/events{/privacy}", + "received_events_url": "https://api.github.com/users/roxspring/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 265902955, + "node_id": "MDU6TGFiZWwyNjU5MDI5NTU=", + "url": "https://api.github.com/repos/hub4j/github-api/labels/new%20feature", + "name": "new feature", + "color": "f4cc53", + "default": false, + "description": "" + }, + { + "id": 1662551322, + "node_id": "MDU6TGFiZWwxNjYyNTUxMzIy", + "url": "https://api.github.com/repos/hub4j/github-api/labels/enhancement", + "name": "enhancement", + "color": "0e8a16", + "default": true, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 7, + "created_at": "2025-02-21T17:32:36Z", + "updated_at": "2025-03-19T17:24:00Z", + "closed_at": "2025-03-19T17:24:00Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "I've been using the API to generate PRs for our main repository in a similar vein to dependabot, and would really like to be able to set those to auto-merge (which has been allowed in our repository):\n```java\nghPullRequest.requestAutoMerge();\n```\n\nClearly this isn't supported by the Java API, presumably because it's not supported by the GitHub REST API either. However it is supported by via a couple (oh, the irony!) of GraphQL API queries:\n\n```graphql\nquery GetPullRequestID {\n repository(name: \"$repo\", owner: \"$owner\") {\n pullRequest(number: \"$prnum\") {\n id\n }\n }\n}\n```\n\n```graphql\nmutation EnableAutoMergeOnPullRequest {\n enablePullRequestAutoMerge(input: {pullRequestId: \"$pullRequestID\", mergeMethod: MERGE}) {\n clientMutationId\n }\n}\n```\n\nRather than boiling the ocean by requesting general purpose public GraphQL support (#521), I wonder whether it might be acceptable to implement some low level GraphQL support that could be used internally to implement specific features not available in the REST API, such as enabling auto-merge on a PR. Perhaps in time this could become the basis for some general support but the immediate goal would be to enable access to APIs.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2039/reactions", + "total_count": 1, + "+1": 1, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/hub4j/github-api/issues/2039/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2033", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2033/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2033/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2033/events", + "html_url": "https://github.com/hub4j/github-api/issues/2033", + "id": 2855685583, + "node_id": "I_kwDOAAlq-s6qNk3P", + "number": 2033, + "title": "Change GHRepository getIssue and getPullRequest to not mentiond ID and make it clear it is number", + "user": { + "login": "rnveach", + "id": 5427943, + "node_id": "MDQ6VXNlcjU0Mjc5NDM=", + "avatar_url": "https://avatars.githubusercontent.com/u/5427943?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/rnveach", + "html_url": "https://github.com/rnveach", + "followers_url": "https://api.github.com/users/rnveach/followers", + "following_url": "https://api.github.com/users/rnveach/following{/other_user}", + "gists_url": "https://api.github.com/users/rnveach/gists{/gist_id}", + "starred_url": "https://api.github.com/users/rnveach/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/rnveach/subscriptions", + "organizations_url": "https://api.github.com/users/rnveach/orgs", + "repos_url": "https://api.github.com/users/rnveach/repos", + "events_url": "https://api.github.com/users/rnveach/events{/privacy}", + "received_events_url": "https://api.github.com/users/rnveach/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 265902919, + "node_id": "MDU6TGFiZWwyNjU5MDI5MTk=", + "url": "https://api.github.com/repos/hub4j/github-api/labels/bug", + "name": "bug", + "color": "e11d21", + "default": true, + "description": null + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-02-15T19:55:08Z", + "updated_at": "2025-02-25T17:58:50Z", + "closed_at": "2025-02-25T17:58:50Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "`GHRepository#getIssue` takes id as an `int`. Same with `getPullRequest`.\nhttps://github.com/hub4j/github-api/blob/e14ec3b3677760714cd096ad8157a3e6a6dded65/src/main/java/org/kohsuke/github/GHRepository.java#L358\nhttps://github.com/hub4j/github-api/blob/e14ec3b3677760714cd096ad8157a3e6a6dded65/src/main/java/org/kohsuke/github/GHRepository.java#L1509\n\n`GHIssue` and `GHPullRequest` which extend `GHObject` returns a `long` for an `id`.\nhttps://github.com/hub4j/github-api/blob/e14ec3b3677760714cd096ad8157a3e6a6dded65/src/main/java/org/kohsuke/github/GHObject.java#L32\n\nTo call either, with the original object requires you to downcast.\n```\n\t\t\tif (issue) {\n\t\t\t\tfinal GHIssue issue = repository.getIssue((int) item.getId());\n\t\t\t} else {\n\t\t\t\tfinal GHPullRequest pullRequest = repository.getPullRequest((int) item.getId());\n\t\t\t}\n```\n\nIt seems to me both should be updated to be a `long` to make everything consistent and not require down casting.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2033/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/hub4j/github-api/issues/2033/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2032", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2032/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2032/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2032/events", + "html_url": "https://github.com/hub4j/github-api/issues/2032", + "id": 2854448657, + "node_id": "I_kwDOAAlq-s6qI24R", + "number": 2032, + "title": "Add more methods to QueryBuilder", + "user": { + "login": "rnveach", + "id": 5427943, + "node_id": "MDQ6VXNlcjU0Mjc5NDM=", + "avatar_url": "https://avatars.githubusercontent.com/u/5427943?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/rnveach", + "html_url": "https://github.com/rnveach", + "followers_url": "https://api.github.com/users/rnveach/followers", + "following_url": "https://api.github.com/users/rnveach/following{/other_user}", + "gists_url": "https://api.github.com/users/rnveach/gists{/gist_id}", + "starred_url": "https://api.github.com/users/rnveach/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/rnveach/subscriptions", + "organizations_url": "https://api.github.com/users/rnveach/orgs", + "repos_url": "https://api.github.com/users/rnveach/repos", + "events_url": "https://api.github.com/users/rnveach/events{/privacy}", + "received_events_url": "https://api.github.com/users/rnveach/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1662551322, + "node_id": "MDU6TGFiZWwxNjYyNTUxMzIy", + "url": "https://api.github.com/repos/hub4j/github-api/labels/enhancement", + "name": "enhancement", + "color": "0e8a16", + "default": true, + "description": "" + }, + { + "id": 1991401619, + "node_id": "MDU6TGFiZWwxOTkxNDAxNjE5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/good%20first%20issue", + "name": "good first issue", + "color": "00FF00", + "default": true, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 5, + "created_at": "2025-02-14T18:22:40Z", + "updated_at": "2026-02-10T07:58:25Z", + "closed_at": "2026-02-10T07:58:25Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "1)\n`GHPullRequestQueryBuilder` doesn't seem to support `pageSize(int)` but `GHIssueQueryBuilder` does.\n\nI don't know if there is a technical reason, but it would help to support repos with a large number of PRs. I didn't realize this was a possibility at first and took a long time to start iterating through issues.\n\n2)\n`GHIssueQueryBuilder` seems to still pull in PRs. Is it possible to add another method to the query to drop PRs from Issue queries and the same for Issues from PR queries. It would help the amount of data to transfer from the server if the server supported it.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2032/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/hub4j/github-api/issues/2032/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2026", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2026/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2026/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2026/events", + "html_url": "https://github.com/hub4j/github-api/issues/2026", + "id": 2843272749, + "node_id": "I_kwDOAAlq-s6peOYt", + "number": 2026, + "title": "GHIssueStateReason should add reopened", + "user": { + "login": "rnveach", + "id": 5427943, + "node_id": "MDQ6VXNlcjU0Mjc5NDM=", + "avatar_url": "https://avatars.githubusercontent.com/u/5427943?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/rnveach", + "html_url": "https://github.com/rnveach", + "followers_url": "https://api.github.com/users/rnveach/followers", + "following_url": "https://api.github.com/users/rnveach/following{/other_user}", + "gists_url": "https://api.github.com/users/rnveach/gists{/gist_id}", + "starred_url": "https://api.github.com/users/rnveach/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/rnveach/subscriptions", + "organizations_url": "https://api.github.com/users/rnveach/orgs", + "repos_url": "https://api.github.com/users/rnveach/repos", + "events_url": "https://api.github.com/users/rnveach/events{/privacy}", + "received_events_url": "https://api.github.com/users/rnveach/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1662551322, + "node_id": "MDU6TGFiZWwxNjYyNTUxMzIy", + "url": "https://api.github.com/repos/hub4j/github-api/labels/enhancement", + "name": "enhancement", + "color": "0e8a16", + "default": true, + "description": "" + }, + { + "id": 1991401619, + "node_id": "MDU6TGFiZWwxOTkxNDAxNjE5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/good%20first%20issue", + "name": "good first issue", + "color": "00FF00", + "default": true, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-02-10T18:22:59Z", + "updated_at": "2025-02-14T17:51:34Z", + "closed_at": "2025-02-14T17:51:34Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "While going against a repository I am involved with, I recieved this message in the console:\n````\norg.kohsuke.github.internal.EnumUtils getEnumOrDefault\nWARNING: Unknown value reopened for enum class org.kohsuke.github.GHIssueStateReason, defaulting to UNKNOWN\n````\nWould be best if it was added for proper recognition.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2026/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/hub4j/github-api/issues/2026/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2011", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2011/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2011/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2011/events", + "html_url": "https://github.com/hub4j/github-api/issues/2011", + "id": 2796095271, + "node_id": "I_kwDOAAlq-s6mqQcn", + "number": 2011, + "title": "Support for /app/installation-requests", + "user": { + "login": "anujhydrabadi", + "id": 129152617, + "node_id": "U_kgDOB7K2aQ", + "avatar_url": "https://avatars.githubusercontent.com/u/129152617?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/anujhydrabadi", + "html_url": "https://github.com/anujhydrabadi", + "followers_url": "https://api.github.com/users/anujhydrabadi/followers", + "following_url": "https://api.github.com/users/anujhydrabadi/following{/other_user}", + "gists_url": "https://api.github.com/users/anujhydrabadi/gists{/gist_id}", + "starred_url": "https://api.github.com/users/anujhydrabadi/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/anujhydrabadi/subscriptions", + "organizations_url": "https://api.github.com/users/anujhydrabadi/orgs", + "repos_url": "https://api.github.com/users/anujhydrabadi/repos", + "events_url": "https://api.github.com/users/anujhydrabadi/events{/privacy}", + "received_events_url": "https://api.github.com/users/anujhydrabadi/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2025-01-17T18:43:29Z", + "updated_at": "2025-01-21T06:57:01Z", + "closed_at": "2025-01-21T06:57:01Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "Request to support the following endpoint: https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#list-installation-requests-for-the-authenticated-app", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2011/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/hub4j/github-api/issues/2011/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2008", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2008/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2008/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2008/events", + "html_url": "https://github.com/hub4j/github-api/issues/2008", + "id": 2788286884, + "node_id": "I_kwDOAAlq-s6mMeGk", + "number": 2008, + "title": "Website down", + "user": { + "login": "wgorder-kr", + "id": 60753563, + "node_id": "MDQ6VXNlcjYwNzUzNTYz", + "avatar_url": "https://avatars.githubusercontent.com/u/60753563?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/wgorder-kr", + "html_url": "https://github.com/wgorder-kr", + "followers_url": "https://api.github.com/users/wgorder-kr/followers", + "following_url": "https://api.github.com/users/wgorder-kr/following{/other_user}", + "gists_url": "https://api.github.com/users/wgorder-kr/gists{/gist_id}", + "starred_url": "https://api.github.com/users/wgorder-kr/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/wgorder-kr/subscriptions", + "organizations_url": "https://api.github.com/users/wgorder-kr/orgs", + "repos_url": "https://api.github.com/users/wgorder-kr/repos", + "events_url": "https://api.github.com/users/wgorder-kr/events{/privacy}", + "received_events_url": "https://api.github.com/users/wgorder-kr/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 3, + "created_at": "2025-01-14T21:11:21Z", + "updated_at": "2025-02-07T19:44:32Z", + "closed_at": "2025-02-07T19:44:30Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "Can you get your website back up? I am new to the project but is a bit painful to have to look through the test cases for basics.\r\n\r\nIt looks like you were using github pages so not sure what happened.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2008/reactions", + "total_count": 5, + "+1": 5, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/hub4j/github-api/issues/2008/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1993", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1993/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1993/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1993/events", + "html_url": "https://github.com/hub4j/github-api/issues/1993", + "id": 2715964451, + "node_id": "I_kwDOAAlq-s6h4lQj", + "number": 1993, + "title": "Feature Request: Fork Only Default Branch", + "user": { + "login": "gounthar", + "id": 116569, + "node_id": "MDQ6VXNlcjExNjU2OQ==", + "avatar_url": "https://avatars.githubusercontent.com/u/116569?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gounthar", + "html_url": "https://github.com/gounthar", + "followers_url": "https://api.github.com/users/gounthar/followers", + "following_url": "https://api.github.com/users/gounthar/following{/other_user}", + "gists_url": "https://api.github.com/users/gounthar/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gounthar/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gounthar/subscriptions", + "organizations_url": "https://api.github.com/users/gounthar/orgs", + "repos_url": "https://api.github.com/users/gounthar/repos", + "events_url": "https://api.github.com/users/gounthar/events{/privacy}", + "received_events_url": "https://api.github.com/users/gounthar/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1662551322, + "node_id": "MDU6TGFiZWwxNjYyNTUxMzIy", + "url": "https://api.github.com/repos/hub4j/github-api/labels/enhancement", + "name": "enhancement", + "color": "0e8a16", + "default": true, + "description": "" + }, + { + "id": 1991401619, + "node_id": "MDU6TGFiZWwxOTkxNDAxNjE5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/good%20first%20issue", + "name": "good first issue", + "color": "00FF00", + "default": true, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 8, + "created_at": "2024-12-03T20:58:22Z", + "updated_at": "2025-01-21T06:30:47Z", + "closed_at": "2025-01-21T06:30:46Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "### What feature do you want to see added?\r\n\r\n## Current Situation\r\nWhen forking a repository on GitHub, our current process retrieves all branches from the original repository.\r\n\r\n## Issue\r\nThis approach may be inefficient and unnecessary for [our use case](https://github.com/jenkins-infra/plugin-modernizer-tool/issues/104).\r\n\r\n## Desired Outcome\r\nWe aim to fork only the default branch of the repository, as this is typically sufficient for our work.\r\n\r\n## GitHub GUI Option\r\nThe GitHub web interface provides an option to fork only the default branch (usually the main branch).\r\n\r\n\r\n## Benefits\r\n1. **Efficiency**: Reduces unnecessary data transfer and storage.\r\n2. **Simplicity**: Maintains a cleaner repository structure in our forks.\r\n3. **Focus**: Aligns with our primary need of working with the main branch.\r\n\r\n## Conclusion\r\nOptimizing our forking process to retrieve only the main branch will streamline our workflow and improve overall efficiency. This change aligns with best practices for managing forks when only the main branch is needed for development or analysis purposes.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1993/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/hub4j/github-api/issues/1993/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1985", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1985/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1985/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1985/events", + "html_url": "https://github.com/hub4j/github-api/issues/1985", + "id": 2654357698, + "node_id": "I_kwDOAAlq-s6eNkjC", + "number": 1985, + "title": "/notifications interface return \"Unable to parse If-Modified-Since request header\"", + "user": { + "login": "AsherSu", + "id": 59462016, + "node_id": "MDQ6VXNlcjU5NDYyMDE2", + "avatar_url": "https://avatars.githubusercontent.com/u/59462016?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AsherSu", + "html_url": "https://github.com/AsherSu", + "followers_url": "https://api.github.com/users/AsherSu/followers", + "following_url": "https://api.github.com/users/AsherSu/following{/other_user}", + "gists_url": "https://api.github.com/users/AsherSu/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AsherSu/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AsherSu/subscriptions", + "organizations_url": "https://api.github.com/users/AsherSu/orgs", + "repos_url": "https://api.github.com/users/AsherSu/repos", + "events_url": "https://api.github.com/users/AsherSu/events{/privacy}", + "received_events_url": "https://api.github.com/users/AsherSu/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2024-11-13T06:27:11Z", + "updated_at": "2024-11-21T03:58:01Z", + "closed_at": "2024-11-21T03:58:01Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "**Describe the bug**\r\n/notifications interface return \"Unable to parse If-Modified-Since request header\"\r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n```\r\ngithub.listNotifications()\r\n .nonBlocking(true)\r\n .participating(false)\r\n .read(true) \r\n .iterator()\r\n .next()\r\n .getRepository()\r\n .getOwnerName()\r\n```\r\n\r\n**Expected behavior**\r\nreturn owner\r\n\r\n**Desktop (please complete the following information):**\r\n - OS: [e.g. iOS]\r\n - Browser [e.g. chrome, safari]\r\n - Version [e.g. 22]\r\n\r\n**Additional context**\r\n```\r\nCaused by: org.kohsuke.github.HttpException: {\"message\":\"Unable to parse If-Modified-Since request header. Please make sure value is in an acceptable format.\",\"documentation_url\":\"https://docs.github.com/rest/activity/notifications#list-notifications-for-the-authenticated-user\",\"status\":\"422\"}\r\n\tat org.kohsuke.github.GitHubConnectorResponseErrorHandler$1.onError(GitHubConnectorResponseErrorHandler.java:83)\r\n\tat org.kohsuke.github.GitHubClient.detectKnownErrors(GitHubClient.java:504)\r\n\tat org.kohsuke.github.GitHubClient.sendRequest(GitHubClient.java:464)\r\n\tat org.kohsuke.github.GitHubPageIterator.fetch(GitHubPageIterator.java:146)\r\n\tat org.kohsuke.github.GitHubPageIterator.hasNext(GitHubPageIterator.java:93)\r\n\tat org.kohsuke.github.PagedIterator.fetch(PagedIterator.java:116)\r\n\tat org.kohsuke.github.PagedIterator.nextPageArray(PagedIterator.java:144)\r\n\tat org.kohsuke.github.PagedIterable.toArray(PagedIterable.java:85)\r\n\tat org.kohsuke.github.GitHubPageContentsIterable.toResponse(GitHubPageContentsIterable.java:70)\r\n\tat org.kohsuke.github.GHNotificationStream$1.fetch(GHNotificationStream.java:194)\r\n\t... 5 more\r\n```", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1985/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/hub4j/github-api/issues/1985/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1970", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1970/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1970/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1970/events", + "html_url": "https://github.com/hub4j/github-api/issues/1970", + "id": 2567834054, + "node_id": "I_kwDOAAlq-s6ZDgnG", + "number": 1970, + "title": "Cannot fork repository to personal account using GitHub app", + "user": { + "login": "jonesbusy", + "id": 825750, + "node_id": "MDQ6VXNlcjgyNTc1MA==", + "avatar_url": "https://avatars.githubusercontent.com/u/825750?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/jonesbusy", + "html_url": "https://github.com/jonesbusy", + "followers_url": "https://api.github.com/users/jonesbusy/followers", + "following_url": "https://api.github.com/users/jonesbusy/following{/other_user}", + "gists_url": "https://api.github.com/users/jonesbusy/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jonesbusy/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jonesbusy/subscriptions", + "organizations_url": "https://api.github.com/users/jonesbusy/orgs", + "repos_url": "https://api.github.com/users/jonesbusy/repos", + "events_url": "https://api.github.com/users/jonesbusy/events{/privacy}", + "received_events_url": "https://api.github.com/users/jonesbusy/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2024-10-05T10:31:49Z", + "updated_at": "2024-10-06T04:36:20Z", + "closed_at": "2024-10-06T04:36:20Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "**Describe the bug**\r\n\r\nI'm using GitHub app to authenticate and I need to fork repository to my personal account. The app has the required access.\r\n\r\nBut due to call on `getMyself()` at https://github.com/hub4j/github-api/blob/768c7154bdb84e775dfafea6b0cb27fa57d835c7/src/main/java/org/kohsuke/github/GHRepository.java#L1467 it's not possible to use public GHRepository fork() throws IOException`\r\n\r\nI would suggest to create a new API GHRepository forkTo(GHUser user) allowing to fork a repository to a given user using GitHub app authentication.\r\n\r\nWould you agree ?\r\n\r\n**To Reproduce**\r\n\r\n- Create GitHub app. Grant permisssion to create/fork repositorx\r\n- Try to use fork()\r\n\r\nSeen on https://github.com/jenkinsci/plugin-modernizer-tool/pull/295\r\n\r\n**Expected behavior**\r\n\r\nI think it's the normal behavior. When calling the fork() the fork is done but fail on the getMyself call\r\n\r\nThat's why I suggest to create a new public method `forkTo(GHUser user)` similar to `forkTo(GHOrganisation org)`\r\n\r\n**Desktop (please complete the following information):**\r\n\r\nAll\r\n\r\n**Additional context**\r\n\r\nI can submit a proposal\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1970/reactions", + "total_count": 1, + "+1": 1, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/hub4j/github-api/issues/1970/timeline", + "performed_via_github_app": null, + "state_reason": "not_planned", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1963", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1963/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1963/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1963/events", + "html_url": "https://github.com/hub4j/github-api/issues/1963", + "id": 2562978157, + "node_id": "I_kwDOAAlq-s6Yw_Ft", + "number": 1963, + "title": "org.kohsuke.github.HttpException for getOrganization", + "user": { + "login": "bisegni", + "id": 3001087, + "node_id": "MDQ6VXNlcjMwMDEwODc=", + "avatar_url": "https://avatars.githubusercontent.com/u/3001087?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bisegni", + "html_url": "https://github.com/bisegni", + "followers_url": "https://api.github.com/users/bisegni/followers", + "following_url": "https://api.github.com/users/bisegni/following{/other_user}", + "gists_url": "https://api.github.com/users/bisegni/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bisegni/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bisegni/subscriptions", + "organizations_url": "https://api.github.com/users/bisegni/orgs", + "repos_url": "https://api.github.com/users/bisegni/repos", + "events_url": "https://api.github.com/users/bisegni/events{/privacy}", + "received_events_url": "https://api.github.com/users/bisegni/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2024-10-03T02:26:33Z", + "updated_at": "2024-10-03T02:39:37Z", + "closed_at": "2024-10-03T02:39:37Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "**Describe the bug**\r\nI have a code that worked to get organization information using github application installed into this application. Not it s not working anymore getting the error below:\r\nCaused by: org.kohsuke.github.HttpException: {\"message\":\"Bad credentials\",\"documentation_url\":\"https://docs.github.com/rest\",\"status\":\"401\"}\r\n\tat org.kohsuke.github.GitHubConnectorResponseErrorHandler$1.onError(GitHubConnectorResponseErrorHandler.java:83)\r\n\tat org.kohsuke.github.GitHubClient.detectKnownErrors(GitHubClient.java:504)\r\n\tat org.kohsuke.github.GitHubClient.sendRequest(GitHubClient.java:464)\r\n\tat org.kohsuke.github.GitHubClient.sendRequest(GitHubClient.java:427)\r\n\tat org.kohsuke.github.Requester.fetch(Requester.java:85)\r\n\tat org.kohsuke.github.GitHub.getOrganization(GitHub.java:640)\r\n\t\r\nusing github app key, id and installation id i can authenticate and get app installation information but when i try to get the organization i got error, below the installation with the information loaded:\r\n```\r\nappInstallation = {GHAppInstallation@18308} \"GHAppInstallation@eb1306c[accessTokenUrl=https://api.github.com/app/installations/55541328/access_tokens,appId=1014645,events=[],htmlUrl=https://github.com/organizations/ad-build-test/settings/installations/55541328,permissions={members=WRITE, contents=WRITE, metadata=READ, pull_requests=WRITE, repository_hooks=WRITE, team_discussions=WRITE, organization_plan=READ, organization_hooks=WRITE, organization_events=READ, organization_secrets=WRITE, organization_projects=ADMIN, organization_codespaces=WRITE, organization_custom_roles=WRITE, organization_user_blocking=WRITE, organization_administration=WRITE, organization_custom_org_roles=WRITE, organization_actions_variables=WRITE, organization_custom_properties=ADMIN, organization_codespaces_secrets=WRITE, organization_dependabot_secrets=WRITE, organization_codespaces_settings=WRITE, organization_self_hosted_runners=WRITE, organization_announcement_banners=WRITE, organization_personal_access_tokens=WRITE, organization_copilot_seat_managemen\"\r\n account = {GHUser@18330} \"GHUser@5ee60e32[suspendedAt=,bio=,blog=,company=,email=,followers=0,following=0,hireable=false,location=,login=ad-build-test,name=,type=Organization,createdAt=,id=168671263,nodeId=O_kgDOCg24Hw,updatedAt=,url=https://api.github.com/users/ad-build-test]\"\r\n accessTokenUrl = \"https://api.github.com/app/installations/55541328/access_tokens\"\r\n repositoriesUrl = \"https://api.github.com/installation/repositories\"\r\n appId = 1014645\r\n targetId = 168671263\r\n targetType = {GHTargetType@18333} \"ORGANIZATION\"\r\n permissions = {LinkedHashMap@18334} size = 26\r\n events = {ArrayList@18335} size = 0\r\n singleFileName = null\r\n repositorySelection = {GHRepositorySelection@18336} \"ALL\"\r\n htmlUrl = \"https://github.com/organizations/ad-build-test/settings/installations/55541328\"\r\n suspendedAt = null\r\n suspendedBy = null\r\n responseHeaderFields = {Collections$UnmodifiableMap@18338} size = 19\r\n url = null\r\n id = 55541328\r\n nodeId = null\r\n createdAt = \"2024-10-03T00:29:22.000Z\"\r\n updatedAt = \"2024-10-03T02:20:09.000Z\"\r\n root = {GitHub@18341} \r\n```\r\n\r\ni have gave all the authorization to ad-build-test organization but i receive always the same error, to note that the same code worked month ago. Any sugestion?\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1963/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/hub4j/github-api/issues/1963/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1957", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1957/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1957/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1957/events", + "html_url": "https://github.com/hub4j/github-api/issues/1957", + "id": 2553307162, + "node_id": "I_kwDOAAlq-s6YMGAa", + "number": 1957, + "title": "GHRepository.getPullRequests(GHIssueState) not deprecated but removed from 2.x", + "user": { + "login": "bitwiseman", + "id": 1958953, + "node_id": "MDQ6VXNlcjE5NTg5NTM=", + "avatar_url": "https://avatars.githubusercontent.com/u/1958953?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bitwiseman", + "html_url": "https://github.com/bitwiseman", + "followers_url": "https://api.github.com/users/bitwiseman/followers", + "following_url": "https://api.github.com/users/bitwiseman/following{/other_user}", + "gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions", + "organizations_url": "https://api.github.com/users/bitwiseman/orgs", + "repos_url": "https://api.github.com/users/bitwiseman/repos", + "events_url": "https://api.github.com/users/bitwiseman/events{/privacy}", + "received_events_url": "https://api.github.com/users/bitwiseman/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1664647346, + "node_id": "MDU6TGFiZWwxNjY0NjQ3MzQ2", + "url": "https://api.github.com/repos/hub4j/github-api/labels/task", + "name": "task", + "color": "bfdadc", + "default": false, + "description": "" + }, + { + "id": 1780165359, + "node_id": "MDU6TGFiZWwxNzgwMTY1MzU5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/breaking%20change", + "name": "breaking change", + "color": "b60205", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2024-09-27T16:21:56Z", + "updated_at": "2025-03-18T21:07:41Z", + "closed_at": "2025-03-18T21:07:41Z", + "author_association": "MEMBER", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "https://github.com/hub4j/github-api/pull/1935/files#r1778850947 - Anchor\r\n\r\n\r\n@ihrigb \r\nNoted that `GHRepository.getPullRequests(GHIssueState)` was not marked as Deprecated but was removed in `2.0-alpha-1`. \r\n\r\nDid a search on usages:\r\nhttps://github.com/search?q=org%3Ajenkinsci+getPullRequests+path%3A%2F%5Esrc%5C%2Fmain%5C%2Fjava%5C%2F%2F+org.kohsuke.github&type=code\r\n\r\nIt looks like there's at least one usage in the wild, and since it is in Jenkins it will cause breaks for some time to come. \r\nhttps://github.com/jenkinsci/ghprb-plugin/blob/master/src/main/java/org/jenkinsci/plugins/ghprb/GhprbRepository.java#L145\r\n\r\nThere's a similar one for listPullRequests(). \r\n\r\nOptions:\r\n* Bring some methods back as bridge methods in 2.0 to not break Jenkins plugins. \r\n* Change to 1.x that converts methods removed in 2.x to bridge methods - this would force projects to change their code in their next release while maintaining binary compatibility. Maybe only convert some methods. This might be a way to push some additional changes into 2.x.\r\n\r\nOn the other hand this is a 2.0 release, some incompatibility is to be expected. \r\n\r\n\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1957/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/hub4j/github-api/issues/1957/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1951", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1951/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1951/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1951/events", + "html_url": "https://github.com/hub4j/github-api/issues/1951", + "id": 2545520390, + "node_id": "I_kwDOAAlq-s6XuY8G", + "number": 1951, + "title": "Sharing of this Github API ", + "user": { + "login": "aeonSolutions", + "id": 7936768, + "node_id": "MDQ6VXNlcjc5MzY3Njg=", + "avatar_url": "https://avatars.githubusercontent.com/u/7936768?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/aeonSolutions", + "html_url": "https://github.com/aeonSolutions", + "followers_url": "https://api.github.com/users/aeonSolutions/followers", + "following_url": "https://api.github.com/users/aeonSolutions/following{/other_user}", + "gists_url": "https://api.github.com/users/aeonSolutions/gists{/gist_id}", + "starred_url": "https://api.github.com/users/aeonSolutions/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/aeonSolutions/subscriptions", + "organizations_url": "https://api.github.com/users/aeonSolutions/orgs", + "repos_url": "https://api.github.com/users/aeonSolutions/repos", + "events_url": "https://api.github.com/users/aeonSolutions/events{/privacy}", + "received_events_url": "https://api.github.com/users/aeonSolutions/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2024-09-24T14:07:14Z", + "updated_at": "2025-01-02T23:27:51Z", + "closed_at": "2025-01-02T23:27:51Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "Hi there @bernd @vbehar @kozmic @jkrall @derfred \r\ngreat work! 😍\r\n\r\nI'm sharing your project on my own C++ project for Github API\r\nhttps://github.com/aeonSolutions/AeonLabs-GitHub-API-C-library\r\n\r\n👍", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1951/reactions", + "total_count": 2, + "+1": 2, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/hub4j/github-api/issues/1951/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1926", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1926/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1926/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1926/events", + "html_url": "https://github.com/hub4j/github-api/issues/1926", + "id": 2516758945, + "node_id": "I_kwDOAAlq-s6WArGh", + "number": 1926, + "title": "Getting timeout while connecting to Github API", + "user": { + "login": "Mohazinkhan", + "id": 97169593, + "node_id": "U_kgDOBcqwuQ", + "avatar_url": "https://avatars.githubusercontent.com/u/97169593?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Mohazinkhan", + "html_url": "https://github.com/Mohazinkhan", + "followers_url": "https://api.github.com/users/Mohazinkhan/followers", + "following_url": "https://api.github.com/users/Mohazinkhan/following{/other_user}", + "gists_url": "https://api.github.com/users/Mohazinkhan/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Mohazinkhan/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Mohazinkhan/subscriptions", + "organizations_url": "https://api.github.com/users/Mohazinkhan/orgs", + "repos_url": "https://api.github.com/users/Mohazinkhan/repos", + "events_url": "https://api.github.com/users/Mohazinkhan/events{/privacy}", + "received_events_url": "https://api.github.com/users/Mohazinkhan/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1686290078, + "node_id": "MDU6TGFiZWwxNjg2MjkwMDc4", + "url": "https://api.github.com/repos/hub4j/github-api/labels/external", + "name": "external", + "color": "a0a0a0", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 14, + "created_at": "2024-09-10T15:16:24Z", + "updated_at": "2025-03-23T07:23:44Z", + "closed_at": "2025-03-23T07:23:42Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "I have configured a Github App for Jenkins and I am running a Global seed job which would connect to the Github API and retrieve all the repositories in the Organization. Whenever it tries to authenticate to the Github API and retrieve the list of repositories it fails with timeout and the following error is displayed,\r\n```\r\nCaused: org.kohsuke.github.HttpException: Server returned HTTP response code: -1, message: 'null' for URL: [https://api.github.com/orgs/{orgname}]\r\n\r\nStacktrace: \r\n\r\nhudson.remoting.ProxyException: java.net.SocketTimeoutException: Connect timed out\r\n\tat java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:551)\r\n\tat java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:602)\r\n\tat java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327)\r\n\tat java.base/java.net.Socket.connect(Socket.java:633)\r\n\tat java.base/sun.security.ssl.SSLSocketImpl.connect(SSLSocketImpl.java:304)\r\n\tat java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:178)\r\n\tat java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:533)\r\n\tat java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:638)\r\n\tat java.base/sun.net.www.protocol.https.HttpsClient.(HttpsClient.java:266)\r\n\tat java.base/sun.net.www.protocol.https.HttpsClient.New(HttpsClient.java:380)\r\n\tat java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.getNewHttpClient(AbstractDelegateHttpsURLConnection.java:193)\r\n\tat java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1241)\r\n\tat java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1127)\r\n\tat java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:179)\r\n\tat java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1686)\r\n\tat java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1610)\r\n\tat java.base/java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:529)\r\n\tat java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:308)\r\n\tat org.kohsuke.github.GitHubHttpUrlConnectionClient.getResponseInfo(GitHubHttpUrlConnectionClient.java:69)\r\n\tat org.kohsuke.github.GitHubClient.sendRequest(GitHubClient.java:400)\r\nAlso: hudson.remoting.ProxyException: org.jenkinsci.plugins.workflow.actions.ErrorAction$ErrorId: fa952780-e9a2-4351-b74d-d3851ac026e3\r\nCaused: hudson.remoting.ProxyException: org.kohsuke.github.HttpException: Server returned HTTP response code: -1, message: 'null' for URL: https://api.github.com/orgs/\r\n\tat org.kohsuke.github.GitHubClient.interpretApiError(GitHubClient.java:500)\r\n\tat org.kohsuke.github.GitHubClient.sendRequest(GitHubClient.java:420)\r\n\tat org.kohsuke.github.GitHubClient.sendRequest(GitHubClient.java:363)\r\n\tat org.kohsuke.github.Requester.fetch(Requester.java:74)\r\n\tat org.kohsuke.github.GitHub.getOrganization(GitHub.java:505)\r\n\tat org.kohsuke.github.GitHub$getOrganization.call(Unknown Source)\r\n\tat org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)\r\n\tat org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)\r\n\tat org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:128)\r\n\tat com..jenkins.jobdsl.GithubFetcher.(GithubFetcher.groovy:19)\r\n\tat java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)\r\n\tat java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)\r\n\tat java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500)\r\n\tat java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:481)\r\n\tat org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:83)\r\n\tat org.codehaus.groovy.reflection.CachedConstructor.doConstructorInvoke(CachedConstructor.java:77)\r\n\tat org.codehaus.groovy.runtime.callsite.ConstructorSite.callConstructor(ConstructorSite.java:45)\r\n\tat org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:59)\r\n\tat org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:238)\r\n\tat org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:258)\r\n\tat uc_generator.generateUcRepos(uc_generator.groovy:38)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\r\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:569)\r\n\tat org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSiteNoUnwrapNoCoerce.invoke(PogoMetaMethodSite.java:210)\r\n\tat org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:59)\r\n\tat org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:51)\r\n\tat org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:157)\r\n\tat org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:161)\r\n\tat uc_generator.run(uc_generator.groovy:8)\r\n\tat uc_generator$run.call(Unknown Source)\r\n\tat org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)\r\n\tat uc_generator$run.call(Unknown Source)\r\n\tat PluginClassLoader for job-dsl//javaposse.jobdsl.dsl.AbstractDslScriptLoader.runScript(AbstractDslScriptLoader.groovy:138)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\r\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:569)\r\n\tat org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSiteNoUnwrapNoCoerce.invoke(PogoMetaMethodSite.java:210)\r\n\tat org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:59)\r\n\tat org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:51)\r\n\tat org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:64)\r\n\tat org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:169)\r\n\tat PluginClassLoader for job-dsl//javaposse.jobdsl.dsl.AbstractDslScriptLoader.runScriptEngine(AbstractDslScriptLoader.groovy:108)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\r\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:569)\r\n\tat org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:98)\r\n\tat groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)\r\n\tat org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:352)\r\n\tat groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1034)\r\n\tat org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:68)\r\n\tat org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:177)\r\n\tat PluginClassLoader for job-dsl//javaposse.jobdsl.dsl.AbstractDslScriptLoader$_runScripts_closure1.doCall(AbstractDslScriptLoader.groovy:61)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\r\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:569)\r\n\tat org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:98)\r\n\tat groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)\r\n\tat org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:264)\r\n\tat groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1034)\r\n\tat groovy.lang.Closure.call(Closure.java:420)\r\n\tat groovy.lang.Closure.call(Closure.java:436)\r\n\tat org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2125)\r\n\tat org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2110)\r\n\tat org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2163)\r\n\tat org.codehaus.groovy.runtime.dgm$165.invoke(Unknown Source)\r\n\tat org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:274)\r\n\tat org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:56)\r\n\tat org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:128)\r\n\tat PluginClassLoader for job-dsl//javaposse.jobdsl.dsl.AbstractDslScriptLoader.runScripts(AbstractDslScriptLoader.groovy:46)\r\n\tat PluginClassLoader for job-dsl//javaposse.jobdsl.plugin.ExecuteDslScripts.perform(ExecuteDslScripts.java:363)\r\n\tat jenkins.tasks.SimpleBuildStep.perform(SimpleBuildStep.java:123)\r\n\tat PluginClassLoader for workflow-basic-steps//org.jenkinsci.plugins.workflow.steps.CoreStep$Execution.run(CoreStep.java:101)\r\n\tat PluginClassLoader for workflow-basic-steps//org.jenkinsci.plugins.workflow.steps.CoreStep$Execution.run(CoreStep.java:71)\r\n\tat PluginClassLoader for workflow-step-api//org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution.lambda$start$0(SynchronousNonBlockingStepExecution.java:47)\r\n\tat java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)\r\n\tat java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)\r\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)\r\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)\r\n\tat java.base/java.lang.Thread.run(Thread.java:840)\r\n```", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1926/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/hub4j/github-api/issues/1926/timeline", + "performed_via_github_app": null, + "state_reason": "not_planned", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1924", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1924/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1924/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1924/events", + "html_url": "https://github.com/hub4j/github-api/issues/1924", + "id": 2514785107, + "node_id": "I_kwDOAAlq-s6V5JNT", + "number": 1924, + "title": "[Vulnerable dependency upgrade] commons-io", + "user": { + "login": "dev-2-controltowerai", + "id": 167620350, + "node_id": "U_kgDOCf2u_g", + "avatar_url": "https://avatars.githubusercontent.com/u/167620350?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dev-2-controltowerai", + "html_url": "https://github.com/dev-2-controltowerai", + "followers_url": "https://api.github.com/users/dev-2-controltowerai/followers", + "following_url": "https://api.github.com/users/dev-2-controltowerai/following{/other_user}", + "gists_url": "https://api.github.com/users/dev-2-controltowerai/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dev-2-controltowerai/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dev-2-controltowerai/subscriptions", + "organizations_url": "https://api.github.com/users/dev-2-controltowerai/orgs", + "repos_url": "https://api.github.com/users/dev-2-controltowerai/repos", + "events_url": "https://api.github.com/users/dev-2-controltowerai/events{/privacy}", + "received_events_url": "https://api.github.com/users/dev-2-controltowerai/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2024-09-09T19:51:06Z", + "updated_at": "2024-09-10T16:02:13Z", + "closed_at": "2024-09-10T16:02:13Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "Apache commons io package is outdated with a bunch of vulnerabilities. Can someone update it?", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1924/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/hub4j/github-api/issues/1924/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1915", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1915/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1915/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1915/events", + "html_url": "https://github.com/hub4j/github-api/issues/1915", + "id": 2492552073, + "node_id": "I_kwDOAAlq-s6UkVOJ", + "number": 1915, + "title": "Support /repos/{owner}/{repo}/vulnerability-alerts", + "user": { + "login": "ranma2913", + "id": 4295880, + "node_id": "MDQ6VXNlcjQyOTU4ODA=", + "avatar_url": "https://avatars.githubusercontent.com/u/4295880?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/ranma2913", + "html_url": "https://github.com/ranma2913", + "followers_url": "https://api.github.com/users/ranma2913/followers", + "following_url": "https://api.github.com/users/ranma2913/following{/other_user}", + "gists_url": "https://api.github.com/users/ranma2913/gists{/gist_id}", + "starred_url": "https://api.github.com/users/ranma2913/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/ranma2913/subscriptions", + "organizations_url": "https://api.github.com/users/ranma2913/orgs", + "repos_url": "https://api.github.com/users/ranma2913/repos", + "events_url": "https://api.github.com/users/ranma2913/events{/privacy}", + "received_events_url": "https://api.github.com/users/ranma2913/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2024-08-28T16:35:38Z", + "updated_at": "2024-09-03T20:31:59Z", + "closed_at": "2024-09-03T20:31:59Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "Support Endpoints:\r\n\r\n- https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#check-if-vulnerability-alerts-are-enabled-for-a-repository\r\n- https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#enable-vulnerability-alerts\r\n- https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#disable-vulnerability-alerts", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1915/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/hub4j/github-api/issues/1915/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1909", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1909/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1909/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1909/events", + "html_url": "https://github.com/hub4j/github-api/issues/1909", + "id": 2472357939, + "node_id": "I_kwDOAAlq-s6TXTAz", + "number": 1909, + "title": "AbuseLimitHandler does not handle scenarios where the local time is not synchronized with GitHub server time", + "user": { + "login": "bitwiseman", + "id": 1958953, + "node_id": "MDQ6VXNlcjE5NTg5NTM=", + "avatar_url": "https://avatars.githubusercontent.com/u/1958953?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bitwiseman", + "html_url": "https://github.com/bitwiseman", + "followers_url": "https://api.github.com/users/bitwiseman/followers", + "following_url": "https://api.github.com/users/bitwiseman/following{/other_user}", + "gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions", + "organizations_url": "https://api.github.com/users/bitwiseman/orgs", + "repos_url": "https://api.github.com/users/bitwiseman/repos", + "events_url": "https://api.github.com/users/bitwiseman/events{/privacy}", + "received_events_url": "https://api.github.com/users/bitwiseman/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 265902919, + "node_id": "MDU6TGFiZWwyNjU5MDI5MTk=", + "url": "https://api.github.com/repos/hub4j/github-api/labels/bug", + "name": "bug", + "color": "e11d21", + "default": true, + "description": null + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 2, + "created_at": "2024-08-19T03:08:21Z", + "updated_at": "2024-10-14T17:19:05Z", + "closed_at": "2024-10-14T17:19:04Z", + "author_association": "MEMBER", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "From [this comment](https://github.com/hub4j/github-api/pull/1895/files#r1704397808) on #1895 : \r\n\r\n> GitHubClient has a method to get Date (or Instant).\r\n> https://github.com/hub4j/github-api/blob/main/src/main/java/org/kohsuke/github/GitHubClient.java#L916\r\n> \r\n> Unfortunately, diff from local machine time is not reliable. The local machine may not be synchronized with the server time. We have to use the diff from the response time.\r\n> https://github.com/hub4j/github-api/blob/main/src/main/java/org/kohsuke/github/GHRateLimit.java#L543-L571\r\n\r\n> However, this PR is a huge step forward and I'd rather have this less than perfect code added than wait for the next release.\r\n\r\nThis is the code that need updating:\r\n\r\nhttps://github.com/hub4j/github-api/blob/314917eabf9762e0d62d52f3ae68bc9ff6ba7ed5/src/main/java/org/kohsuke/github/AbuseLimitHandler.java#L116-L122", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1909/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/hub4j/github-api/issues/1909/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1908", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1908/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1908/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1908/events", + "html_url": "https://github.com/hub4j/github-api/issues/1908", + "id": 2467567361, + "node_id": "I_kwDOAAlq-s6TFBcB", + "number": 1908, + "title": "Enable github-api to support GraalVM native images", + "user": { + "login": "klopfdreh", + "id": 980773, + "node_id": "MDQ6VXNlcjk4MDc3Mw==", + "avatar_url": "https://avatars.githubusercontent.com/u/980773?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/klopfdreh", + "html_url": "https://github.com/klopfdreh", + "followers_url": "https://api.github.com/users/klopfdreh/followers", + "following_url": "https://api.github.com/users/klopfdreh/following{/other_user}", + "gists_url": "https://api.github.com/users/klopfdreh/gists{/gist_id}", + "starred_url": "https://api.github.com/users/klopfdreh/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/klopfdreh/subscriptions", + "organizations_url": "https://api.github.com/users/klopfdreh/orgs", + "repos_url": "https://api.github.com/users/klopfdreh/repos", + "events_url": "https://api.github.com/users/klopfdreh/events{/privacy}", + "received_events_url": "https://api.github.com/users/klopfdreh/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 265902955, + "node_id": "MDU6TGFiZWwyNjU5MDI5NTU=", + "url": "https://api.github.com/repos/hub4j/github-api/labels/new%20feature", + "name": "new feature", + "color": "f4cc53", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 18, + "created_at": "2024-08-15T07:31:23Z", + "updated_at": "2024-09-05T16:24:05Z", + "closed_at": "2024-09-05T16:24:05Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "**Describe the bug**\r\nDue to some reflections you encounter errors during the runtime when `github-api` is used in a native image.\r\n\r\nExample\r\n```plain\r\nat java.base@22.0.1/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)\\nCaused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.kohsuke.github.GHRepository`: cannot deserialize from Object value (no delegate- or property-based Creator): this appears to be a native image, in which case you may need to configure reflection for the class that is to be deserialized\\n at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 2]\r\n```\r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n1. Build an application with Spring Boot Native and `github-api`\r\n2. Perform a `native-image` build\r\n3. Run the native application\r\n\r\n**Expected behavior**\r\n`github-api` should be used in a native image without any issues\r\n\r\n**Desktop (please complete the following information):**\r\n N/A\r\n\r\n**Additional context**\r\nYou could add `META-INF/native-image///reflect-config.json` and describe the reflection usage:\r\n\r\nExample:\r\n```json\r\n[\r\n {\r\n \"name\": \"org.kohsuke.github.GHRepository\",\r\n \r\n }\r\n]\r\n```\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1908/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/hub4j/github-api/issues/1908/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1905", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1905/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1905/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1905/events", + "html_url": "https://github.com/hub4j/github-api/issues/1905", + "id": 2449641453, + "node_id": "I_kwDOAAlq-s6SAo_t", + "number": 1905, + "title": "List Anonymous Repository Contributors", + "user": { + "login": "augustd", + "id": 1258191, + "node_id": "MDQ6VXNlcjEyNTgxOTE=", + "avatar_url": "https://avatars.githubusercontent.com/u/1258191?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/augustd", + "html_url": "https://github.com/augustd", + "followers_url": "https://api.github.com/users/augustd/followers", + "following_url": "https://api.github.com/users/augustd/following{/other_user}", + "gists_url": "https://api.github.com/users/augustd/gists{/gist_id}", + "starred_url": "https://api.github.com/users/augustd/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/augustd/subscriptions", + "organizations_url": "https://api.github.com/users/augustd/orgs", + "repos_url": "https://api.github.com/users/augustd/repos", + "events_url": "https://api.github.com/users/augustd/events{/privacy}", + "received_events_url": "https://api.github.com/users/augustd/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1662551322, + "node_id": "MDU6TGFiZWwxNjYyNTUxMzIy", + "url": "https://api.github.com/repos/hub4j/github-api/labels/enhancement", + "name": "enhancement", + "color": "0e8a16", + "default": true, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 2, + "created_at": "2024-08-05T23:31:16Z", + "updated_at": "2025-01-06T17:08:10Z", + "closed_at": "2025-01-06T17:08:10Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "The Github API docs (https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repository-contributors) say that there is am `anon=true` parameter to add to `/repos/{owner}/{repo}/contributors` in order to fetch anonymous contributions. \r\n\r\nThere does not seem to be an option in the API to add that parameter. Is there any way to fetch anonymous contributors? ", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1905/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/hub4j/github-api/issues/1905/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1852", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1852/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1852/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1852/events", + "html_url": "https://github.com/hub4j/github-api/issues/1852", + "id": 2344425004, + "node_id": "I_kwDOAAlq-s6LvRYs", + "number": 1852, + "title": "Can't Iterate over ghRepo.listCollaborators()", + "user": { + "login": "MouadhKh", + "id": 50799773, + "node_id": "MDQ6VXNlcjUwNzk5Nzcz", + "avatar_url": "https://avatars.githubusercontent.com/u/50799773?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/MouadhKh", + "html_url": "https://github.com/MouadhKh", + "followers_url": "https://api.github.com/users/MouadhKh/followers", + "following_url": "https://api.github.com/users/MouadhKh/following{/other_user}", + "gists_url": "https://api.github.com/users/MouadhKh/gists{/gist_id}", + "starred_url": "https://api.github.com/users/MouadhKh/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/MouadhKh/subscriptions", + "organizations_url": "https://api.github.com/users/MouadhKh/orgs", + "repos_url": "https://api.github.com/users/MouadhKh/repos", + "events_url": "https://api.github.com/users/MouadhKh/events{/privacy}", + "received_events_url": "https://api.github.com/users/MouadhKh/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2024-06-10T17:06:18Z", + "updated_at": "2024-06-11T19:17:11Z", + "closed_at": "2024-06-11T19:16:21Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "**Description**\r\nAccessing single collaborators by iterating over `ghRepo.listCollaborators()` is not possible for some repositories (all public)\r\nThe detailed message looks : \r\n`Server returned HTTP response code: -1, message: 'null' for URL: www.reposUrl.com`\r\n\r\n**To Reproduce**\r\nIt doesn't matter which token I use ( classic token with all permissions/Fine-grained token with all permissions). For some repositories, it is not possible to go over the collaborators.\r\nThe result of the following curl varies depending on the used token(classic/new)\r\n`curl -L \\\r\n -H \"Accept: application/vnd.github+json\" \\\r\n -H \"Authorization: Bearer TOKEN_PLACEHOLDER\" \\\r\n -H \"X-GitHub-Api-Version: 2022-11-28\" \\\r\n URL`\r\n\r\n**Classic PAT** --> Must have push access to view repository collaborators.\r\n**Fine grained token** --> Resource not accessible by personal access token\r\n\r\nThe first displayed error message allude to missing permissions, which I think is wrong since the repository is publicly accessible\r\nThe second error message is more confusing and contradicts the documentation(https://docs.github.com/rest/collaborators/collaborators#list-repository-collaborators)\r\n\r\n**Expected behavior**\r\nCan access collaborators of all public repositories \r\n\r\n**Additional context**\r\nThe given repository is a special case because it is archived(should be readable nevertheless). But this problem persists with other non-archived repositories aswell", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1852/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/hub4j/github-api/issues/1852/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + } + ] +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchIssuesOnly/__files/3-search_issues.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchIssuesOnly/__files/3-search_issues.json new file mode 100644 index 0000000000..74d8e9279d --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchIssuesOnly/__files/3-search_issues.json @@ -0,0 +1,2430 @@ +{ + "total_count": 553, + "incomplete_results": false, + "items": [ + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2150", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2150/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2150/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2150/events", + "html_url": "https://github.com/hub4j/github-api/issues/2150", + "id": 3495762524, + "node_id": "I_kwDOAAlq-s7QXRpc", + "number": 2150, + "title": "Add new DYNAMIC event to GHEvent enum", + "user": { + "login": "kkroner8451", + "id": 14809736, + "node_id": "MDQ6VXNlcjE0ODA5NzM2", + "avatar_url": "https://avatars.githubusercontent.com/u/14809736?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/kkroner8451", + "html_url": "https://github.com/kkroner8451", + "followers_url": "https://api.github.com/users/kkroner8451/followers", + "following_url": "https://api.github.com/users/kkroner8451/following{/other_user}", + "gists_url": "https://api.github.com/users/kkroner8451/gists{/gist_id}", + "starred_url": "https://api.github.com/users/kkroner8451/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/kkroner8451/subscriptions", + "organizations_url": "https://api.github.com/users/kkroner8451/orgs", + "repos_url": "https://api.github.com/users/kkroner8451/repos", + "events_url": "https://api.github.com/users/kkroner8451/events{/privacy}", + "received_events_url": "https://api.github.com/users/kkroner8451/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2025-10-08T14:45:59Z", + "updated_at": "2025-10-23T00:51:33Z", + "closed_at": "2025-10-23T00:51:33Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "GitHub now has a new event of \"dynamic\" on workflow runs. I can't find any published docs, but looking at the data it appears to be dynamic runs of workflows for things like Dependabot, etc. The results is when `GHWorkflowRun` maps `event` field in `getEvent()` method to the `GHEvent` enum it ends up being UNKNOWN and logs a warning in the `EnumUtils` class that the value is unknown. DYNAMIC should be added to the GHEvent enum to minimize log warnings and add clarity for known (even if not published) possible values.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2150/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/hub4j/github-api/issues/2150/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2144", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2144/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2144/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2144/events", + "html_url": "https://github.com/hub4j/github-api/issues/2144", + "id": 3450064053, + "node_id": "I_kwDOAAlq-s7No8y1", + "number": 2144, + "title": "Unbridged Artifact for 1.330", + "user": { + "login": "gilday", + "id": 1431609, + "node_id": "MDQ6VXNlcjE0MzE2MDk=", + "avatar_url": "https://avatars.githubusercontent.com/u/1431609?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gilday", + "html_url": "https://github.com/gilday", + "followers_url": "https://api.github.com/users/gilday/followers", + "following_url": "https://api.github.com/users/gilday/following{/other_user}", + "gists_url": "https://api.github.com/users/gilday/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gilday/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gilday/subscriptions", + "organizations_url": "https://api.github.com/users/gilday/orgs", + "repos_url": "https://api.github.com/users/gilday/repos", + "events_url": "https://api.github.com/users/gilday/events{/privacy}", + "received_events_url": "https://api.github.com/users/gilday/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-09-24T16:05:57Z", + "updated_at": "2025-10-23T17:46:14Z", + "closed_at": "2025-10-23T17:46:14Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "Could you please release the `github-api-unbridged` artifact for version 1.330? This would allow projects using Mockito in their test suites to upgrade to the Jackson-compatible version while maintaining test functionality.\n\n## Current Situation\n- ✅ github-api:1.330 (bridged) - Released and available\n- ❌ github-api-unbridged:1.330 - Not yet released\n- 🔧 Tests fail with the bridged version due to Mockito incompatibilities\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2144/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/hub4j/github-api/issues/2144/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2140", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2140/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2140/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2140/events", + "html_url": "https://github.com/hub4j/github-api/issues/2140", + "id": 3379331716, + "node_id": "I_kwDOAAlq-s7JbIKE", + "number": 2140, + "title": "NoClassDefFoundError when using Jackson 2.20", + "user": { + "login": "sfc-gh-pvillard", + "id": 189795559, + "node_id": "U_kgDOC1AM5w", + "avatar_url": "https://avatars.githubusercontent.com/u/189795559?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sfc-gh-pvillard", + "html_url": "https://github.com/sfc-gh-pvillard", + "followers_url": "https://api.github.com/users/sfc-gh-pvillard/followers", + "following_url": "https://api.github.com/users/sfc-gh-pvillard/following{/other_user}", + "gists_url": "https://api.github.com/users/sfc-gh-pvillard/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sfc-gh-pvillard/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sfc-gh-pvillard/subscriptions", + "organizations_url": "https://api.github.com/users/sfc-gh-pvillard/orgs", + "repos_url": "https://api.github.com/users/sfc-gh-pvillard/repos", + "events_url": "https://api.github.com/users/sfc-gh-pvillard/events{/privacy}", + "received_events_url": "https://api.github.com/users/sfc-gh-pvillard/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 3, + "created_at": "2025-09-03T10:45:15Z", + "updated_at": "2025-09-04T17:48:24Z", + "closed_at": "2025-09-03T13:08:25Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "````java\njava.lang.NoClassDefFoundError: Could not initialize class org.kohsuke.github.GitHubClient\n at org.kohsuke.github.GitHub.(GitHub.java:137)\n at org.kohsuke.github.GitHubBuilder.build(GitHubBuilder.java:509)\n at ...\nCaused by: java.lang.ExceptionInInitializerError: Exception java.lang.NoSuchFieldError: Class com.fasterxml.jackson.databind.PropertyNamingStrategy does not have member field 'com.fasterxml.jackson.databind.PropertyNamingStrategy SNAKE_CASE' [in thread \"ForkJoinPool-1-worker-2\"]\n at org.kohsuke.github.GitHubClient.(GitHubClient.java:92)\n ... 15 common frames omitted\n````\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2140/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/hub4j/github-api/issues/2140/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2137", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2137/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2137/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2137/events", + "html_url": "https://github.com/hub4j/github-api/issues/2137", + "id": 3373441552, + "node_id": "I_kwDOAAlq-s7JEqIQ", + "number": 2137, + "title": "Runtime errors when using jackson 2.20.0", + "user": { + "login": "ketan", + "id": 10598, + "node_id": "MDQ6VXNlcjEwNTk4", + "avatar_url": "https://avatars.githubusercontent.com/u/10598?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/ketan", + "html_url": "https://github.com/ketan", + "followers_url": "https://api.github.com/users/ketan/followers", + "following_url": "https://api.github.com/users/ketan/following{/other_user}", + "gists_url": "https://api.github.com/users/ketan/gists{/gist_id}", + "starred_url": "https://api.github.com/users/ketan/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/ketan/subscriptions", + "organizations_url": "https://api.github.com/users/ketan/orgs", + "repos_url": "https://api.github.com/users/ketan/repos", + "events_url": "https://api.github.com/users/ketan/events{/privacy}", + "received_events_url": "https://api.github.com/users/ketan/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2025-09-01T17:51:50Z", + "updated_at": "2025-09-02T19:22:33Z", + "closed_at": "2025-09-02T19:22:33Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "**Environment**\n\n* Jackson databind - `2.20.0`\n* org.kohsuke:github-api `1.329`\n\nWhen running with this combination, there's a runtime error when loading GitHubClient. In particular, `PropertyNamingStrategy.SNAKE_CASE` seems to have been removed in jackson databind `2.20.0` as part of https://github.com/FasterXML/jackson-databind/commit/4d2083160fef06e6063a3082f0fdaab8c2803793. https://github.com/FasterXML/jackson-databind/issues/4136 contains the discussion around it.\n\nThe suggestion is to use `PropertyNamingStrategies#SNAKE_CASE` instead.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2137/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/hub4j/github-api/issues/2137/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2128", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2128/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2128/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2128/events", + "html_url": "https://github.com/hub4j/github-api/issues/2128", + "id": 3364033362, + "node_id": "I_kwDOAAlq-s7IgxNS", + "number": 2128, + "title": "GHRepository#getIssues() takes 30s", + "user": { + "login": "Alathreon", + "id": 45936420, + "node_id": "MDQ6VXNlcjQ1OTM2NDIw", + "avatar_url": "https://avatars.githubusercontent.com/u/45936420?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Alathreon", + "html_url": "https://github.com/Alathreon", + "followers_url": "https://api.github.com/users/Alathreon/followers", + "following_url": "https://api.github.com/users/Alathreon/following{/other_user}", + "gists_url": "https://api.github.com/users/Alathreon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Alathreon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Alathreon/subscriptions", + "organizations_url": "https://api.github.com/users/Alathreon/orgs", + "repos_url": "https://api.github.com/users/Alathreon/repos", + "events_url": "https://api.github.com/users/Alathreon/events{/privacy}", + "received_events_url": "https://api.github.com/users/Alathreon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-08-28T16:54:27Z", + "updated_at": "2025-08-30T20:45:46Z", + "closed_at": "2025-08-30T20:45:46Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "**Describe the bug**\nA clear and concise description of what the bug is.\nI am using the method GHRepository#getIssues() to get all issues for auto complete purpose in a discord bot, but the problem is that there are more than 1000 issues and fetching them all takes 28s...\nIt could be partially patched by allowing the client to set a page size to large numbers, so it doesn't need to do a HTTP call 44 times.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n- Have a repository with 1000+ issues\n- Call getIssues() with no filter\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\nIt should take much less time.\n\n**Desktop (please complete the following information):**\n- Version 1.329\n- Tested in many Windows/Linux distributions\n\n**Additional context**\nAdd any other context about the problem here.\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2128/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/hub4j/github-api/issues/2128/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2112", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2112/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2112/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2112/events", + "html_url": "https://github.com/hub4j/github-api/issues/2112", + "id": 3218889361, + "node_id": "I_kwDOAAlq-s6_3FqR", + "number": 2112, + "title": "I am using `v2.0-rc.3` and when starting an application I get a load of warnings regarding annotation used in the github-api library.", + "user": { + "login": "HerrDerb", + "id": 7398256, + "node_id": "MDQ6VXNlcjczOTgyNTY=", + "avatar_url": "https://avatars.githubusercontent.com/u/7398256?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/HerrDerb", + "html_url": "https://github.com/HerrDerb", + "followers_url": "https://api.github.com/users/HerrDerb/followers", + "following_url": "https://api.github.com/users/HerrDerb/following{/other_user}", + "gists_url": "https://api.github.com/users/HerrDerb/gists{/gist_id}", + "starred_url": "https://api.github.com/users/HerrDerb/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/HerrDerb/subscriptions", + "organizations_url": "https://api.github.com/users/HerrDerb/orgs", + "repos_url": "https://api.github.com/users/HerrDerb/repos", + "events_url": "https://api.github.com/users/HerrDerb/events{/privacy}", + "received_events_url": "https://api.github.com/users/HerrDerb/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2025-07-10T11:04:06Z", + "updated_at": "2025-07-23T23:42:08Z", + "closed_at": "2025-07-23T23:42:08Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "I am using `v2.0-rc.3` and when starting an application I get a load of warnings regarding annotation used in the github-api library.\n\n```\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GitHub.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings': class file for edu.umd.cs.findbugs.annotations.SuppressFBWarnings not found\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GitHub.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GitHub.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GitHub.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHArtifact.class): warning: Cannot find annotation method 'value()' in type 'WithBridgeMethods': class file for com.infradna.tool.bridge_method_injector.WithBridgeMethods not found\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHArtifact.class): warning: Cannot find annotation method 'adapterMethod()' in type 'WithBridgeMethods'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHArtifact.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHArtifact.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'value()' in type 'WithBridgeMethods'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'adapterMethod()' in type 'WithBridgeMethods'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GitHub.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings': class file for edu.umd.cs.findbugs.annotations.SuppressFBWarnings not found\n.gradle\\caches\\modules-2\\files-2.1\\org.kohsuke\\github-api\\2.0-rc.3\\aa2079a423762ba0ce99457038d4d81a13be8c07\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GitHub.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\n...\n```\n\nTo avoid this one needs to add `spotbugs-annotations` and `bridge-method-annotation` to each project.\nAdditionally `bridge-method-annotation` is not even hosted on maven central and a third party repository also needs to be added. This is too much overhead just to avoid warnings. Therefor by adding the deps as `runtime` solves this issue for all users of the github library \n\n_Originally posted by @HerrDerb in https://github.com/hub4j/github-api/discussions/2090_", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2112/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/hub4j/github-api/issues/2112/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2111", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2111/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2111/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2111/events", + "html_url": "https://github.com/hub4j/github-api/issues/2111", + "id": 3218887702, + "node_id": "I_kwDOAAlq-s6_3FQW", + "number": 2111, + "title": "Inlcude optional dependencies to avoid warnings", + "user": { + "login": "HerrDerb", + "id": 7398256, + "node_id": "MDQ6VXNlcjczOTgyNTY=", + "avatar_url": "https://avatars.githubusercontent.com/u/7398256?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/HerrDerb", + "html_url": "https://github.com/HerrDerb", + "followers_url": "https://api.github.com/users/HerrDerb/followers", + "following_url": "https://api.github.com/users/HerrDerb/following{/other_user}", + "gists_url": "https://api.github.com/users/HerrDerb/gists{/gist_id}", + "starred_url": "https://api.github.com/users/HerrDerb/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/HerrDerb/subscriptions", + "organizations_url": "https://api.github.com/users/HerrDerb/orgs", + "repos_url": "https://api.github.com/users/HerrDerb/repos", + "events_url": "https://api.github.com/users/HerrDerb/events{/privacy}", + "received_events_url": "https://api.github.com/users/HerrDerb/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-07-10T11:03:38Z", + "updated_at": "2025-10-23T18:09:10Z", + "closed_at": "2025-10-23T18:09:10Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "I am using `v2.0-rc.3` and when starting an application I get a load of warnings regarding annotation used in the github-api library.\r\n\r\n```\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHArtifact.class): warning: Cannot find annotation method 'value()' in type 'WithBridgeMethods': class file for com.infradna.tool.bridge_method_injector.WithBridgeMethods not found\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHArtifact.class): warning: Cannot find annotation method 'adapterMethod()' in type 'WithBridgeMethods'\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHArtifact.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings': class file for edu.umd.cs.findbugs.annotations.SuppressFBWarnings not found\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHArtifact.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'value()' in type 'WithBridgeMethods'\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'adapterMethod()' in type 'WithBridgeMethods'\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings'\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings'\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'value()' in type 'SuppressFBWarnings'\r\n...\\github-api-2.0-rc.3.jar(/org/kohsuke/github/GHWorkflowRun.class): warning: Cannot find annotation method 'justification()' in type 'SuppressFBWarnings'\r\n...\r\n```\r\nTo avoid this warnings, one needs to add the required dependencies \r\n```\r\n- com.infradna.tool:bridge-method-annotation\r\n- com.github.spotbugs:spotbugs-annotations\r\n```\r\nto each project. Additionally the `bridge-method-annotation` dependency doesn't even exist on maven central and a thirdparty repo needs to be added to the project which is a massive overhead to only avoid warnings.\r\n\r\nBy adding the dependencies as `runtime` this solves the problem for all users of this library\r\n_Originally posted by @HerrDerb in https://github.com/hub4j/github-api/discussions/2090_", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2111/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/hub4j/github-api/issues/2111/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2073", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2073/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2073/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2073/events", + "html_url": "https://github.com/hub4j/github-api/issues/2073", + "id": 2950997416, + "node_id": "I_kwDOAAlq-s6v5KWo", + "number": 2073, + "title": "Replace methods which return `Date` with `Instant` or `ZonedDateTime` for 2.x", + "user": { + "login": "solonovamax", + "id": 46940694, + "node_id": "MDQ6VXNlcjQ2OTQwNjk0", + "avatar_url": "https://avatars.githubusercontent.com/u/46940694?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/solonovamax", + "html_url": "https://github.com/solonovamax", + "followers_url": "https://api.github.com/users/solonovamax/followers", + "following_url": "https://api.github.com/users/solonovamax/following{/other_user}", + "gists_url": "https://api.github.com/users/solonovamax/gists{/gist_id}", + "starred_url": "https://api.github.com/users/solonovamax/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/solonovamax/subscriptions", + "organizations_url": "https://api.github.com/users/solonovamax/orgs", + "repos_url": "https://api.github.com/users/solonovamax/repos", + "events_url": "https://api.github.com/users/solonovamax/events{/privacy}", + "received_events_url": "https://api.github.com/users/solonovamax/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1662551322, + "node_id": "MDU6TGFiZWwxNjYyNTUxMzIy", + "url": "https://api.github.com/repos/hub4j/github-api/labels/enhancement", + "name": "enhancement", + "color": "0e8a16", + "default": true, + "description": "" + }, + { + "id": 1780165359, + "node_id": "MDU6TGFiZWwxNzgwMTY1MzU5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/breaking%20change", + "name": "breaking change", + "color": "b60205", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-03-26T23:38:02Z", + "updated_at": "2025-04-11T07:02:09Z", + "closed_at": "2025-04-11T07:02:09Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "There are multiple methods which return `Date` such as `GHObject#getUpdatedAt()`.\nthe old java `Date` api should really be replaced with one of the following:\n- `Instant` (imo this would be the best pick)\n- `LocalDateTime`\n- `ZonedDateTime`\n\nsince a 2.x version is currently being made, now would be the best time to replace it.\n\nit is recommended to avoid the old date-time api and instead use the newer api. many of the methods on `Date` are even marked as deprecated.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2073/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/hub4j/github-api/issues/2073/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2061", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2061/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2061/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2061/events", + "html_url": "https://github.com/hub4j/github-api/issues/2061", + "id": 2923132207, + "node_id": "I_kwDOAAlq-s6uO3Uv", + "number": 2061, + "title": "`GHIssue#isPullRequest` is false despite being a PR", + "user": { + "login": "Haarolean", + "id": 1494347, + "node_id": "MDQ6VXNlcjE0OTQzNDc=", + "avatar_url": "https://avatars.githubusercontent.com/u/1494347?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Haarolean", + "html_url": "https://github.com/Haarolean", + "followers_url": "https://api.github.com/users/Haarolean/followers", + "following_url": "https://api.github.com/users/Haarolean/following{/other_user}", + "gists_url": "https://api.github.com/users/Haarolean/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Haarolean/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Haarolean/subscriptions", + "organizations_url": "https://api.github.com/users/Haarolean/orgs", + "repos_url": "https://api.github.com/users/Haarolean/repos", + "events_url": "https://api.github.com/users/Haarolean/events{/privacy}", + "received_events_url": "https://api.github.com/users/Haarolean/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 265902919, + "node_id": "MDU6TGFiZWwyNjU5MDI5MTk=", + "url": "https://api.github.com/repos/hub4j/github-api/labels/bug", + "name": "bug", + "color": "e11d21", + "default": true, + "description": null + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2025-03-16T15:23:51Z", + "updated_at": "2026-02-10T07:49:35Z", + "closed_at": "2026-02-10T07:49:35Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "**Describe the bug**\n`isPullRequest` in GHIssue returns false due to `pull_request == null` condition. The PR object was acquired via `payload.getPullRequest()` method of `GHEventPayload.PullRequest` class.\n\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Get an instance of `GHEventPayload.PullRequest` event\n2. Get the pr entity via `payload.getPullRequest()`\n3. Observe that despite object being an instance of `GHPullRequest` the return value of `isPullRequest` method is false.\n\n**Expected behavior**\nexpected true", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2061/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/hub4j/github-api/issues/2061/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2057", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2057/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2057/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2057/events", + "html_url": "https://github.com/hub4j/github-api/issues/2057", + "id": 2913719792, + "node_id": "I_kwDOAAlq-s6tq9Xw", + "number": 2057, + "title": "Comments seem to be stripped?", + "user": { + "login": "koppor", + "id": 1366654, + "node_id": "MDQ6VXNlcjEzNjY2NTQ=", + "avatar_url": "https://avatars.githubusercontent.com/u/1366654?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/koppor", + "html_url": "https://github.com/koppor", + "followers_url": "https://api.github.com/users/koppor/followers", + "following_url": "https://api.github.com/users/koppor/following{/other_user}", + "gists_url": "https://api.github.com/users/koppor/gists{/gist_id}", + "starred_url": "https://api.github.com/users/koppor/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/koppor/subscriptions", + "organizations_url": "https://api.github.com/users/koppor/orgs", + "repos_url": "https://api.github.com/users/koppor/repos", + "events_url": "https://api.github.com/users/koppor/events{/privacy}", + "received_events_url": "https://api.github.com/users/koppor/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-03-12T11:56:46Z", + "updated_at": "2025-03-12T13:08:56Z", + "closed_at": "2025-03-12T13:08:55Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "https://github.com/JabRef/jabref/pull/12710#pullrequestreview-2678113413\n\n![Image](https://github.com/user-attachments/assets/1e135941-65b8-410d-ae19-04f40d1786db)\n\nDebug of `comment.getBody();` does not include this text:\n\n![Image](https://github.com/user-attachments/assets/c1530e54-67ab-4520-b269-1a70c1065dd1)\n\norg.kohsuke.github.GHIssueComment#update looks good - is it a GitHub API issue.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2057/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/hub4j/github-api/issues/2057/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2040", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2040/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2040/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2040/events", + "html_url": "https://github.com/hub4j/github-api/issues/2040", + "id": 2869668624, + "node_id": "I_kwDOAAlq-s6rC6sQ", + "number": 2040, + "title": "Status of 2.x stream", + "user": { + "login": "nedtwigg", + "id": 2924992, + "node_id": "MDQ6VXNlcjI5MjQ5OTI=", + "avatar_url": "https://avatars.githubusercontent.com/u/2924992?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/nedtwigg", + "html_url": "https://github.com/nedtwigg", + "followers_url": "https://api.github.com/users/nedtwigg/followers", + "following_url": "https://api.github.com/users/nedtwigg/following{/other_user}", + "gists_url": "https://api.github.com/users/nedtwigg/gists{/gist_id}", + "starred_url": "https://api.github.com/users/nedtwigg/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/nedtwigg/subscriptions", + "organizations_url": "https://api.github.com/users/nedtwigg/orgs", + "repos_url": "https://api.github.com/users/nedtwigg/repos", + "events_url": "https://api.github.com/users/nedtwigg/events{/privacy}", + "received_events_url": "https://api.github.com/users/nedtwigg/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1664647346, + "node_id": "MDU6TGFiZWwxNjY0NjQ3MzQ2", + "url": "https://api.github.com/repos/hub4j/github-api/labels/task", + "name": "task", + "color": "bfdadc", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 6, + "created_at": "2025-02-21T17:51:09Z", + "updated_at": "2025-03-23T06:48:12Z", + "closed_at": "2025-03-23T06:48:12Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "Hello! Thanks very much for maintaining this great library! The changes coming in 2.x seem good, I'm eager to be an early adopter of it.\n\nDo you have rough ideas around\n\n- what might be broken in the 2.x train\n- what might still change in the 2.x train\n- how long until the 2.x train is out of beta\n\nNot looking for hard commitments or anything, just your general vibe.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2040/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/hub4j/github-api/issues/2040/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2039", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2039/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2039/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2039/events", + "html_url": "https://github.com/hub4j/github-api/issues/2039", + "id": 2869632221, + "node_id": "I_kwDOAAlq-s6rCxzd", + "number": 2039, + "title": "Allow a GHPullRequest to set auto-merge", + "user": { + "login": "roxspring", + "id": 783694, + "node_id": "MDQ6VXNlcjc4MzY5NA==", + "avatar_url": "https://avatars.githubusercontent.com/u/783694?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/roxspring", + "html_url": "https://github.com/roxspring", + "followers_url": "https://api.github.com/users/roxspring/followers", + "following_url": "https://api.github.com/users/roxspring/following{/other_user}", + "gists_url": "https://api.github.com/users/roxspring/gists{/gist_id}", + "starred_url": "https://api.github.com/users/roxspring/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/roxspring/subscriptions", + "organizations_url": "https://api.github.com/users/roxspring/orgs", + "repos_url": "https://api.github.com/users/roxspring/repos", + "events_url": "https://api.github.com/users/roxspring/events{/privacy}", + "received_events_url": "https://api.github.com/users/roxspring/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 265902955, + "node_id": "MDU6TGFiZWwyNjU5MDI5NTU=", + "url": "https://api.github.com/repos/hub4j/github-api/labels/new%20feature", + "name": "new feature", + "color": "f4cc53", + "default": false, + "description": "" + }, + { + "id": 1662551322, + "node_id": "MDU6TGFiZWwxNjYyNTUxMzIy", + "url": "https://api.github.com/repos/hub4j/github-api/labels/enhancement", + "name": "enhancement", + "color": "0e8a16", + "default": true, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 7, + "created_at": "2025-02-21T17:32:36Z", + "updated_at": "2025-03-19T17:24:00Z", + "closed_at": "2025-03-19T17:24:00Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "I've been using the API to generate PRs for our main repository in a similar vein to dependabot, and would really like to be able to set those to auto-merge (which has been allowed in our repository):\n```java\nghPullRequest.requestAutoMerge();\n```\n\nClearly this isn't supported by the Java API, presumably because it's not supported by the GitHub REST API either. However it is supported by via a couple (oh, the irony!) of GraphQL API queries:\n\n```graphql\nquery GetPullRequestID {\n repository(name: \"$repo\", owner: \"$owner\") {\n pullRequest(number: \"$prnum\") {\n id\n }\n }\n}\n```\n\n```graphql\nmutation EnableAutoMergeOnPullRequest {\n enablePullRequestAutoMerge(input: {pullRequestId: \"$pullRequestID\", mergeMethod: MERGE}) {\n clientMutationId\n }\n}\n```\n\nRather than boiling the ocean by requesting general purpose public GraphQL support (#521), I wonder whether it might be acceptable to implement some low level GraphQL support that could be used internally to implement specific features not available in the REST API, such as enabling auto-merge on a PR. Perhaps in time this could become the basis for some general support but the immediate goal would be to enable access to APIs.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2039/reactions", + "total_count": 1, + "+1": 1, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/hub4j/github-api/issues/2039/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2033", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2033/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2033/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2033/events", + "html_url": "https://github.com/hub4j/github-api/issues/2033", + "id": 2855685583, + "node_id": "I_kwDOAAlq-s6qNk3P", + "number": 2033, + "title": "Change GHRepository getIssue and getPullRequest to not mentiond ID and make it clear it is number", + "user": { + "login": "rnveach", + "id": 5427943, + "node_id": "MDQ6VXNlcjU0Mjc5NDM=", + "avatar_url": "https://avatars.githubusercontent.com/u/5427943?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/rnveach", + "html_url": "https://github.com/rnveach", + "followers_url": "https://api.github.com/users/rnveach/followers", + "following_url": "https://api.github.com/users/rnveach/following{/other_user}", + "gists_url": "https://api.github.com/users/rnveach/gists{/gist_id}", + "starred_url": "https://api.github.com/users/rnveach/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/rnveach/subscriptions", + "organizations_url": "https://api.github.com/users/rnveach/orgs", + "repos_url": "https://api.github.com/users/rnveach/repos", + "events_url": "https://api.github.com/users/rnveach/events{/privacy}", + "received_events_url": "https://api.github.com/users/rnveach/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 265902919, + "node_id": "MDU6TGFiZWwyNjU5MDI5MTk=", + "url": "https://api.github.com/repos/hub4j/github-api/labels/bug", + "name": "bug", + "color": "e11d21", + "default": true, + "description": null + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-02-15T19:55:08Z", + "updated_at": "2025-02-25T17:58:50Z", + "closed_at": "2025-02-25T17:58:50Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "`GHRepository#getIssue` takes id as an `int`. Same with `getPullRequest`.\nhttps://github.com/hub4j/github-api/blob/e14ec3b3677760714cd096ad8157a3e6a6dded65/src/main/java/org/kohsuke/github/GHRepository.java#L358\nhttps://github.com/hub4j/github-api/blob/e14ec3b3677760714cd096ad8157a3e6a6dded65/src/main/java/org/kohsuke/github/GHRepository.java#L1509\n\n`GHIssue` and `GHPullRequest` which extend `GHObject` returns a `long` for an `id`.\nhttps://github.com/hub4j/github-api/blob/e14ec3b3677760714cd096ad8157a3e6a6dded65/src/main/java/org/kohsuke/github/GHObject.java#L32\n\nTo call either, with the original object requires you to downcast.\n```\n\t\t\tif (issue) {\n\t\t\t\tfinal GHIssue issue = repository.getIssue((int) item.getId());\n\t\t\t} else {\n\t\t\t\tfinal GHPullRequest pullRequest = repository.getPullRequest((int) item.getId());\n\t\t\t}\n```\n\nIt seems to me both should be updated to be a `long` to make everything consistent and not require down casting.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2033/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/hub4j/github-api/issues/2033/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2032", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2032/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2032/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2032/events", + "html_url": "https://github.com/hub4j/github-api/issues/2032", + "id": 2854448657, + "node_id": "I_kwDOAAlq-s6qI24R", + "number": 2032, + "title": "Add more methods to QueryBuilder", + "user": { + "login": "rnveach", + "id": 5427943, + "node_id": "MDQ6VXNlcjU0Mjc5NDM=", + "avatar_url": "https://avatars.githubusercontent.com/u/5427943?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/rnveach", + "html_url": "https://github.com/rnveach", + "followers_url": "https://api.github.com/users/rnveach/followers", + "following_url": "https://api.github.com/users/rnveach/following{/other_user}", + "gists_url": "https://api.github.com/users/rnveach/gists{/gist_id}", + "starred_url": "https://api.github.com/users/rnveach/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/rnveach/subscriptions", + "organizations_url": "https://api.github.com/users/rnveach/orgs", + "repos_url": "https://api.github.com/users/rnveach/repos", + "events_url": "https://api.github.com/users/rnveach/events{/privacy}", + "received_events_url": "https://api.github.com/users/rnveach/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1662551322, + "node_id": "MDU6TGFiZWwxNjYyNTUxMzIy", + "url": "https://api.github.com/repos/hub4j/github-api/labels/enhancement", + "name": "enhancement", + "color": "0e8a16", + "default": true, + "description": "" + }, + { + "id": 1991401619, + "node_id": "MDU6TGFiZWwxOTkxNDAxNjE5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/good%20first%20issue", + "name": "good first issue", + "color": "00FF00", + "default": true, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 5, + "created_at": "2025-02-14T18:22:40Z", + "updated_at": "2026-02-10T07:58:25Z", + "closed_at": "2026-02-10T07:58:25Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "1)\n`GHPullRequestQueryBuilder` doesn't seem to support `pageSize(int)` but `GHIssueQueryBuilder` does.\n\nI don't know if there is a technical reason, but it would help to support repos with a large number of PRs. I didn't realize this was a possibility at first and took a long time to start iterating through issues.\n\n2)\n`GHIssueQueryBuilder` seems to still pull in PRs. Is it possible to add another method to the query to drop PRs from Issue queries and the same for Issues from PR queries. It would help the amount of data to transfer from the server if the server supported it.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2032/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/hub4j/github-api/issues/2032/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2026", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2026/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2026/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2026/events", + "html_url": "https://github.com/hub4j/github-api/issues/2026", + "id": 2843272749, + "node_id": "I_kwDOAAlq-s6peOYt", + "number": 2026, + "title": "GHIssueStateReason should add reopened", + "user": { + "login": "rnveach", + "id": 5427943, + "node_id": "MDQ6VXNlcjU0Mjc5NDM=", + "avatar_url": "https://avatars.githubusercontent.com/u/5427943?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/rnveach", + "html_url": "https://github.com/rnveach", + "followers_url": "https://api.github.com/users/rnveach/followers", + "following_url": "https://api.github.com/users/rnveach/following{/other_user}", + "gists_url": "https://api.github.com/users/rnveach/gists{/gist_id}", + "starred_url": "https://api.github.com/users/rnveach/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/rnveach/subscriptions", + "organizations_url": "https://api.github.com/users/rnveach/orgs", + "repos_url": "https://api.github.com/users/rnveach/repos", + "events_url": "https://api.github.com/users/rnveach/events{/privacy}", + "received_events_url": "https://api.github.com/users/rnveach/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1662551322, + "node_id": "MDU6TGFiZWwxNjYyNTUxMzIy", + "url": "https://api.github.com/repos/hub4j/github-api/labels/enhancement", + "name": "enhancement", + "color": "0e8a16", + "default": true, + "description": "" + }, + { + "id": 1991401619, + "node_id": "MDU6TGFiZWwxOTkxNDAxNjE5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/good%20first%20issue", + "name": "good first issue", + "color": "00FF00", + "default": true, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-02-10T18:22:59Z", + "updated_at": "2025-02-14T17:51:34Z", + "closed_at": "2025-02-14T17:51:34Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "While going against a repository I am involved with, I recieved this message in the console:\n````\norg.kohsuke.github.internal.EnumUtils getEnumOrDefault\nWARNING: Unknown value reopened for enum class org.kohsuke.github.GHIssueStateReason, defaulting to UNKNOWN\n````\nWould be best if it was added for proper recognition.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2026/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/hub4j/github-api/issues/2026/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2011", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2011/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2011/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2011/events", + "html_url": "https://github.com/hub4j/github-api/issues/2011", + "id": 2796095271, + "node_id": "I_kwDOAAlq-s6mqQcn", + "number": 2011, + "title": "Support for /app/installation-requests", + "user": { + "login": "anujhydrabadi", + "id": 129152617, + "node_id": "U_kgDOB7K2aQ", + "avatar_url": "https://avatars.githubusercontent.com/u/129152617?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/anujhydrabadi", + "html_url": "https://github.com/anujhydrabadi", + "followers_url": "https://api.github.com/users/anujhydrabadi/followers", + "following_url": "https://api.github.com/users/anujhydrabadi/following{/other_user}", + "gists_url": "https://api.github.com/users/anujhydrabadi/gists{/gist_id}", + "starred_url": "https://api.github.com/users/anujhydrabadi/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/anujhydrabadi/subscriptions", + "organizations_url": "https://api.github.com/users/anujhydrabadi/orgs", + "repos_url": "https://api.github.com/users/anujhydrabadi/repos", + "events_url": "https://api.github.com/users/anujhydrabadi/events{/privacy}", + "received_events_url": "https://api.github.com/users/anujhydrabadi/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2025-01-17T18:43:29Z", + "updated_at": "2025-01-21T06:57:01Z", + "closed_at": "2025-01-21T06:57:01Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "Request to support the following endpoint: https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#list-installation-requests-for-the-authenticated-app", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2011/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/hub4j/github-api/issues/2011/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2008", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2008/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2008/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2008/events", + "html_url": "https://github.com/hub4j/github-api/issues/2008", + "id": 2788286884, + "node_id": "I_kwDOAAlq-s6mMeGk", + "number": 2008, + "title": "Website down", + "user": { + "login": "wgorder-kr", + "id": 60753563, + "node_id": "MDQ6VXNlcjYwNzUzNTYz", + "avatar_url": "https://avatars.githubusercontent.com/u/60753563?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/wgorder-kr", + "html_url": "https://github.com/wgorder-kr", + "followers_url": "https://api.github.com/users/wgorder-kr/followers", + "following_url": "https://api.github.com/users/wgorder-kr/following{/other_user}", + "gists_url": "https://api.github.com/users/wgorder-kr/gists{/gist_id}", + "starred_url": "https://api.github.com/users/wgorder-kr/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/wgorder-kr/subscriptions", + "organizations_url": "https://api.github.com/users/wgorder-kr/orgs", + "repos_url": "https://api.github.com/users/wgorder-kr/repos", + "events_url": "https://api.github.com/users/wgorder-kr/events{/privacy}", + "received_events_url": "https://api.github.com/users/wgorder-kr/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 3, + "created_at": "2025-01-14T21:11:21Z", + "updated_at": "2025-02-07T19:44:32Z", + "closed_at": "2025-02-07T19:44:30Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "Can you get your website back up? I am new to the project but is a bit painful to have to look through the test cases for basics.\r\n\r\nIt looks like you were using github pages so not sure what happened.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2008/reactions", + "total_count": 5, + "+1": 5, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/hub4j/github-api/issues/2008/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1993", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1993/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1993/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1993/events", + "html_url": "https://github.com/hub4j/github-api/issues/1993", + "id": 2715964451, + "node_id": "I_kwDOAAlq-s6h4lQj", + "number": 1993, + "title": "Feature Request: Fork Only Default Branch", + "user": { + "login": "gounthar", + "id": 116569, + "node_id": "MDQ6VXNlcjExNjU2OQ==", + "avatar_url": "https://avatars.githubusercontent.com/u/116569?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gounthar", + "html_url": "https://github.com/gounthar", + "followers_url": "https://api.github.com/users/gounthar/followers", + "following_url": "https://api.github.com/users/gounthar/following{/other_user}", + "gists_url": "https://api.github.com/users/gounthar/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gounthar/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gounthar/subscriptions", + "organizations_url": "https://api.github.com/users/gounthar/orgs", + "repos_url": "https://api.github.com/users/gounthar/repos", + "events_url": "https://api.github.com/users/gounthar/events{/privacy}", + "received_events_url": "https://api.github.com/users/gounthar/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1662551322, + "node_id": "MDU6TGFiZWwxNjYyNTUxMzIy", + "url": "https://api.github.com/repos/hub4j/github-api/labels/enhancement", + "name": "enhancement", + "color": "0e8a16", + "default": true, + "description": "" + }, + { + "id": 1991401619, + "node_id": "MDU6TGFiZWwxOTkxNDAxNjE5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/good%20first%20issue", + "name": "good first issue", + "color": "00FF00", + "default": true, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 8, + "created_at": "2024-12-03T20:58:22Z", + "updated_at": "2025-01-21T06:30:47Z", + "closed_at": "2025-01-21T06:30:46Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "### What feature do you want to see added?\r\n\r\n## Current Situation\r\nWhen forking a repository on GitHub, our current process retrieves all branches from the original repository.\r\n\r\n## Issue\r\nThis approach may be inefficient and unnecessary for [our use case](https://github.com/jenkins-infra/plugin-modernizer-tool/issues/104).\r\n\r\n## Desired Outcome\r\nWe aim to fork only the default branch of the repository, as this is typically sufficient for our work.\r\n\r\n## GitHub GUI Option\r\nThe GitHub web interface provides an option to fork only the default branch (usually the main branch).\r\n\r\n\r\n## Benefits\r\n1. **Efficiency**: Reduces unnecessary data transfer and storage.\r\n2. **Simplicity**: Maintains a cleaner repository structure in our forks.\r\n3. **Focus**: Aligns with our primary need of working with the main branch.\r\n\r\n## Conclusion\r\nOptimizing our forking process to retrieve only the main branch will streamline our workflow and improve overall efficiency. This change aligns with best practices for managing forks when only the main branch is needed for development or analysis purposes.", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1993/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/hub4j/github-api/issues/1993/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1985", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1985/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1985/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1985/events", + "html_url": "https://github.com/hub4j/github-api/issues/1985", + "id": 2654357698, + "node_id": "I_kwDOAAlq-s6eNkjC", + "number": 1985, + "title": "/notifications interface return \"Unable to parse If-Modified-Since request header\"", + "user": { + "login": "AsherSu", + "id": 59462016, + "node_id": "MDQ6VXNlcjU5NDYyMDE2", + "avatar_url": "https://avatars.githubusercontent.com/u/59462016?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/AsherSu", + "html_url": "https://github.com/AsherSu", + "followers_url": "https://api.github.com/users/AsherSu/followers", + "following_url": "https://api.github.com/users/AsherSu/following{/other_user}", + "gists_url": "https://api.github.com/users/AsherSu/gists{/gist_id}", + "starred_url": "https://api.github.com/users/AsherSu/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/AsherSu/subscriptions", + "organizations_url": "https://api.github.com/users/AsherSu/orgs", + "repos_url": "https://api.github.com/users/AsherSu/repos", + "events_url": "https://api.github.com/users/AsherSu/events{/privacy}", + "received_events_url": "https://api.github.com/users/AsherSu/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2024-11-13T06:27:11Z", + "updated_at": "2024-11-21T03:58:01Z", + "closed_at": "2024-11-21T03:58:01Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "**Describe the bug**\r\n/notifications interface return \"Unable to parse If-Modified-Since request header\"\r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n```\r\ngithub.listNotifications()\r\n .nonBlocking(true)\r\n .participating(false)\r\n .read(true) \r\n .iterator()\r\n .next()\r\n .getRepository()\r\n .getOwnerName()\r\n```\r\n\r\n**Expected behavior**\r\nreturn owner\r\n\r\n**Desktop (please complete the following information):**\r\n - OS: [e.g. iOS]\r\n - Browser [e.g. chrome, safari]\r\n - Version [e.g. 22]\r\n\r\n**Additional context**\r\n```\r\nCaused by: org.kohsuke.github.HttpException: {\"message\":\"Unable to parse If-Modified-Since request header. Please make sure value is in an acceptable format.\",\"documentation_url\":\"https://docs.github.com/rest/activity/notifications#list-notifications-for-the-authenticated-user\",\"status\":\"422\"}\r\n\tat org.kohsuke.github.GitHubConnectorResponseErrorHandler$1.onError(GitHubConnectorResponseErrorHandler.java:83)\r\n\tat org.kohsuke.github.GitHubClient.detectKnownErrors(GitHubClient.java:504)\r\n\tat org.kohsuke.github.GitHubClient.sendRequest(GitHubClient.java:464)\r\n\tat org.kohsuke.github.GitHubPageIterator.fetch(GitHubPageIterator.java:146)\r\n\tat org.kohsuke.github.GitHubPageIterator.hasNext(GitHubPageIterator.java:93)\r\n\tat org.kohsuke.github.PagedIterator.fetch(PagedIterator.java:116)\r\n\tat org.kohsuke.github.PagedIterator.nextPageArray(PagedIterator.java:144)\r\n\tat org.kohsuke.github.PagedIterable.toArray(PagedIterable.java:85)\r\n\tat org.kohsuke.github.GitHubPageContentsIterable.toResponse(GitHubPageContentsIterable.java:70)\r\n\tat org.kohsuke.github.GHNotificationStream$1.fetch(GHNotificationStream.java:194)\r\n\t... 5 more\r\n```", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1985/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/hub4j/github-api/issues/1985/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1970", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1970/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1970/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1970/events", + "html_url": "https://github.com/hub4j/github-api/issues/1970", + "id": 2567834054, + "node_id": "I_kwDOAAlq-s6ZDgnG", + "number": 1970, + "title": "Cannot fork repository to personal account using GitHub app", + "user": { + "login": "jonesbusy", + "id": 825750, + "node_id": "MDQ6VXNlcjgyNTc1MA==", + "avatar_url": "https://avatars.githubusercontent.com/u/825750?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/jonesbusy", + "html_url": "https://github.com/jonesbusy", + "followers_url": "https://api.github.com/users/jonesbusy/followers", + "following_url": "https://api.github.com/users/jonesbusy/following{/other_user}", + "gists_url": "https://api.github.com/users/jonesbusy/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jonesbusy/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jonesbusy/subscriptions", + "organizations_url": "https://api.github.com/users/jonesbusy/orgs", + "repos_url": "https://api.github.com/users/jonesbusy/repos", + "events_url": "https://api.github.com/users/jonesbusy/events{/privacy}", + "received_events_url": "https://api.github.com/users/jonesbusy/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2024-10-05T10:31:49Z", + "updated_at": "2024-10-06T04:36:20Z", + "closed_at": "2024-10-06T04:36:20Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "**Describe the bug**\r\n\r\nI'm using GitHub app to authenticate and I need to fork repository to my personal account. The app has the required access.\r\n\r\nBut due to call on `getMyself()` at https://github.com/hub4j/github-api/blob/768c7154bdb84e775dfafea6b0cb27fa57d835c7/src/main/java/org/kohsuke/github/GHRepository.java#L1467 it's not possible to use public GHRepository fork() throws IOException`\r\n\r\nI would suggest to create a new API GHRepository forkTo(GHUser user) allowing to fork a repository to a given user using GitHub app authentication.\r\n\r\nWould you agree ?\r\n\r\n**To Reproduce**\r\n\r\n- Create GitHub app. Grant permisssion to create/fork repositorx\r\n- Try to use fork()\r\n\r\nSeen on https://github.com/jenkinsci/plugin-modernizer-tool/pull/295\r\n\r\n**Expected behavior**\r\n\r\nI think it's the normal behavior. When calling the fork() the fork is done but fail on the getMyself call\r\n\r\nThat's why I suggest to create a new public method `forkTo(GHUser user)` similar to `forkTo(GHOrganisation org)`\r\n\r\n**Desktop (please complete the following information):**\r\n\r\nAll\r\n\r\n**Additional context**\r\n\r\nI can submit a proposal\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1970/reactions", + "total_count": 1, + "+1": 1, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/hub4j/github-api/issues/1970/timeline", + "performed_via_github_app": null, + "state_reason": "not_planned", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1963", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1963/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1963/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1963/events", + "html_url": "https://github.com/hub4j/github-api/issues/1963", + "id": 2562978157, + "node_id": "I_kwDOAAlq-s6Yw_Ft", + "number": 1963, + "title": "org.kohsuke.github.HttpException for getOrganization", + "user": { + "login": "bisegni", + "id": 3001087, + "node_id": "MDQ6VXNlcjMwMDEwODc=", + "avatar_url": "https://avatars.githubusercontent.com/u/3001087?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bisegni", + "html_url": "https://github.com/bisegni", + "followers_url": "https://api.github.com/users/bisegni/followers", + "following_url": "https://api.github.com/users/bisegni/following{/other_user}", + "gists_url": "https://api.github.com/users/bisegni/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bisegni/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bisegni/subscriptions", + "organizations_url": "https://api.github.com/users/bisegni/orgs", + "repos_url": "https://api.github.com/users/bisegni/repos", + "events_url": "https://api.github.com/users/bisegni/events{/privacy}", + "received_events_url": "https://api.github.com/users/bisegni/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2024-10-03T02:26:33Z", + "updated_at": "2024-10-03T02:39:37Z", + "closed_at": "2024-10-03T02:39:37Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "**Describe the bug**\r\nI have a code that worked to get organization information using github application installed into this application. Not it s not working anymore getting the error below:\r\nCaused by: org.kohsuke.github.HttpException: {\"message\":\"Bad credentials\",\"documentation_url\":\"https://docs.github.com/rest\",\"status\":\"401\"}\r\n\tat org.kohsuke.github.GitHubConnectorResponseErrorHandler$1.onError(GitHubConnectorResponseErrorHandler.java:83)\r\n\tat org.kohsuke.github.GitHubClient.detectKnownErrors(GitHubClient.java:504)\r\n\tat org.kohsuke.github.GitHubClient.sendRequest(GitHubClient.java:464)\r\n\tat org.kohsuke.github.GitHubClient.sendRequest(GitHubClient.java:427)\r\n\tat org.kohsuke.github.Requester.fetch(Requester.java:85)\r\n\tat org.kohsuke.github.GitHub.getOrganization(GitHub.java:640)\r\n\t\r\nusing github app key, id and installation id i can authenticate and get app installation information but when i try to get the organization i got error, below the installation with the information loaded:\r\n```\r\nappInstallation = {GHAppInstallation@18308} \"GHAppInstallation@eb1306c[accessTokenUrl=https://api.github.com/app/installations/55541328/access_tokens,appId=1014645,events=[],htmlUrl=https://github.com/organizations/ad-build-test/settings/installations/55541328,permissions={members=WRITE, contents=WRITE, metadata=READ, pull_requests=WRITE, repository_hooks=WRITE, team_discussions=WRITE, organization_plan=READ, organization_hooks=WRITE, organization_events=READ, organization_secrets=WRITE, organization_projects=ADMIN, organization_codespaces=WRITE, organization_custom_roles=WRITE, organization_user_blocking=WRITE, organization_administration=WRITE, organization_custom_org_roles=WRITE, organization_actions_variables=WRITE, organization_custom_properties=ADMIN, organization_codespaces_secrets=WRITE, organization_dependabot_secrets=WRITE, organization_codespaces_settings=WRITE, organization_self_hosted_runners=WRITE, organization_announcement_banners=WRITE, organization_personal_access_tokens=WRITE, organization_copilot_seat_managemen\"\r\n account = {GHUser@18330} \"GHUser@5ee60e32[suspendedAt=,bio=,blog=,company=,email=,followers=0,following=0,hireable=false,location=,login=ad-build-test,name=,type=Organization,createdAt=,id=168671263,nodeId=O_kgDOCg24Hw,updatedAt=,url=https://api.github.com/users/ad-build-test]\"\r\n accessTokenUrl = \"https://api.github.com/app/installations/55541328/access_tokens\"\r\n repositoriesUrl = \"https://api.github.com/installation/repositories\"\r\n appId = 1014645\r\n targetId = 168671263\r\n targetType = {GHTargetType@18333} \"ORGANIZATION\"\r\n permissions = {LinkedHashMap@18334} size = 26\r\n events = {ArrayList@18335} size = 0\r\n singleFileName = null\r\n repositorySelection = {GHRepositorySelection@18336} \"ALL\"\r\n htmlUrl = \"https://github.com/organizations/ad-build-test/settings/installations/55541328\"\r\n suspendedAt = null\r\n suspendedBy = null\r\n responseHeaderFields = {Collections$UnmodifiableMap@18338} size = 19\r\n url = null\r\n id = 55541328\r\n nodeId = null\r\n createdAt = \"2024-10-03T00:29:22.000Z\"\r\n updatedAt = \"2024-10-03T02:20:09.000Z\"\r\n root = {GitHub@18341} \r\n```\r\n\r\ni have gave all the authorization to ad-build-test organization but i receive always the same error, to note that the same code worked month ago. Any sugestion?\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1963/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/hub4j/github-api/issues/1963/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1957", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1957/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1957/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1957/events", + "html_url": "https://github.com/hub4j/github-api/issues/1957", + "id": 2553307162, + "node_id": "I_kwDOAAlq-s6YMGAa", + "number": 1957, + "title": "GHRepository.getPullRequests(GHIssueState) not deprecated but removed from 2.x", + "user": { + "login": "bitwiseman", + "id": 1958953, + "node_id": "MDQ6VXNlcjE5NTg5NTM=", + "avatar_url": "https://avatars.githubusercontent.com/u/1958953?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bitwiseman", + "html_url": "https://github.com/bitwiseman", + "followers_url": "https://api.github.com/users/bitwiseman/followers", + "following_url": "https://api.github.com/users/bitwiseman/following{/other_user}", + "gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions", + "organizations_url": "https://api.github.com/users/bitwiseman/orgs", + "repos_url": "https://api.github.com/users/bitwiseman/repos", + "events_url": "https://api.github.com/users/bitwiseman/events{/privacy}", + "received_events_url": "https://api.github.com/users/bitwiseman/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1664647346, + "node_id": "MDU6TGFiZWwxNjY0NjQ3MzQ2", + "url": "https://api.github.com/repos/hub4j/github-api/labels/task", + "name": "task", + "color": "bfdadc", + "default": false, + "description": "" + }, + { + "id": 1780165359, + "node_id": "MDU6TGFiZWwxNzgwMTY1MzU5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/breaking%20change", + "name": "breaking change", + "color": "b60205", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2024-09-27T16:21:56Z", + "updated_at": "2025-03-18T21:07:41Z", + "closed_at": "2025-03-18T21:07:41Z", + "author_association": "MEMBER", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "https://github.com/hub4j/github-api/pull/1935/files#r1778850947 - Anchor\r\n\r\n\r\n@ihrigb \r\nNoted that `GHRepository.getPullRequests(GHIssueState)` was not marked as Deprecated but was removed in `2.0-alpha-1`. \r\n\r\nDid a search on usages:\r\nhttps://github.com/search?q=org%3Ajenkinsci+getPullRequests+path%3A%2F%5Esrc%5C%2Fmain%5C%2Fjava%5C%2F%2F+org.kohsuke.github&type=code\r\n\r\nIt looks like there's at least one usage in the wild, and since it is in Jenkins it will cause breaks for some time to come. \r\nhttps://github.com/jenkinsci/ghprb-plugin/blob/master/src/main/java/org/jenkinsci/plugins/ghprb/GhprbRepository.java#L145\r\n\r\nThere's a similar one for listPullRequests(). \r\n\r\nOptions:\r\n* Bring some methods back as bridge methods in 2.0 to not break Jenkins plugins. \r\n* Change to 1.x that converts methods removed in 2.x to bridge methods - this would force projects to change their code in their next release while maintaining binary compatibility. Maybe only convert some methods. This might be a way to push some additional changes into 2.x.\r\n\r\nOn the other hand this is a 2.0 release, some incompatibility is to be expected. \r\n\r\n\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1957/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/hub4j/github-api/issues/1957/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1951", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1951/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1951/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1951/events", + "html_url": "https://github.com/hub4j/github-api/issues/1951", + "id": 2545520390, + "node_id": "I_kwDOAAlq-s6XuY8G", + "number": 1951, + "title": "Sharing of this Github API ", + "user": { + "login": "aeonSolutions", + "id": 7936768, + "node_id": "MDQ6VXNlcjc5MzY3Njg=", + "avatar_url": "https://avatars.githubusercontent.com/u/7936768?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/aeonSolutions", + "html_url": "https://github.com/aeonSolutions", + "followers_url": "https://api.github.com/users/aeonSolutions/followers", + "following_url": "https://api.github.com/users/aeonSolutions/following{/other_user}", + "gists_url": "https://api.github.com/users/aeonSolutions/gists{/gist_id}", + "starred_url": "https://api.github.com/users/aeonSolutions/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/aeonSolutions/subscriptions", + "organizations_url": "https://api.github.com/users/aeonSolutions/orgs", + "repos_url": "https://api.github.com/users/aeonSolutions/repos", + "events_url": "https://api.github.com/users/aeonSolutions/events{/privacy}", + "received_events_url": "https://api.github.com/users/aeonSolutions/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2024-09-24T14:07:14Z", + "updated_at": "2025-01-02T23:27:51Z", + "closed_at": "2025-01-02T23:27:51Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "Hi there @bernd @vbehar @kozmic @jkrall @derfred \r\ngreat work! 😍\r\n\r\nI'm sharing your project on my own C++ project for Github API\r\nhttps://github.com/aeonSolutions/AeonLabs-GitHub-API-C-library\r\n\r\n👍", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1951/reactions", + "total_count": 2, + "+1": 2, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/hub4j/github-api/issues/1951/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1926", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1926/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1926/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1926/events", + "html_url": "https://github.com/hub4j/github-api/issues/1926", + "id": 2516758945, + "node_id": "I_kwDOAAlq-s6WArGh", + "number": 1926, + "title": "Getting timeout while connecting to Github API", + "user": { + "login": "Mohazinkhan", + "id": 97169593, + "node_id": "U_kgDOBcqwuQ", + "avatar_url": "https://avatars.githubusercontent.com/u/97169593?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Mohazinkhan", + "html_url": "https://github.com/Mohazinkhan", + "followers_url": "https://api.github.com/users/Mohazinkhan/followers", + "following_url": "https://api.github.com/users/Mohazinkhan/following{/other_user}", + "gists_url": "https://api.github.com/users/Mohazinkhan/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Mohazinkhan/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Mohazinkhan/subscriptions", + "organizations_url": "https://api.github.com/users/Mohazinkhan/orgs", + "repos_url": "https://api.github.com/users/Mohazinkhan/repos", + "events_url": "https://api.github.com/users/Mohazinkhan/events{/privacy}", + "received_events_url": "https://api.github.com/users/Mohazinkhan/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1686290078, + "node_id": "MDU6TGFiZWwxNjg2MjkwMDc4", + "url": "https://api.github.com/repos/hub4j/github-api/labels/external", + "name": "external", + "color": "a0a0a0", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 14, + "created_at": "2024-09-10T15:16:24Z", + "updated_at": "2025-03-23T07:23:44Z", + "closed_at": "2025-03-23T07:23:42Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "I have configured a Github App for Jenkins and I am running a Global seed job which would connect to the Github API and retrieve all the repositories in the Organization. Whenever it tries to authenticate to the Github API and retrieve the list of repositories it fails with timeout and the following error is displayed,\r\n```\r\nCaused: org.kohsuke.github.HttpException: Server returned HTTP response code: -1, message: 'null' for URL: [https://api.github.com/orgs/{orgname}]\r\n\r\nStacktrace: \r\n\r\nhudson.remoting.ProxyException: java.net.SocketTimeoutException: Connect timed out\r\n\tat java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:551)\r\n\tat java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:602)\r\n\tat java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327)\r\n\tat java.base/java.net.Socket.connect(Socket.java:633)\r\n\tat java.base/sun.security.ssl.SSLSocketImpl.connect(SSLSocketImpl.java:304)\r\n\tat java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:178)\r\n\tat java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:533)\r\n\tat java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:638)\r\n\tat java.base/sun.net.www.protocol.https.HttpsClient.(HttpsClient.java:266)\r\n\tat java.base/sun.net.www.protocol.https.HttpsClient.New(HttpsClient.java:380)\r\n\tat java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.getNewHttpClient(AbstractDelegateHttpsURLConnection.java:193)\r\n\tat java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1241)\r\n\tat java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1127)\r\n\tat java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:179)\r\n\tat java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1686)\r\n\tat java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1610)\r\n\tat java.base/java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:529)\r\n\tat java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:308)\r\n\tat org.kohsuke.github.GitHubHttpUrlConnectionClient.getResponseInfo(GitHubHttpUrlConnectionClient.java:69)\r\n\tat org.kohsuke.github.GitHubClient.sendRequest(GitHubClient.java:400)\r\nAlso: hudson.remoting.ProxyException: org.jenkinsci.plugins.workflow.actions.ErrorAction$ErrorId: fa952780-e9a2-4351-b74d-d3851ac026e3\r\nCaused: hudson.remoting.ProxyException: org.kohsuke.github.HttpException: Server returned HTTP response code: -1, message: 'null' for URL: https://api.github.com/orgs/\r\n\tat org.kohsuke.github.GitHubClient.interpretApiError(GitHubClient.java:500)\r\n\tat org.kohsuke.github.GitHubClient.sendRequest(GitHubClient.java:420)\r\n\tat org.kohsuke.github.GitHubClient.sendRequest(GitHubClient.java:363)\r\n\tat org.kohsuke.github.Requester.fetch(Requester.java:74)\r\n\tat org.kohsuke.github.GitHub.getOrganization(GitHub.java:505)\r\n\tat org.kohsuke.github.GitHub$getOrganization.call(Unknown Source)\r\n\tat org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)\r\n\tat org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)\r\n\tat org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:128)\r\n\tat com..jenkins.jobdsl.GithubFetcher.(GithubFetcher.groovy:19)\r\n\tat java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)\r\n\tat java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)\r\n\tat java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500)\r\n\tat java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:481)\r\n\tat org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:83)\r\n\tat org.codehaus.groovy.reflection.CachedConstructor.doConstructorInvoke(CachedConstructor.java:77)\r\n\tat org.codehaus.groovy.runtime.callsite.ConstructorSite.callConstructor(ConstructorSite.java:45)\r\n\tat org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:59)\r\n\tat org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:238)\r\n\tat org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:258)\r\n\tat uc_generator.generateUcRepos(uc_generator.groovy:38)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\r\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:569)\r\n\tat org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSiteNoUnwrapNoCoerce.invoke(PogoMetaMethodSite.java:210)\r\n\tat org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:59)\r\n\tat org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:51)\r\n\tat org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:157)\r\n\tat org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:161)\r\n\tat uc_generator.run(uc_generator.groovy:8)\r\n\tat uc_generator$run.call(Unknown Source)\r\n\tat org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)\r\n\tat uc_generator$run.call(Unknown Source)\r\n\tat PluginClassLoader for job-dsl//javaposse.jobdsl.dsl.AbstractDslScriptLoader.runScript(AbstractDslScriptLoader.groovy:138)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\r\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:569)\r\n\tat org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSiteNoUnwrapNoCoerce.invoke(PogoMetaMethodSite.java:210)\r\n\tat org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:59)\r\n\tat org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:51)\r\n\tat org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:64)\r\n\tat org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:169)\r\n\tat PluginClassLoader for job-dsl//javaposse.jobdsl.dsl.AbstractDslScriptLoader.runScriptEngine(AbstractDslScriptLoader.groovy:108)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\r\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:569)\r\n\tat org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:98)\r\n\tat groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)\r\n\tat org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:352)\r\n\tat groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1034)\r\n\tat org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:68)\r\n\tat org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:177)\r\n\tat PluginClassLoader for job-dsl//javaposse.jobdsl.dsl.AbstractDslScriptLoader$_runScripts_closure1.doCall(AbstractDslScriptLoader.groovy:61)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\r\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:569)\r\n\tat org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:98)\r\n\tat groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)\r\n\tat org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:264)\r\n\tat groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1034)\r\n\tat groovy.lang.Closure.call(Closure.java:420)\r\n\tat groovy.lang.Closure.call(Closure.java:436)\r\n\tat org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2125)\r\n\tat org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2110)\r\n\tat org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2163)\r\n\tat org.codehaus.groovy.runtime.dgm$165.invoke(Unknown Source)\r\n\tat org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:274)\r\n\tat org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:56)\r\n\tat org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:128)\r\n\tat PluginClassLoader for job-dsl//javaposse.jobdsl.dsl.AbstractDslScriptLoader.runScripts(AbstractDslScriptLoader.groovy:46)\r\n\tat PluginClassLoader for job-dsl//javaposse.jobdsl.plugin.ExecuteDslScripts.perform(ExecuteDslScripts.java:363)\r\n\tat jenkins.tasks.SimpleBuildStep.perform(SimpleBuildStep.java:123)\r\n\tat PluginClassLoader for workflow-basic-steps//org.jenkinsci.plugins.workflow.steps.CoreStep$Execution.run(CoreStep.java:101)\r\n\tat PluginClassLoader for workflow-basic-steps//org.jenkinsci.plugins.workflow.steps.CoreStep$Execution.run(CoreStep.java:71)\r\n\tat PluginClassLoader for workflow-step-api//org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution.lambda$start$0(SynchronousNonBlockingStepExecution.java:47)\r\n\tat java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)\r\n\tat java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)\r\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)\r\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)\r\n\tat java.base/java.lang.Thread.run(Thread.java:840)\r\n```", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1926/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/hub4j/github-api/issues/1926/timeline", + "performed_via_github_app": null, + "state_reason": "not_planned", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1924", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1924/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1924/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1924/events", + "html_url": "https://github.com/hub4j/github-api/issues/1924", + "id": 2514785107, + "node_id": "I_kwDOAAlq-s6V5JNT", + "number": 1924, + "title": "[Vulnerable dependency upgrade] commons-io", + "user": { + "login": "dev-2-controltowerai", + "id": 167620350, + "node_id": "U_kgDOCf2u_g", + "avatar_url": "https://avatars.githubusercontent.com/u/167620350?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dev-2-controltowerai", + "html_url": "https://github.com/dev-2-controltowerai", + "followers_url": "https://api.github.com/users/dev-2-controltowerai/followers", + "following_url": "https://api.github.com/users/dev-2-controltowerai/following{/other_user}", + "gists_url": "https://api.github.com/users/dev-2-controltowerai/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dev-2-controltowerai/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dev-2-controltowerai/subscriptions", + "organizations_url": "https://api.github.com/users/dev-2-controltowerai/orgs", + "repos_url": "https://api.github.com/users/dev-2-controltowerai/repos", + "events_url": "https://api.github.com/users/dev-2-controltowerai/events{/privacy}", + "received_events_url": "https://api.github.com/users/dev-2-controltowerai/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2024-09-09T19:51:06Z", + "updated_at": "2024-09-10T16:02:13Z", + "closed_at": "2024-09-10T16:02:13Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "Apache commons io package is outdated with a bunch of vulnerabilities. Can someone update it?", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1924/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/hub4j/github-api/issues/1924/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1915", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1915/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1915/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1915/events", + "html_url": "https://github.com/hub4j/github-api/issues/1915", + "id": 2492552073, + "node_id": "I_kwDOAAlq-s6UkVOJ", + "number": 1915, + "title": "Support /repos/{owner}/{repo}/vulnerability-alerts", + "user": { + "login": "ranma2913", + "id": 4295880, + "node_id": "MDQ6VXNlcjQyOTU4ODA=", + "avatar_url": "https://avatars.githubusercontent.com/u/4295880?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/ranma2913", + "html_url": "https://github.com/ranma2913", + "followers_url": "https://api.github.com/users/ranma2913/followers", + "following_url": "https://api.github.com/users/ranma2913/following{/other_user}", + "gists_url": "https://api.github.com/users/ranma2913/gists{/gist_id}", + "starred_url": "https://api.github.com/users/ranma2913/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/ranma2913/subscriptions", + "organizations_url": "https://api.github.com/users/ranma2913/orgs", + "repos_url": "https://api.github.com/users/ranma2913/repos", + "events_url": "https://api.github.com/users/ranma2913/events{/privacy}", + "received_events_url": "https://api.github.com/users/ranma2913/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2024-08-28T16:35:38Z", + "updated_at": "2024-09-03T20:31:59Z", + "closed_at": "2024-09-03T20:31:59Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "Support Endpoints:\r\n\r\n- https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#check-if-vulnerability-alerts-are-enabled-for-a-repository\r\n- https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#enable-vulnerability-alerts\r\n- https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#disable-vulnerability-alerts", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1915/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/hub4j/github-api/issues/1915/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1909", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1909/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1909/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1909/events", + "html_url": "https://github.com/hub4j/github-api/issues/1909", + "id": 2472357939, + "node_id": "I_kwDOAAlq-s6TXTAz", + "number": 1909, + "title": "AbuseLimitHandler does not handle scenarios where the local time is not synchronized with GitHub server time", + "user": { + "login": "bitwiseman", + "id": 1958953, + "node_id": "MDQ6VXNlcjE5NTg5NTM=", + "avatar_url": "https://avatars.githubusercontent.com/u/1958953?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bitwiseman", + "html_url": "https://github.com/bitwiseman", + "followers_url": "https://api.github.com/users/bitwiseman/followers", + "following_url": "https://api.github.com/users/bitwiseman/following{/other_user}", + "gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions", + "organizations_url": "https://api.github.com/users/bitwiseman/orgs", + "repos_url": "https://api.github.com/users/bitwiseman/repos", + "events_url": "https://api.github.com/users/bitwiseman/events{/privacy}", + "received_events_url": "https://api.github.com/users/bitwiseman/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 265902919, + "node_id": "MDU6TGFiZWwyNjU5MDI5MTk=", + "url": "https://api.github.com/repos/hub4j/github-api/labels/bug", + "name": "bug", + "color": "e11d21", + "default": true, + "description": null + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 2, + "created_at": "2024-08-19T03:08:21Z", + "updated_at": "2024-10-14T17:19:05Z", + "closed_at": "2024-10-14T17:19:04Z", + "author_association": "MEMBER", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "From [this comment](https://github.com/hub4j/github-api/pull/1895/files#r1704397808) on #1895 : \r\n\r\n> GitHubClient has a method to get Date (or Instant).\r\n> https://github.com/hub4j/github-api/blob/main/src/main/java/org/kohsuke/github/GitHubClient.java#L916\r\n> \r\n> Unfortunately, diff from local machine time is not reliable. The local machine may not be synchronized with the server time. We have to use the diff from the response time.\r\n> https://github.com/hub4j/github-api/blob/main/src/main/java/org/kohsuke/github/GHRateLimit.java#L543-L571\r\n\r\n> However, this PR is a huge step forward and I'd rather have this less than perfect code added than wait for the next release.\r\n\r\nThis is the code that need updating:\r\n\r\nhttps://github.com/hub4j/github-api/blob/314917eabf9762e0d62d52f3ae68bc9ff6ba7ed5/src/main/java/org/kohsuke/github/AbuseLimitHandler.java#L116-L122", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1909/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/hub4j/github-api/issues/1909/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1908", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1908/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1908/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1908/events", + "html_url": "https://github.com/hub4j/github-api/issues/1908", + "id": 2467567361, + "node_id": "I_kwDOAAlq-s6TFBcB", + "number": 1908, + "title": "Enable github-api to support GraalVM native images", + "user": { + "login": "klopfdreh", + "id": 980773, + "node_id": "MDQ6VXNlcjk4MDc3Mw==", + "avatar_url": "https://avatars.githubusercontent.com/u/980773?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/klopfdreh", + "html_url": "https://github.com/klopfdreh", + "followers_url": "https://api.github.com/users/klopfdreh/followers", + "following_url": "https://api.github.com/users/klopfdreh/following{/other_user}", + "gists_url": "https://api.github.com/users/klopfdreh/gists{/gist_id}", + "starred_url": "https://api.github.com/users/klopfdreh/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/klopfdreh/subscriptions", + "organizations_url": "https://api.github.com/users/klopfdreh/orgs", + "repos_url": "https://api.github.com/users/klopfdreh/repos", + "events_url": "https://api.github.com/users/klopfdreh/events{/privacy}", + "received_events_url": "https://api.github.com/users/klopfdreh/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 265902955, + "node_id": "MDU6TGFiZWwyNjU5MDI5NTU=", + "url": "https://api.github.com/repos/hub4j/github-api/labels/new%20feature", + "name": "new feature", + "color": "f4cc53", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 18, + "created_at": "2024-08-15T07:31:23Z", + "updated_at": "2024-09-05T16:24:05Z", + "closed_at": "2024-09-05T16:24:05Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "**Describe the bug**\r\nDue to some reflections you encounter errors during the runtime when `github-api` is used in a native image.\r\n\r\nExample\r\n```plain\r\nat java.base@22.0.1/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)\\nCaused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.kohsuke.github.GHRepository`: cannot deserialize from Object value (no delegate- or property-based Creator): this appears to be a native image, in which case you may need to configure reflection for the class that is to be deserialized\\n at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 2]\r\n```\r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n1. Build an application with Spring Boot Native and `github-api`\r\n2. Perform a `native-image` build\r\n3. Run the native application\r\n\r\n**Expected behavior**\r\n`github-api` should be used in a native image without any issues\r\n\r\n**Desktop (please complete the following information):**\r\n N/A\r\n\r\n**Additional context**\r\nYou could add `META-INF/native-image///reflect-config.json` and describe the reflection usage:\r\n\r\nExample:\r\n```json\r\n[\r\n {\r\n \"name\": \"org.kohsuke.github.GHRepository\",\r\n \r\n }\r\n]\r\n```\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1908/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/hub4j/github-api/issues/1908/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1905", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1905/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1905/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1905/events", + "html_url": "https://github.com/hub4j/github-api/issues/1905", + "id": 2449641453, + "node_id": "I_kwDOAAlq-s6SAo_t", + "number": 1905, + "title": "List Anonymous Repository Contributors", + "user": { + "login": "augustd", + "id": 1258191, + "node_id": "MDQ6VXNlcjEyNTgxOTE=", + "avatar_url": "https://avatars.githubusercontent.com/u/1258191?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/augustd", + "html_url": "https://github.com/augustd", + "followers_url": "https://api.github.com/users/augustd/followers", + "following_url": "https://api.github.com/users/augustd/following{/other_user}", + "gists_url": "https://api.github.com/users/augustd/gists{/gist_id}", + "starred_url": "https://api.github.com/users/augustd/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/augustd/subscriptions", + "organizations_url": "https://api.github.com/users/augustd/orgs", + "repos_url": "https://api.github.com/users/augustd/repos", + "events_url": "https://api.github.com/users/augustd/events{/privacy}", + "received_events_url": "https://api.github.com/users/augustd/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1662551322, + "node_id": "MDU6TGFiZWwxNjYyNTUxMzIy", + "url": "https://api.github.com/repos/hub4j/github-api/labels/enhancement", + "name": "enhancement", + "color": "0e8a16", + "default": true, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 2, + "created_at": "2024-08-05T23:31:16Z", + "updated_at": "2025-01-06T17:08:10Z", + "closed_at": "2025-01-06T17:08:10Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "The Github API docs (https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repository-contributors) say that there is am `anon=true` parameter to add to `/repos/{owner}/{repo}/contributors` in order to fetch anonymous contributions. \r\n\r\nThere does not seem to be an option in the API to add that parameter. Is there any way to fetch anonymous contributors? ", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1905/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/hub4j/github-api/issues/1905/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1852", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/1852/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/1852/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/1852/events", + "html_url": "https://github.com/hub4j/github-api/issues/1852", + "id": 2344425004, + "node_id": "I_kwDOAAlq-s6LvRYs", + "number": 1852, + "title": "Can't Iterate over ghRepo.listCollaborators()", + "user": { + "login": "MouadhKh", + "id": 50799773, + "node_id": "MDQ6VXNlcjUwNzk5Nzcz", + "avatar_url": "https://avatars.githubusercontent.com/u/50799773?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/MouadhKh", + "html_url": "https://github.com/MouadhKh", + "followers_url": "https://api.github.com/users/MouadhKh/followers", + "following_url": "https://api.github.com/users/MouadhKh/following{/other_user}", + "gists_url": "https://api.github.com/users/MouadhKh/gists{/gist_id}", + "starred_url": "https://api.github.com/users/MouadhKh/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/MouadhKh/subscriptions", + "organizations_url": "https://api.github.com/users/MouadhKh/orgs", + "repos_url": "https://api.github.com/users/MouadhKh/repos", + "events_url": "https://api.github.com/users/MouadhKh/events{/privacy}", + "received_events_url": "https://api.github.com/users/MouadhKh/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2024-06-10T17:06:18Z", + "updated_at": "2024-06-11T19:17:11Z", + "closed_at": "2024-06-11T19:16:21Z", + "author_association": "NONE", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "issue_dependencies_summary": { + "blocked_by": 0, + "total_blocked_by": 0, + "blocking": 0, + "total_blocking": 0 + }, + "body": "**Description**\r\nAccessing single collaborators by iterating over `ghRepo.listCollaborators()` is not possible for some repositories (all public)\r\nThe detailed message looks : \r\n`Server returned HTTP response code: -1, message: 'null' for URL: www.reposUrl.com`\r\n\r\n**To Reproduce**\r\nIt doesn't matter which token I use ( classic token with all permissions/Fine-grained token with all permissions). For some repositories, it is not possible to go over the collaborators.\r\nThe result of the following curl varies depending on the used token(classic/new)\r\n`curl -L \\\r\n -H \"Accept: application/vnd.github+json\" \\\r\n -H \"Authorization: Bearer TOKEN_PLACEHOLDER\" \\\r\n -H \"X-GitHub-Api-Version: 2022-11-28\" \\\r\n URL`\r\n\r\n**Classic PAT** --> Must have push access to view repository collaborators.\r\n**Fine grained token** --> Resource not accessible by personal access token\r\n\r\nThe first displayed error message allude to missing permissions, which I think is wrong since the repository is publicly accessible\r\nThe second error message is more confusing and contradicts the documentation(https://docs.github.com/rest/collaborators/collaborators#list-repository-collaborators)\r\n\r\n**Expected behavior**\r\nCan access collaborators of all public repositories \r\n\r\n**Additional context**\r\nThe given repository is a special case because it is archived(should be readable nevertheless). But this problem persists with other non-archived repositories aswell", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/1852/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/hub4j/github-api/issues/1852/timeline", + "performed_via_github_app": null, + "state_reason": "completed", + "score": 1 + } + ] +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchIssuesOnly/mappings/1-user.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchIssuesOnly/mappings/1-user.json new file mode 100644 index 0000000000..acb861f081 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchIssuesOnly/mappings/1-user.json @@ -0,0 +1,48 @@ +{ + "id": "fcc027dc-1763-4fc2-8537-10be7501b306", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "1-user.json", + "headers": { + "Date": "Tue, 10 Feb 2026 20:14:50 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"15d7e1ad92a3639b979fc55254902e63ee0bfa5c8f6766990bf989044d491ce1\"", + "Last-Modified": "Sat, 24 Jan 2026 22:07:12 GMT", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4905", + "X-RateLimit-Reset": "1770755531", + "X-RateLimit-Used": "95", + "X-RateLimit-Resource": "core", + "X-GitHub-Request-Id": "FC8F:31B50A:A0F14CA:959A082:698B91B9" + } + }, + "uuid": "fcc027dc-1763-4fc2-8537-10be7501b306", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchIssuesOnly/mappings/2-search_issues.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchIssuesOnly/mappings/2-search_issues.json new file mode 100644 index 0000000000..01e9ad04ab --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchIssuesOnly/mappings/2-search_issues.json @@ -0,0 +1,50 @@ +{ + "id": "02b59382-023b-4a4c-976d-a1f5dc88c747", + "name": "search_issues", + "request": { + "url": "/search/issues?sort=created&q=repo%3Ahub4j%2Fgithub-api+is%3Aissue+is%3Aclosed", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "2-search_issues.json", + "headers": { + "Date": "Tue, 10 Feb 2026 20:14:51 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "no-cache", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "30", + "X-RateLimit-Remaining": "27", + "X-RateLimit-Reset": "1770754529", + "X-RateLimit-Used": "3", + "X-RateLimit-Resource": "search", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-GitHub-Request-Id": "FC91:2EFE2C:A1558AD:9585268:698B91BA", + "Link": "; rel=\"next\", ; rel=\"last\"" + } + }, + "uuid": "02b59382-023b-4a4c-976d-a1f5dc88c747", + "persistent": true, + "scenarioName": "scenario-1-search-issues", + "requiredScenarioState": "Started", + "newScenarioState": "scenario-1-search-issues-2", + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchIssuesOnly/mappings/3-search_issues.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchIssuesOnly/mappings/3-search_issues.json new file mode 100644 index 0000000000..8f53581075 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchIssuesOnly/mappings/3-search_issues.json @@ -0,0 +1,49 @@ +{ + "id": "ccfa0439-0837-4599-a44e-9ca76d1b0328", + "name": "search_issues", + "request": { + "url": "/search/issues?sort=created&q=repo%3Ahub4j%2Fgithub-api+is%3Aissue+is%3Aclosed", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "3-search_issues.json", + "headers": { + "Date": "Tue, 10 Feb 2026 20:14:52 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "no-cache", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "30", + "X-RateLimit-Remaining": "26", + "X-RateLimit-Reset": "1770754529", + "X-RateLimit-Used": "4", + "X-RateLimit-Resource": "search", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-GitHub-Request-Id": "FC92:3085FD:A017065:94A6FBB:698B91BB", + "Link": "; rel=\"next\", ; rel=\"last\"" + } + }, + "uuid": "ccfa0439-0837-4599-a44e-9ca76d1b0328", + "persistent": true, + "scenarioName": "scenario-1-search-issues", + "requiredScenarioState": "scenario-1-search-issues-2", + "insertionIndex": 3 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchPullRequestsOnly/__files/1-user.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchPullRequestsOnly/__files/1-user.json new file mode 100644 index 0000000000..fbc5eae788 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchPullRequestsOnly/__files/1-user.json @@ -0,0 +1,36 @@ +{ + "login": "Anonycoders", + "id": 40047636, + "node_id": "MDQ6VXNlcjQwMDQ3NjM2", + "avatar_url": "https://avatars.githubusercontent.com/u/40047636?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Anonycoders", + "html_url": "https://github.com/Anonycoders", + "followers_url": "https://api.github.com/users/Anonycoders/followers", + "following_url": "https://api.github.com/users/Anonycoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Anonycoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Anonycoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Anonycoders/subscriptions", + "organizations_url": "https://api.github.com/users/Anonycoders/orgs", + "repos_url": "https://api.github.com/users/Anonycoders/repos", + "events_url": "https://api.github.com/users/Anonycoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Anonycoders/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false, + "name": "Sorena Sarabadani", + "company": "@Adevinta", + "blog": "", + "location": "Berlin, Germany", + "email": "sorena.sarabadani@gmail.com", + "hireable": null, + "bio": "Ex-Shopifyer - Adevinta/Kleinanzeigen", + "twitter_username": "sorena_s", + "notification_email": "sorena.sarabadani@gmail.com", + "public_repos": 12, + "public_gists": 0, + "followers": 38, + "following": 4, + "created_at": "2018-06-08T02:07:15Z", + "updated_at": "2026-01-24T22:07:12Z" +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchPullRequestsOnly/__files/2-search_issues.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchPullRequestsOnly/__files/2-search_issues.json new file mode 100644 index 0000000000..bdce10bd5b --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchPullRequestsOnly/__files/2-search_issues.json @@ -0,0 +1,2554 @@ +{ + "total_count": 1370, + "incomplete_results": false, + "items": [ + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2193", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2193/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2193/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2193/events", + "html_url": "https://github.com/hub4j/github-api/pull/2193", + "id": 3880789387, + "node_id": "PR_kwDOAAlq-s7ApqDL", + "number": 2193, + "title": "Chore(deps): Bump org.jacoco:jacoco-maven-plugin from 0.8.13 to 0.8.14", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-02-01T02:02:49Z", + "updated_at": "2026-02-10T07:47:33Z", + "closed_at": "2026-02-10T07:47:20Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2193", + "html_url": "https://github.com/hub4j/github-api/pull/2193", + "diff_url": "https://github.com/hub4j/github-api/pull/2193.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2193.patch", + "merged_at": "2026-02-10T07:47:19Z" + }, + "body": "Bumps [org.jacoco:jacoco-maven-plugin](https://github.com/jacoco/jacoco) from 0.8.13 to 0.8.14.\n
\nRelease notes\n

Sourced from org.jacoco:jacoco-maven-plugin's releases.

\n
\n

0.8.14

\n

New Features

\n
    \n
  • JaCoCo now officially supports Java 25 (GitHub #1950).
  • \n
  • Experimental support for Java 26 class files (GitHub #1870).
  • \n
  • Branches added by the Kotlin compiler for default argument number 33 or higher are filtered out during generation of report (GitHub #1655).
  • \n
  • Part of bytecode generated by the Kotlin compiler for elvis operator that follows safe call operator is filtered out during generation of report (GitHub #1814, #1954).
  • \n
  • Part of bytecode generated by the Kotlin compiler for more cases of chained safe call operators is filtered out during generation of report (GitHub #1956).
  • \n
  • Part of bytecode generated by the Kotlin compiler for invocations of suspendCoroutineUninterceptedOrReturn intrinsic is filtered out during generation of report (GitHub #1929).
  • \n
  • Part of bytecode generated by the Kotlin compiler for suspending lambdas with parameters is filtered out during generation of report (GitHub #1945).
  • \n
  • Part of bytecode generated by the Kotlin compiler for suspending functions and lambdas with suspension points that return inline value class is filtered out during generation of report (GitHub #1871).
  • \n
  • Part of bytecode generated by the Kotlin Compose compiler plugin for pausable composition is filtered out during generation of report (GitHub #1911).
  • \n
  • Methods generated by the Kotlin serialization compiler plugin are filtered out (GitHub #1885, #1970, #1971).
  • \n
\n

Fixed bugs

\n
    \n
  • Fixed handling of implicit else clause of when with String subject in Kotlin (GitHub #1813, #1940).
  • \n
  • Fixed handling of implicit default clause of switch by String in Java when compiled by ECJ (GitHub #1813, #1940).\nFixed handling of exceptions in chains of safe call operators in Kotlin (GitHub #1819).
  • \n
\n

Non-functional Changes

\n
    \n
  • JaCoCo now depends on ASM 9.9 (GitHub #1965).
  • \n
\n
\n
\n
\nCommits\n
    \n
  • 2eb2483 Prepare release v0.8.14
  • \n
  • de76181 KotlinSerializableFilter should filter more methods (#1971)
  • \n
  • 89c4bd5 Fix NPE in KotlinSerializableFilter (#1970)
  • \n
  • 0981128 Migrate release staging to the Central Publisher Portal (#1968)
  • \n
  • d07bc6b Add filter for bytecode generated by Kotlin serialization compiler plugin (#1...
  • \n
  • 5e35fd5 Upgrade maven-dependency-plugin to 3.9.0 (#1966)
  • \n
  • c2fe5cc Upgrade ASM to 9.9 (#1965)
  • \n
  • b0f8e23 KotlinSafeCallOperatorFilter should filter "unoptimized" safe call followed b...
  • \n
  • c7bd3f4 Upgrade spotless-maven-plugin to 3.0.0 (#1961)
  • \n
  • faa289d KotlinSafeCallOperatorFilter should not be affected by presence of pseudo ins...
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.jacoco:jacoco-maven-plugin&package-manager=maven&previous-version=0.8.13&new-version=0.8.14)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2193/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/hub4j/github-api/issues/2193/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2191", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2191/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2191/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2191/events", + "html_url": "https://github.com/hub4j/github-api/pull/2191", + "id": 3880788559, + "node_id": "PR_kwDOAAlq-s7App3r", + "number": 2191, + "title": "Chore(deps): Bump org.apache.maven.plugins:maven-gpg-plugin from 3.2.7 to 3.2.8", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-02-01T02:02:28Z", + "updated_at": "2026-02-10T07:48:12Z", + "closed_at": "2026-02-10T07:48:04Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2191", + "html_url": "https://github.com/hub4j/github-api/pull/2191", + "diff_url": "https://github.com/hub4j/github-api/pull/2191.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2191.patch", + "merged_at": "2026-02-10T07:48:04Z" + }, + "body": "Bumps [org.apache.maven.plugins:maven-gpg-plugin](https://github.com/apache/maven-gpg-plugin) from 3.2.7 to 3.2.8.\n
\nRelease notes\n

Sourced from org.apache.maven.plugins:maven-gpg-plugin's releases.

\n
\n

3.2.8

\n\n

🐛 Bug Fixes

\n\n

📝 Documentation updates

\n\n

đŸ‘ģ Maintenance

\n\n

đŸ“Ļ Dependency updates

\n\n
\n
\n
\nCommits\n
    \n
  • 8a46455 [maven-release-plugin] prepare release maven-gpg-plugin-3.2.8
  • \n
  • 7012821 Fix issueManagement, ciManagement system and url
  • \n
  • a9a8c84 Make empty classifier null (not empty string) (#287)
  • \n
  • a8368b0 Add .mvn
  • \n
  • f0e45e0 Update parent POM to 45 (#284)
  • \n
  • cb1236c Bump bouncycastleVersion from 1.78.1 to 1.80 (#127)
  • \n
  • 5377a10 Bump commons-io:commons-io from 2.18.0 to 2.19.0 (#133)
  • \n
  • 8b63932 Bump org.apache.maven.plugins:maven-invoker-plugin from 3.8.0 to 3.9.0 (#125)
  • \n
  • 54ea518 Bump org.simplify4u.plugins:pgpverify-maven-plugin from 1.18.2 to 1.19.1
  • \n
  • a6a412d Remove old JIRA issue link
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.apache.maven.plugins:maven-gpg-plugin&package-manager=maven&previous-version=3.2.7&new-version=3.2.8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2191/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/hub4j/github-api/issues/2191/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2190", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2190/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2190/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2190/events", + "html_url": "https://github.com/hub4j/github-api/pull/2190", + "id": 3858243660, + "node_id": "PR_kwDOAAlq-s6_e9RM", + "number": 2190, + "title": "fix: override GHPullRequest isPullRequest", + "user": { + "login": "Anonycoders", + "id": 40047636, + "node_id": "MDQ6VXNlcjQwMDQ3NjM2", + "avatar_url": "https://avatars.githubusercontent.com/u/40047636?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Anonycoders", + "html_url": "https://github.com/Anonycoders", + "followers_url": "https://api.github.com/users/Anonycoders/followers", + "following_url": "https://api.github.com/users/Anonycoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Anonycoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Anonycoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Anonycoders/subscriptions", + "organizations_url": "https://api.github.com/users/Anonycoders/orgs", + "repos_url": "https://api.github.com/users/Anonycoders/repos", + "events_url": "https://api.github.com/users/Anonycoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Anonycoders/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-01-27T00:18:17Z", + "updated_at": "2026-02-10T07:49:48Z", + "closed_at": "2026-02-10T07:49:34Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2190", + "html_url": "https://github.com/hub4j/github-api/pull/2190", + "diff_url": "https://github.com/hub4j/github-api/pull/2190.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2190.patch", + "merged_at": "2026-02-10T07:49:34Z" + }, + "body": "# Description\r\n\r\n* Fixes #2061\r\n\r\n# Before submitting a PR:\r\n\r\n- [x] Changes must not break binary backwards compatibility. If you are unclear on how to make the change you think is needed while maintaining backward compatibility, [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Add JavaDocs and other comments explaining the behavior. \r\n- [x] When adding or updating methods that fetch entities, add `@link` JavaDoc entries to the relevant documentation on https://docs.github.com/en/rest . \r\n- [x] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Run `mvn -D enable-ci clean install site \"-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED\"` locally. If this command doesn't succeed, your change will not pass CI.\r\n- [x] Push your changes to a branch other than `main`. You will create your PR from that branch.\r\n\r\n# When creating a PR: \r\n\r\n- [x] Fill in the \"Description\" above with clear summary of the changes. This includes:\r\n - [x] If this PR fixes one or more issues, include \"Fixes #\" lines for each issue. \r\n - [ ] Provide links to relevant documentation on https://docs.github.com/en/rest where possible. If not including links, explain why not.\r\n- [x] All lines of new code should be covered by tests as reported by code coverage. Any lines that are not covered must have PR comments explaining why they cannot be covered. For example, \"Reaching this particular exception is hard and is not a particular common scenario.\"\r\n- [x] Enable \"Allow edits from maintainers\".\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2190/reactions", + "total_count": 1, + "+1": 1, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/hub4j/github-api/issues/2190/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2185", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2185/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2185/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2185/events", + "html_url": "https://github.com/hub4j/github-api/pull/2185", + "id": 3853746359, + "node_id": "PR_kwDOAAlq-s6_QWo_", + "number": 2185, + "title": "feat: paginated gh pull request query builder", + "user": { + "login": "Anonycoders", + "id": 40047636, + "node_id": "MDQ6VXNlcjQwMDQ3NjM2", + "avatar_url": "https://avatars.githubusercontent.com/u/40047636?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Anonycoders", + "html_url": "https://github.com/Anonycoders", + "followers_url": "https://api.github.com/users/Anonycoders/followers", + "following_url": "https://api.github.com/users/Anonycoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Anonycoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Anonycoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Anonycoders/subscriptions", + "organizations_url": "https://api.github.com/users/Anonycoders/orgs", + "repos_url": "https://api.github.com/users/Anonycoders/repos", + "events_url": "https://api.github.com/users/Anonycoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Anonycoders/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-01-25T19:29:46Z", + "updated_at": "2026-02-10T07:59:14Z", + "closed_at": "2026-02-10T07:58:24Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2185", + "html_url": "https://github.com/hub4j/github-api/pull/2185", + "diff_url": "https://github.com/hub4j/github-api/pull/2185.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2185.patch", + "merged_at": "2026-02-10T07:58:24Z" + }, + "body": "# Description\r\n\r\n* Fixes #2032 - (`GHPullRequestQueryBuilder` doesn't seem to support `pageSize(int)` but `GHIssueQueryBuilder` does)\r\n\r\nhttps://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#list-pull-requests\r\n\r\n# Before submitting a PR:\r\n\r\n- [x] Changes must not break binary backwards compatibility. If you are unclear on how to make the change you think is needed while maintaining backward compatibility, [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Add JavaDocs and other comments explaining the behavior. \r\n- [x] When adding or updating methods that fetch entities, add `@link` JavaDoc entries to the relevant documentation on https://docs.github.com/en/rest . \r\n- [x] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Run `mvn -D enable-ci clean install site \"-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED\"` locally. If this command doesn't succeed, your change will not pass CI.\r\n- [x] Push your changes to a branch other than `main`. You will create your PR from that branch.\r\n\r\n# When creating a PR: \r\n\r\n- [x] Fill in the \"Description\" above with clear summary of the changes. This includes:\r\n - [x] If this PR fixes one or more issues, include \"Fixes #\" lines for each issue. \r\n - [x] Provide links to relevant documentation on https://docs.github.com/en/rest where possible. If not including links, explain why not.\r\n- [x] All lines of new code should be covered by tests as reported by code coverage. Any lines that are not covered must have PR comments explaining why they cannot be covered. For example, \"Reaching this particular exception is hard and is not a particular common scenario.\"\r\n- [x] Enable \"Allow edits from maintainers\".\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2185/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/hub4j/github-api/issues/2185/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2184", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2184/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2184/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2184/events", + "html_url": "https://github.com/hub4j/github-api/pull/2184", + "id": 3852498861, + "node_id": "PR_kwDOAAlq-s6_MgOl", + "number": 2184, + "title": "feat: add GHPullRequest.markReadyForReview for draft PRs", + "user": { + "login": "Anonycoders", + "id": 40047636, + "node_id": "MDQ6VXNlcjQwMDQ3NjM2", + "avatar_url": "https://avatars.githubusercontent.com/u/40047636?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Anonycoders", + "html_url": "https://github.com/Anonycoders", + "followers_url": "https://api.github.com/users/Anonycoders/followers", + "following_url": "https://api.github.com/users/Anonycoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Anonycoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Anonycoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Anonycoders/subscriptions", + "organizations_url": "https://api.github.com/users/Anonycoders/orgs", + "repos_url": "https://api.github.com/users/Anonycoders/repos", + "events_url": "https://api.github.com/users/Anonycoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Anonycoders/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 2, + "created_at": "2026-01-25T03:46:29Z", + "updated_at": "2026-02-10T07:51:41Z", + "closed_at": "2026-02-10T07:51:18Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2184", + "html_url": "https://github.com/hub4j/github-api/pull/2184", + "diff_url": "https://github.com/hub4j/github-api/pull/2184.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2184.patch", + "merged_at": "2026-02-10T07:51:18Z" + }, + "body": "# Description\r\n\r\nThis change adds new functionality to allow `marking draft PRs as ready for review` using GraphQL as it's not supported by REST.\r\n\r\nhttps://docs.github.com/en/graphql/reference/mutations#markpullrequestreadyforreview\r\n\r\n# Before submitting a PR:\r\n\r\n- [x] Changes must not break binary backwards compatibility. If you are unclear on how to make the change you think is needed while maintaining backward compatibility, [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Add JavaDocs and other comments explaining the behavior. \r\n- [x] When adding or updating methods that fetch entities, add `@link` JavaDoc entries to the relevant documentation on https://docs.github.com/en/rest . \r\n- [x] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Run `mvn -D enable-ci clean install site \"-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED\"` locally. If this command doesn't succeed, your change will not pass CI.\r\n- [x] Push your changes to a branch other than `main`. You will create your PR from that branch.\r\n\r\n# When creating a PR: \r\n\r\n- [x] Fill in the \"Description\" above with clear summary of the changes. This includes:\r\n - [ ] If this PR fixes one or more issues, include \"Fixes #\" lines for each issue. \r\n - [x] Provide links to relevant documentation on https://docs.github.com/en/rest where possible. If not including links, explain why not.\r\n- [x] All lines of new code should be covered by tests as reported by code coverage. Any lines that are not covered must have PR comments explaining why they cannot be covered. For example, \"Reaching this particular exception is hard and is not a particular common scenario.\"\r\n- [x] Enable \"Allow edits from maintainers\".\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2184/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/hub4j/github-api/issues/2184/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2182", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2182/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2182/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2182/events", + "html_url": "https://github.com/hub4j/github-api/pull/2182", + "id": 3852417004, + "node_id": "PR_kwDOAAlq-s6_MQiJ", + "number": 2182, + "title": "Enable Jackson 3 - Phase 1", + "user": { + "login": "pvillard31", + "id": 11541012, + "node_id": "MDQ6VXNlcjExNTQxMDEy", + "avatar_url": "https://avatars.githubusercontent.com/u/11541012?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/pvillard31", + "html_url": "https://github.com/pvillard31", + "followers_url": "https://api.github.com/users/pvillard31/followers", + "following_url": "https://api.github.com/users/pvillard31/following{/other_user}", + "gists_url": "https://api.github.com/users/pvillard31/gists{/gist_id}", + "starred_url": "https://api.github.com/users/pvillard31/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/pvillard31/subscriptions", + "organizations_url": "https://api.github.com/users/pvillard31/orgs", + "repos_url": "https://api.github.com/users/pvillard31/repos", + "events_url": "https://api.github.com/users/pvillard31/events{/privacy}", + "received_events_url": "https://api.github.com/users/pvillard31/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-01-25T02:13:04Z", + "updated_at": "2026-01-25T03:20:51Z", + "closed_at": "2026-01-25T03:20:35Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2182", + "html_url": "https://github.com/hub4j/github-api/pull/2182", + "diff_url": "https://github.com/hub4j/github-api/pull/2182.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2182.patch", + "merged_at": "2026-01-25T03:20:35Z" + }, + "body": "# Description\r\n\r\nSee discussion in #2173.\r\nThis is a first PR to stay on Jackson 2.x but prepare for Jackson 3 support.\r\n\r\n# Before submitting a PR:\r\n\r\n- [x] Changes must not break binary backwards compatibility. If you are unclear on how to make the change you think is needed while maintaining backward compatibility, [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Add JavaDocs and other comments explaining the behavior. \r\n- [x] When adding or updating methods that fetch entities, add `@link` JavaDoc entries to the relevant documentation on https://docs.github.com/en/rest . \r\n- [x] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Run `mvn -D enable-ci clean install site \"-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED\"` locally. If this command doesn't succeed, your change will not pass CI.\r\n- [x] Push your changes to a branch other than `main`. You will create your PR from that branch.\r\n\r\n# When creating a PR: \r\n\r\n- [x] Fill in the \"Description\" above with clear summary of the changes. This includes:\r\n - [x] If this PR fixes one or more issues, include \"Fixes #\" lines for each issue. \r\n - [x] Provide links to relevant documentation on https://docs.github.com/en/rest where possible. If not including links, explain why not.\r\n- [x] All lines of new code should be covered by tests as reported by code coverage. Any lines that are not covered must have PR comments explaining why they cannot be covered. For example, \"Reaching this particular exception is hard and is not a particular common scenario.\"\r\n- [x] Enable \"Allow edits from maintainers\".\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2182/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/hub4j/github-api/issues/2182/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2180", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2180/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2180/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2180/events", + "html_url": "https://github.com/hub4j/github-api/pull/2180", + "id": 3839900916, + "node_id": "PR_kwDOAAlq-s6-iczp", + "number": 2180, + "title": "fix: adjust enterprise api url for graphql use case", + "user": { + "login": "Anonycoders", + "id": 40047636, + "node_id": "MDQ6VXNlcjQwMDQ3NjM2", + "avatar_url": "https://avatars.githubusercontent.com/u/40047636?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Anonycoders", + "html_url": "https://github.com/Anonycoders", + "followers_url": "https://api.github.com/users/Anonycoders/followers", + "following_url": "https://api.github.com/users/Anonycoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Anonycoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Anonycoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Anonycoders/subscriptions", + "organizations_url": "https://api.github.com/users/Anonycoders/orgs", + "repos_url": "https://api.github.com/users/Anonycoders/repos", + "events_url": "https://api.github.com/users/Anonycoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Anonycoders/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-01-21T20:26:10Z", + "updated_at": "2026-01-24T22:05:14Z", + "closed_at": "2026-01-24T22:05:06Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2180", + "html_url": "https://github.com/hub4j/github-api/pull/2180", + "diff_url": "https://github.com/hub4j/github-api/pull/2180.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2180.patch", + "merged_at": "2026-01-24T22:05:06Z" + }, + "body": "# Description\r\n\r\nFor `GitHub.com` this is `https://api.github.com/graphql`. For `GitHub Enterprise Server`, the GraphQL endpoint is at `/api/graphql (not /api/v3/graphql)`.\r\nThis change makes sure the URL constructed appropriately.\r\n\r\nhttps://docs.github.com/en/enterprise-cloud@latest/graphql/guides/managing-enterprise-accounts#3-setting-up-insomnia-to-use-the-github-graphql-api-with-enterprise-accounts\r\n\r\n# Before submitting a PR:\r\n\r\n- [x] Changes must not break binary backwards compatibility. If you are unclear on how to make the change you think is needed while maintaining backward compatibility, [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [ ] Add JavaDocs and other comments explaining the behavior. \r\n- [ ] When adding or updating methods that fetch entities, add `@link` JavaDoc entries to the relevant documentation on https://docs.github.com/en/rest . \r\n- [x] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Run `mvn -D enable-ci clean install site \"-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED\"` locally. If this command doesn't succeed, your change will not pass CI.\r\n- [x] Push your changes to a branch other than `main`. You will create your PR from that branch.\r\n\r\n# When creating a PR: \r\n\r\n- [x] Fill in the \"Description\" above with clear summary of the changes. This includes:\r\n - [ ] If this PR fixes one or more issues, include \"Fixes #\" lines for each issue. \r\n - [x] Provide links to relevant documentation on https://docs.github.com/en/rest where possible. If not including links, explain why not.\r\n- [x] All lines of new code should be covered by tests as reported by code coverage. Any lines that are not covered must have PR comments explaining why they cannot be covered. For example, \"Reaching this particular exception is hard and is not a particular common scenario.\"\r\n- [x] Enable \"Allow edits from maintainers\".\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2180/reactions", + "total_count": 1, + "+1": 1, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/hub4j/github-api/issues/2180/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2178", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2178/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2178/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2178/events", + "html_url": "https://github.com/hub4j/github-api/pull/2178", + "id": 3774013900, + "node_id": "PR_kwDOAAlq-s67KzV5", + "number": 2178, + "title": "Chore(deps): Bump jjwt.suite.version from 0.12.6 to 0.13.0", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-01-01T02:01:02Z", + "updated_at": "2026-01-24T21:50:50Z", + "closed_at": "2026-01-24T21:50:09Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2178", + "html_url": "https://github.com/hub4j/github-api/pull/2178", + "diff_url": "https://github.com/hub4j/github-api/pull/2178.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2178.patch", + "merged_at": "2026-01-24T21:50:09Z" + }, + "body": "Bumps `jjwt.suite.version` from 0.12.6 to 0.13.0.\nUpdates `io.jsonwebtoken:jjwt-api` from 0.12.6 to 0.13.0\n
\nRelease notes\n

Sourced from io.jsonwebtoken:jjwt-api's releases.

\n
\n

0.13.0

\n

This is the last minor JJWT release branch that will support Java 7.

\n

Any necessary emergency bug fixes will be fixed in subsequent 0.13.x patch releases, but all new development, including Java 8 compatible changes, will be in the next minor (0.14.0) release.

\n

All future JJWT major and minor versions ( 0.14.0 and later) will require Java 8 or later.

\n

What's Changed

\n

This release contains a single change:

\n
    \n
  • The previously private JacksonDeserializer(ObjectMapper objectMapper, Map<String, Class<?>> claimTypeMap) constructor is now public for those that want register a claims type converter on their own specified ObjectMapper instance. Thank you to @​kesrishubham2510 for PR #972. See Issue 914.
  • \n
\n

Full Changelog: https://github.com/jwtk/jjwt/compare/0.12.7...0.13.0

\n

0.12.7

\n

This patch release:

\n
    \n
  • \n

    Adds a new Maven BOM! This is useful for multi-module projects. See Issue 967.

    \n
  • \n
  • \n

    Allows the JwtParserBuilder to have empty nested algorithm collections, effectively disabling the parser's associated feature:

    \n
      \n
    • Emptying the zip() nested collection disables JWT decompression.
    • \n
    • Emptying the sig() nested collection disables JWS mac/signature verification (i.e. all JWSs will be unsupported/rejected).
    • \n
    • Emptying either the enc() or key() nested collections disables JWE decryption (i.e. all JWEs will be unsupported/rejected)
    • \n
    \n

    See Issue 996.

    \n
  • \n
  • \n

    Fixes bug 961 where JwtParserBuilder nested collection builders were not correctly replacing algorithms with the same id.

    \n
  • \n
  • \n

    Ensures a JwkSet's keys collection is no longer entirely secret/redacted by default. This was an overzealous default that was unnecessarily restrictive; the keys collection itself should always be public, and each individual key within should determine which fields should be redacted when printed. See Issue 976.

    \n
  • \n
  • \n

    Improves performance slightly by ensuring all jjwt-api utility methods that create *Builder instances (Jwts.builder(), Jwts.parserBuilder(), Jwks.builder(), etc) no longer use reflection.

    \n

    Instead,static factories are created via reflection only once during initial jjwt-api classloading, and then *Builders are created via standard instantiation using the new operator thereafter. This also benefits certain environments that may not have ideal ClassLoader implementations (e.g. Tomcat in some cases).

    \n

    NOTE: because this changes which classes are loaded via reflection, any environments that must explicitly reference reflective class names (e.g. GraalVM applications) will need to be updated to reflect the new factory class names.

    \n

    See Issue 988.

    \n
  • \n
  • \n

    Upgrades the Gson dependency to 2.11.0

    \n
  • \n
  • \n

    Upgrades the BouncyCastle dependency to 1.78.1

    \n
  • \n
\n

New Contributors

\n\n

Full Changelog: https://github.com/jwtk/jjwt/compare/0.12.6...0.12.7

\n
\n
\n
\nChangelog\n

Sourced from io.jsonwebtoken:jjwt-api's changelog.

\n
\n

0.13.0

\n

This is the last minor JJWT release branch that will support Java 7. Any necessary emergency bug fixes will be fixed in subsequent 0.13.x patch releases, but all new development, including Java 8 compatible changes, will be in the next minor (0.14.0) release.

\n

All future JJWT major and minor versions ( 0.14.0 and later) will require Java 8 or later.

\n

This 0.13.0 minor release has only one change:

\n
    \n
  • The previously private JacksonDeserializer(ObjectMapper objectMapper, Map<String, Class<?>> claimTypeMap) constructor is now public for those that want register a claims\ntype converter on their own specified ObjectMapper instance. See Issue 914.
  • \n
\n

0.12.7

\n

This patch release:

\n
    \n
  • \n

    Adds a new Maven BOM, useful for multi-module projects. See Issue 967.

    \n
  • \n
  • \n

    Allows the JwtParserBuilder to have empty nested algorithm collections, effectively disabling the parser's associated feature:

    \n
      \n
    • Emptying the zip() nested collection disables JWT decompression.
    • \n
    • Emptying the sig() nested collection disables JWS mac/signature verification (i.e. all JWSs will be unsupported/rejected).
    • \n
    • Emptying either the enc() or key() nested collections disables JWE decryption (i.e. all JWEs will be unsupported/rejected)
    • \n
    \n

    See Issue 996.

    \n
  • \n
  • \n

    Fixes bug 961 where JwtParserBuilder nested collection builders were not correctly replacing algorithms with the same id.

    \n
  • \n
  • \n

    Ensures a JwkSet's keys collection is no longer entirely secret/redacted by default. This was an overzealous default that was unnecessarily restrictive; the keys collection itself should always be public, and each individual key within should determine which fields should be redacted when printed. See Issue 976.

    \n
  • \n
  • \n

    Improves performance slightly by ensuring all jjwt-api utility methods that create *Builder instances (Jwts.builder(), Jwts.parserBuilder(), Jwks.builder(), etc) no longer use reflection.

    \n

    Instead,static factories are created via reflection only once during initial jjwt-api classloading, and then *Builders are created via standard instantiation using the new operator thereafter. This also benefits certain environments that may not have ideal ClassLoader implementations (e.g. Tomcat in some cases).

    \n

    NOTE: because this changes which classes are loaded via reflection, any environments that must explicitly reference reflective class names (e.g. GraalVM applications) will need to be updated to reflect the new factory class names.

    \n

    See Issue 988.

    \n
  • \n
  • \n

    Upgrades the Gson dependency to 2.11.0

    \n
  • \n
  • \n

    Upgrades the BouncyCastle dependency to 1.78.1

    \n
  • \n
\n
\n
\n
\nCommits\n\n
\n
\n\nUpdates `io.jsonwebtoken:jjwt-impl` from 0.12.6 to 0.13.0\n
\nRelease notes\n

Sourced from io.jsonwebtoken:jjwt-impl's releases.

\n
\n

0.13.0

\n

This is the last minor JJWT release branch that will support Java 7.

\n

Any necessary emergency bug fixes will be fixed in subsequent 0.13.x patch releases, but all new development, including Java 8 compatible changes, will be in the next minor (0.14.0) release.

\n

All future JJWT major and minor versions ( 0.14.0 and later) will require Java 8 or later.

\n

What's Changed

\n

This release contains a single change:

\n
    \n
  • The previously private JacksonDeserializer(ObjectMapper objectMapper, Map<String, Class<?>> claimTypeMap) constructor is now public for those that want register a claims type converter on their own specified ObjectMapper instance. Thank you to @​kesrishubham2510 for PR #972. See Issue 914.
  • \n
\n

Full Changelog: https://github.com/jwtk/jjwt/compare/0.12.7...0.13.0

\n

0.12.7

\n

This patch release:

\n
    \n
  • \n

    Adds a new Maven BOM! This is useful for multi-module projects. See Issue 967.

    \n
  • \n
  • \n

    Allows the JwtParserBuilder to have empty nested algorithm collections, effectively disabling the parser's associated feature:

    \n
      \n
    • Emptying the zip() nested collection disables JWT decompression.
    • \n
    • Emptying the sig() nested collection disables JWS mac/signature verification (i.e. all JWSs will be unsupported/rejected).
    • \n
    • Emptying either the enc() or key() nested collections disables JWE decryption (i.e. all JWEs will be unsupported/rejected)
    • \n
    \n

    See Issue 996.

    \n
  • \n
  • \n

    Fixes bug 961 where JwtParserBuilder nested collection builders were not correctly replacing algorithms with the same id.

    \n
  • \n
  • \n

    Ensures a JwkSet's keys collection is no longer entirely secret/redacted by default. This was an overzealous default that was unnecessarily restrictive; the keys collection itself should always be public, and each individual key within should determine which fields should be redacted when printed. See Issue 976.

    \n
  • \n
  • \n

    Improves performance slightly by ensuring all jjwt-api utility methods that create *Builder instances (Jwts.builder(), Jwts.parserBuilder(), Jwks.builder(), etc) no longer use reflection.

    \n

    Instead,static factories are created via reflection only once during initial jjwt-api classloading, and then *Builders are created via standard instantiation using the new operator thereafter. This also benefits certain environments that may not have ideal ClassLoader implementations (e.g. Tomcat in some cases).

    \n

    NOTE: because this changes which classes are loaded via reflection, any environments that must explicitly reference reflective class names (e.g. GraalVM applications) will need to be updated to reflect the new factory class names.

    \n

    See Issue 988.

    \n
  • \n
  • \n

    Upgrades the Gson dependency to 2.11.0

    \n
  • \n
  • \n

    Upgrades the BouncyCastle dependency to 1.78.1

    \n
  • \n
\n

New Contributors

\n\n

Full Changelog: https://github.com/jwtk/jjwt/compare/0.12.6...0.12.7

\n
\n
\n
\nChangelog\n

Sourced from io.jsonwebtoken:jjwt-impl's changelog.

\n
\n

0.13.0

\n

This is the last minor JJWT release branch that will support Java 7. Any necessary emergency bug fixes will be fixed in subsequent 0.13.x patch releases, but all new development, including Java 8 compatible changes, will be in the next minor (0.14.0) release.

\n

All future JJWT major and minor versions ( 0.14.0 and later) will require Java 8 or later.

\n

This 0.13.0 minor release has only one change:

\n
    \n
  • The previously private JacksonDeserializer(ObjectMapper objectMapper, Map<String, Class<?>> claimTypeMap) constructor is now public for those that want register a claims\ntype converter on their own specified ObjectMapper instance. See Issue 914.
  • \n
\n

0.12.7

\n

This patch release:

\n
    \n
  • \n

    Adds a new Maven BOM, useful for multi-module projects. See Issue 967.

    \n
  • \n
  • \n

    Allows the JwtParserBuilder to have empty nested algorithm collections, effectively disabling the parser's associated feature:

    \n
      \n
    • Emptying the zip() nested collection disables JWT decompression.
    • \n
    • Emptying the sig() nested collection disables JWS mac/signature verification (i.e. all JWSs will be unsupported/rejected).
    • \n
    • Emptying either the enc() or key() nested collections disables JWE decryption (i.e. all JWEs will be unsupported/rejected)
    • \n
    \n

    See Issue 996.

    \n
  • \n
  • \n

    Fixes bug 961 where JwtParserBuilder nested collection builders were not correctly replacing algorithms with the same id.

    \n
  • \n
  • \n

    Ensures a JwkSet's keys collection is no longer entirely secret/redacted by default. This was an overzealous default that was unnecessarily restrictive; the keys collection itself should always be public, and each individual key within should determine which fields should be redacted when printed. See Issue 976.

    \n
  • \n
  • \n

    Improves performance slightly by ensuring all jjwt-api utility methods that create *Builder instances (Jwts.builder(), Jwts.parserBuilder(), Jwks.builder(), etc) no longer use reflection.

    \n

    Instead,static factories are created via reflection only once during initial jjwt-api classloading, and then *Builders are created via standard instantiation using the new operator thereafter. This also benefits certain environments that may not have ideal ClassLoader implementations (e.g. Tomcat in some cases).

    \n

    NOTE: because this changes which classes are loaded via reflection, any environments that must explicitly reference reflective class names (e.g. GraalVM applications) will need to be updated to reflect the new factory class names.

    \n

    See Issue 988.

    \n
  • \n
  • \n

    Upgrades the Gson dependency to 2.11.0

    \n
  • \n
  • \n

    Upgrades the BouncyCastle dependency to 1.78.1

    \n
  • \n
\n
\n
\n
\nCommits\n\n
\n
\n\nUpdates `io.jsonwebtoken:jjwt-jackson` from 0.12.6 to 0.13.0\n\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2178/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/hub4j/github-api/issues/2178/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2177", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2177/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2177/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2177/events", + "html_url": "https://github.com/hub4j/github-api/pull/2177", + "id": 3774013874, + "node_id": "PR_kwDOAAlq-s67KzVi", + "number": 2177, + "title": "Chore(deps): Bump codecov/codecov-action from 5.5.1 to 5.5.2", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391660, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjYw", + "url": "https://api.github.com/repos/hub4j/github-api/labels/github_actions", + "name": "github_actions", + "color": "000000", + "default": false, + "description": "Pull requests that update Github_actions code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-01-01T02:01:00Z", + "updated_at": "2026-01-23T20:37:39Z", + "closed_at": "2026-01-23T20:36:48Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2177", + "html_url": "https://github.com/hub4j/github-api/pull/2177", + "diff_url": "https://github.com/hub4j/github-api/pull/2177.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2177.patch", + "merged_at": "2026-01-23T20:36:48Z" + }, + "body": "Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.5.1 to 5.5.2.\n
\nRelease notes\n

Sourced from codecov/codecov-action's releases.

\n
\n

v5.5.2

\n

What's Changed

\n\n

New Contributors

\n\n

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.5.1...v5.5.2

\n
\n
\n
\nChangelog\n

Sourced from codecov/codecov-action's changelog.

\n
\n

v5.5.2

\n

What's Changed

\n

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.5.1..v5.5.2

\n
\n
\n
\nCommits\n\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=codecov/codecov-action&package-manager=github_actions&previous-version=5.5.1&new-version=5.5.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2177/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/hub4j/github-api/issues/2177/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2176", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2176/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2176/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2176/events", + "html_url": "https://github.com/hub4j/github-api/pull/2176", + "id": 3774013831, + "node_id": "PR_kwDOAAlq-s67KzU-", + "number": 2176, + "title": "Chore(deps): Bump actions/download-artifact from 6 to 7", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391660, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjYw", + "url": "https://api.github.com/repos/hub4j/github-api/labels/github_actions", + "name": "github_actions", + "color": "000000", + "default": false, + "description": "Pull requests that update Github_actions code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-01-01T02:00:56Z", + "updated_at": "2026-01-24T21:51:17Z", + "closed_at": "2026-01-24T21:50:23Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2176", + "html_url": "https://github.com/hub4j/github-api/pull/2176", + "diff_url": "https://github.com/hub4j/github-api/pull/2176.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2176.patch", + "merged_at": "2026-01-24T21:50:23Z" + }, + "body": "Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.\n
\nRelease notes\n

Sourced from actions/download-artifact's releases.

\n
\n

v7.0.0

\n

v7 - What's new

\n
\n

[!IMPORTANT]\nactions/download-artifact@v7 now runs on Node.js 24 (runs.using: node24) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.

\n
\n

Node.js 24

\n

This release updates the runtime to Node.js 24. v6 had preliminary support for Node 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.

\n

What's Changed

\n\n

New Contributors

\n\n

Full Changelog: https://github.com/actions/download-artifact/compare/v6.0.0...v7.0.0

\n
\n
\n
\nCommits\n
    \n
  • 37930b1 Merge pull request #452 from actions/download-artifact-v7-release
  • \n
  • 72582b9 doc: update readme
  • \n
  • 0d2ec9d chore: release v7.0.0 for Node.js 24 support
  • \n
  • fd7ae8f Merge pull request #451 from actions/fix-storage-blob
  • \n
  • d484700 chore: restore minimatch.dep.yml license file
  • \n
  • 03a8080 chore: remove obsolete dependency license files
  • \n
  • 56fe6d9 chore: update @​actions/artifact license file to 5.0.1
  • \n
  • 8e3ebc4 chore: update package-lock.json with @​actions/artifact@​5.0.1
  • \n
  • 1e3c4b4 fix: update @​actions/artifact to ^5.0.0 for Node.js 24 punycode fix
  • \n
  • 458627d chore: use local @​actions/artifact package for Node.js 24 testing
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/download-artifact&package-manager=github_actions&previous-version=6&new-version=7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2176/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/hub4j/github-api/issues/2176/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2175", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2175/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2175/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2175/events", + "html_url": "https://github.com/hub4j/github-api/pull/2175", + "id": 3774013775, + "node_id": "PR_kwDOAAlq-s67KzUJ", + "number": 2175, + "title": "Chore(deps): Bump actions/upload-artifact from 5 to 6", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391660, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjYw", + "url": "https://api.github.com/repos/hub4j/github-api/labels/github_actions", + "name": "github_actions", + "color": "000000", + "default": false, + "description": "Pull requests that update Github_actions code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-01-01T02:00:52Z", + "updated_at": "2026-01-24T21:51:38Z", + "closed_at": "2026-01-24T21:50:40Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2175", + "html_url": "https://github.com/hub4j/github-api/pull/2175", + "diff_url": "https://github.com/hub4j/github-api/pull/2175.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2175.patch", + "merged_at": "2026-01-24T21:50:40Z" + }, + "body": "Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.\n
\nRelease notes\n

Sourced from actions/upload-artifact's releases.

\n
\n

v6.0.0

\n

v6 - What's new

\n
\n

[!IMPORTANT]\nactions/upload-artifact@v6 now runs on Node.js 24 (runs.using: node24) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.

\n
\n

Node.js 24

\n

This release updates the runtime to Node.js 24. v5 had preliminary support for Node.js 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.

\n

What's Changed

\n\n

Full Changelog: https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0

\n
\n
\n
\nCommits\n
    \n
  • b7c566a Merge pull request #745 from actions/upload-artifact-v6-release
  • \n
  • e516bc8 docs: correct description of Node.js 24 support in README
  • \n
  • ddc45ed docs: update README to correct action name for Node.js 24 support
  • \n
  • 615b319 chore: release v6.0.0 for Node.js 24 support
  • \n
  • 017748b Merge pull request #744 from actions/fix-storage-blob
  • \n
  • 38d4c79 chore: rebuild dist
  • \n
  • 7d27270 chore: add missing license cache files for @​actions/core, @​actions/io, and mi...
  • \n
  • 5f643d3 chore: update license files for @​actions/artifact@​5.0.1 dependencies
  • \n
  • 1df1684 chore: update package-lock.json with @​actions/artifact@​5.0.1
  • \n
  • b5b1a91 fix: update @​actions/artifact to ^5.0.0 for Node.js 24 punycode fix
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/upload-artifact&package-manager=github_actions&previous-version=5&new-version=6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2175/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/hub4j/github-api/issues/2175/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2174", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2174/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2174/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2174/events", + "html_url": "https://github.com/hub4j/github-api/pull/2174", + "id": 3774013768, + "node_id": "PR_kwDOAAlq-s67KzUC", + "number": 2174, + "title": "Chore(deps-dev): Bump org.mockito:mockito-core from 5.20.0 to 5.21.0", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 2, + "created_at": "2026-01-01T02:00:51Z", + "updated_at": "2026-01-24T21:51:51Z", + "closed_at": "2026-01-24T21:51:05Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2174", + "html_url": "https://github.com/hub4j/github-api/pull/2174", + "diff_url": "https://github.com/hub4j/github-api/pull/2174.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2174.patch", + "merged_at": "2026-01-24T21:51:05Z" + }, + "body": "Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.20.0 to 5.21.0.\n
\nRelease notes\n

Sourced from org.mockito:mockito-core's releases.

\n
\n

v5.21.0

\n

Changelog generated by Shipkit Changelog Gradle Plugin

\n

5.21.0

\n\n
\n
\n
\nCommits\n
    \n
  • 09d2230 Bump graalvm/setup-graalvm from 1.4.3 to 1.4.4 (#3768)
  • \n
  • df3e0cc Bump graalvm/setup-graalvm from 1.4.2 to 1.4.3 (#3767)
  • \n
  • 04a6e9f Bump actions/checkout from 5 to 6 (#3765)
  • \n
  • 756a3cf Add description of matchers to potential mismatch (#3760)
  • \n
  • 58ba445 Forbid mocking WeakReference with inline mock maker (#3759)
  • \n
  • 966d600 Bump actions/upload-artifact from 4 to 5 (#3756)
  • \n
  • 632bf7b Bump graalvm/setup-graalvm from 1.4.1 to 1.4.2 (#3755)
  • \n
  • 8564b43 Fix primitives support in GenericArrayReturnType for Android (#3753)
  • \n
  • bf3a809 Bump graalvm/setup-graalvm from 1.4.0 to 1.4.1 (#3744)
  • \n
  • cffddd4 Bump gradle/actions from 4 to 5 (#3743)
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.mockito:mockito-core&package-manager=maven&previous-version=5.20.0&new-version=5.21.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2174/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/hub4j/github-api/issues/2174/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2170", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2170/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2170/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2170/events", + "html_url": "https://github.com/hub4j/github-api/pull/2170", + "id": 3678909093, + "node_id": "PR_kwDOAAlq-s62PDSN", + "number": 2170, + "title": "Chore(deps): Bump actions/checkout from 5 to 6", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391660, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjYw", + "url": "https://api.github.com/repos/hub4j/github-api/labels/github_actions", + "name": "github_actions", + "color": "000000", + "default": false, + "description": "Pull requests that update Github_actions code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-12-01T02:10:52Z", + "updated_at": "2025-12-24T01:52:41Z", + "closed_at": "2025-12-24T01:52:29Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2170", + "html_url": "https://github.com/hub4j/github-api/pull/2170", + "diff_url": "https://github.com/hub4j/github-api/pull/2170.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2170.patch", + "merged_at": "2025-12-24T01:52:29Z" + }, + "body": "Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.\n
\nRelease notes\n

Sourced from actions/checkout's releases.

\n
\n

v6.0.0

\n

What's Changed

\n\n

Full Changelog: https://github.com/actions/checkout/compare/v5.0.0...v6.0.0

\n

v6-beta

\n

What's Changed

\n

Updated persist-credentials to store the credentials under $RUNNER_TEMP instead of directly in the local git config.

\n

This requires a minimum Actions Runner version of v2.329.0 to access the persisted credentials for Docker container action scenarios.

\n

v5.0.1

\n

What's Changed

\n\n

Full Changelog: https://github.com/actions/checkout/compare/v5...v5.0.1

\n
\n
\n
\nChangelog\n

Sourced from actions/checkout's changelog.

\n
\n

Changelog

\n

V6.0.0

\n\n

V5.0.1

\n\n

V5.0.0

\n\n

V4.3.1

\n\n

V4.3.0

\n\n

v4.2.2

\n\n

v4.2.1

\n\n

v4.2.0

\n\n

v4.1.7

\n\n

v4.1.6

\n\n

v4.1.5

\n\n\n
\n

... (truncated)

\n
\n
\nCommits\n\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=5&new-version=6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2170/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/hub4j/github-api/issues/2170/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2169", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2169/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2169/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2169/events", + "html_url": "https://github.com/hub4j/github-api/pull/2169", + "id": 3678905792, + "node_id": "PR_kwDOAAlq-s62PCjL", + "number": 2169, + "title": "Chore(deps): Bump com.github.spotbugs:spotbugs-maven-plugin from 4.9.8.1 to 4.9.8.2", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-12-01T02:09:11Z", + "updated_at": "2025-12-24T01:54:46Z", + "closed_at": "2025-12-24T01:54:19Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2169", + "html_url": "https://github.com/hub4j/github-api/pull/2169", + "diff_url": "https://github.com/hub4j/github-api/pull/2169.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2169.patch", + "merged_at": "2025-12-24T01:54:19Z" + }, + "body": "Bumps [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) from 4.9.8.1 to 4.9.8.2.\n
\nRelease notes\n

Sourced from com.github.spotbugs:spotbugs-maven-plugin's releases.

\n
\n

Spotbugs Maven Plugin 4.9.8.2

\n\n
\n
\n
\nCommits\n
    \n
  • a03feda [maven-release-plugin] prepare release spotbugs-maven-plugin-4.9.8.2
  • \n
  • 1c8063d [gha] Update actions
  • \n
  • f59d628 Merge pull request #1265 from spotbugs/renovate/actions-checkout-6.x
  • \n
  • 1c232fb chore(deps): update actions/checkout action to v6
  • \n
  • 436be13 Merge pull request #1263 from spotbugs/renovate/actions-checkout-digest
  • \n
  • 0708203 Merge pull request #1264 from spotbugs/renovate/github-codeql-action-digest
  • \n
  • fcd2d1b chore(deps): update github/codeql-action digest to e12f017
  • \n
  • 7c54b5b chore(deps): update actions/checkout digest to 93cb6ef
  • \n
  • 79d724e Merge pull request #1262 from spotbugs/renovate/lang3.version
  • \n
  • b9bbed3 fix(deps): update dependency org.apache.commons:commons-lang3 to v3.20.0
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.github.spotbugs:spotbugs-maven-plugin&package-manager=maven&previous-version=4.9.8.1&new-version=4.9.8.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2169/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/hub4j/github-api/issues/2169/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2168", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2168/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2168/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2168/events", + "html_url": "https://github.com/hub4j/github-api/pull/2168", + "id": 3678905639, + "node_id": "PR_kwDOAAlq-s62PChC", + "number": 2168, + "title": "Chore(deps): Bump spring.boot.version from 3.4.5 to 4.0.0", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-12-01T02:09:06Z", + "updated_at": "2026-02-01T02:02:44Z", + "closed_at": "2026-02-01T02:02:43Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2168", + "html_url": "https://github.com/hub4j/github-api/pull/2168", + "diff_url": "https://github.com/hub4j/github-api/pull/2168.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2168.patch", + "merged_at": null + }, + "body": "Bumps `spring.boot.version` from 3.4.5 to 4.0.0.\nUpdates `org.springframework.boot:spring-boot-dependencies` from 3.4.5 to 4.0.0\n
\nRelease notes\n

Sourced from org.springframework.boot:spring-boot-dependencies's releases.

\n
\n

v4.0.0

\n

Full release notes for Spring Boot 4.0 are available on the wiki. There is also a migration guide to help you upgrade from Spring Boot 3.5.

\n

:star: New Features

\n
    \n
  • Change tomcat and jetty runtime modules to starters #48175
  • \n
  • Rename spring-boot-kotlin-serialization to align with the name of the Kotlinx module that it pulls in #48076
  • \n
\n

:lady_beetle: Bug Fixes

\n
    \n
  • Error properties are a general web concern and should not be located beneath server.* #48201
  • \n
  • With both Jackson 2 and 3 on the classpath, @JsonTest fails due to duplicate jacksonTesterFactoryBean #48198
  • \n
  • Gradle war task does not exclude starter POMs from lib-provided #48197
  • \n
  • spring.test.webclient.mockrestserviceserver.enabled is not aligned with its module's name #48193
  • \n
  • SslMeterBinder doesn't register metrics for dynamically added bundles if no bundles exist at bind time #48182
  • \n
  • Properties bound in the child management context ignore the parent's environment prefix #48177
  • \n
  • ssl.chain.expiry metrics doesn't update for dynamically registered SSL bundles #48171
  • \n
  • Starter for spring-boot-micrometer-metrics is missing #48161
  • \n
  • Elasticsearch client's sniffer functionality should not be enabled by default #48155
  • \n
  • spring-boot-starter-elasticsearch should depend on elasticsearch-java #48141
  • \n
  • Auto-configuration exclusions are checked using a different class loader to the one that loads auto-configuration classes #48132
  • \n
  • New arm64 macbooks fail to bootBuildImage due to incorrect platform image #48128
  • \n
  • Properties for configuring an isolated JsonMapper or ObjectMapper are incorrectly named #48116
  • \n
  • Buildpack fails with recent Docker installs due to hardcoded version in URL #48103
  • \n
  • Image building may fail when specifying a platform if an image has already been built with a different platform #48099
  • \n
  • Default values of Kotlinx Serialization JSON configuration properties are not documented #48097
  • \n
  • Custom XML converters should override defaults in HttpMessageConverters #48096
  • \n
  • Kotlin serialization is used too aggressively when other JSON libraries are available #48070
  • \n
  • PortInUseException incorrectly thrown on failure to bind port due to Netty IP misconfiguration #48059
  • \n
  • Auto-configured JCacheMetrics cannot be customized #48057
  • \n
  • WebSecurityCustomizer beans are excluded by WebMvcTest #48055
  • \n
  • Deprecated EnvironmentPostProcessor does not resolve arguments #48047
  • \n
  • RetryPolicySettings should refer to maxRetries, not maxAttempts #48023
  • \n
  • Devtools Restarter does not work with a parameterless main method #47996
  • \n
  • Dependency management for Kafka should not manage Scala 2.12 libraries #47991
  • \n
  • spring-boot-mail should depend on jakarta.mail:jakarta.mail-api and org.eclipse.angus:angus-mail instead of org.eclipse.angus:jakarta.mail #47983
  • \n
  • spring-boot-starter-data-mongodb-reactive has dependency on reactor-test #47982
  • \n
  • Support for ReactiveElasticsearchClient is in the wrong module #47848
  • \n
\n

:notebook_with_decorative_cover: Documentation

\n
    \n
  • Removed property spring.test.webclient.register-rest-template is still documented #48199
  • \n
  • Mention support for detecting AWS ECS in "Deploying to the Cloud" #48170
  • \n
  • Revise AWS section of "Deploying to the Cloud" in reference manual #48163
  • \n
  • Fix typo in PortInUseException Javadoc #48134
  • \n
  • Correct section about required setters in "Type-safe Configuration Properties" #48131
  • \n
  • Use since attribute in configuration properties deprecation consistently #48122
  • \n
  • Document EndpointJsonMapper and management.endpoints.jackson.isolated-json-mapper #48115
  • \n
  • Document support for configuring servlet context init parameters using properties #48112
  • \n
  • Some configuration properties are not documented in the appendix #48095
  • \n
\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 1c0e08b Release v4.0.0
  • \n
  • 3487928 Merge branch '3.5.x'
  • \n
  • 29b8e96 Switch make-default in preparation for Spring Boot 4.0.0
  • \n
  • 88da0dd Merge branch '3.5.x'
  • \n
  • 56feeaa Next development version (v3.5.9-SNAPSHOT)
  • \n
  • 3becdc7 Move server.error properties to spring.web.error
  • \n
  • 2b30632 Merge branch '3.5.x'
  • \n
  • 4f03b44 Merge branch '3.4.x' into 3.5.x
  • \n
  • 3d15c13 Next development version (v3.4.13-SNAPSHOT)
  • \n
  • dc140df Upgrade to Spring Framework 7.0.1
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\nUpdates `org.springframework.boot:spring-boot-maven-plugin` from 3.4.5 to 4.0.0\n
\nRelease notes\n

Sourced from org.springframework.boot:spring-boot-maven-plugin's releases.

\n
\n

v4.0.0

\n

Full release notes for Spring Boot 4.0 are available on the wiki. There is also a migration guide to help you upgrade from Spring Boot 3.5.

\n

:star: New Features

\n
    \n
  • Change tomcat and jetty runtime modules to starters #48175
  • \n
  • Rename spring-boot-kotlin-serialization to align with the name of the Kotlinx module that it pulls in #48076
  • \n
\n

:lady_beetle: Bug Fixes

\n
    \n
  • Error properties are a general web concern and should not be located beneath server.* #48201
  • \n
  • With both Jackson 2 and 3 on the classpath, @JsonTest fails due to duplicate jacksonTesterFactoryBean #48198
  • \n
  • Gradle war task does not exclude starter POMs from lib-provided #48197
  • \n
  • spring.test.webclient.mockrestserviceserver.enabled is not aligned with its module's name #48193
  • \n
  • SslMeterBinder doesn't register metrics for dynamically added bundles if no bundles exist at bind time #48182
  • \n
  • Properties bound in the child management context ignore the parent's environment prefix #48177
  • \n
  • ssl.chain.expiry metrics doesn't update for dynamically registered SSL bundles #48171
  • \n
  • Starter for spring-boot-micrometer-metrics is missing #48161
  • \n
  • Elasticsearch client's sniffer functionality should not be enabled by default #48155
  • \n
  • spring-boot-starter-elasticsearch should depend on elasticsearch-java #48141
  • \n
  • Auto-configuration exclusions are checked using a different class loader to the one that loads auto-configuration classes #48132
  • \n
  • New arm64 macbooks fail to bootBuildImage due to incorrect platform image #48128
  • \n
  • Properties for configuring an isolated JsonMapper or ObjectMapper are incorrectly named #48116
  • \n
  • Buildpack fails with recent Docker installs due to hardcoded version in URL #48103
  • \n
  • Image building may fail when specifying a platform if an image has already been built with a different platform #48099
  • \n
  • Default values of Kotlinx Serialization JSON configuration properties are not documented #48097
  • \n
  • Custom XML converters should override defaults in HttpMessageConverters #48096
  • \n
  • Kotlin serialization is used too aggressively when other JSON libraries are available #48070
  • \n
  • PortInUseException incorrectly thrown on failure to bind port due to Netty IP misconfiguration #48059
  • \n
  • Auto-configured JCacheMetrics cannot be customized #48057
  • \n
  • WebSecurityCustomizer beans are excluded by WebMvcTest #48055
  • \n
  • Deprecated EnvironmentPostProcessor does not resolve arguments #48047
  • \n
  • RetryPolicySettings should refer to maxRetries, not maxAttempts #48023
  • \n
  • Devtools Restarter does not work with a parameterless main method #47996
  • \n
  • Dependency management for Kafka should not manage Scala 2.12 libraries #47991
  • \n
  • spring-boot-mail should depend on jakarta.mail:jakarta.mail-api and org.eclipse.angus:angus-mail instead of org.eclipse.angus:jakarta.mail #47983
  • \n
  • spring-boot-starter-data-mongodb-reactive has dependency on reactor-test #47982
  • \n
  • Support for ReactiveElasticsearchClient is in the wrong module #47848
  • \n
\n

:notebook_with_decorative_cover: Documentation

\n
    \n
  • Removed property spring.test.webclient.register-rest-template is still documented #48199
  • \n
  • Mention support for detecting AWS ECS in "Deploying to the Cloud" #48170
  • \n
  • Revise AWS section of "Deploying to the Cloud" in reference manual #48163
  • \n
  • Fix typo in PortInUseException Javadoc #48134
  • \n
  • Correct section about required setters in "Type-safe Configuration Properties" #48131
  • \n
  • Use since attribute in configuration properties deprecation consistently #48122
  • \n
  • Document EndpointJsonMapper and management.endpoints.jackson.isolated-json-mapper #48115
  • \n
  • Document support for configuring servlet context init parameters using properties #48112
  • \n
  • Some configuration properties are not documented in the appendix #48095
  • \n
\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 1c0e08b Release v4.0.0
  • \n
  • 3487928 Merge branch '3.5.x'
  • \n
  • 29b8e96 Switch make-default in preparation for Spring Boot 4.0.0
  • \n
  • 88da0dd Merge branch '3.5.x'
  • \n
  • 56feeaa Next development version (v3.5.9-SNAPSHOT)
  • \n
  • 3becdc7 Move server.error properties to spring.web.error
  • \n
  • 2b30632 Merge branch '3.5.x'
  • \n
  • 4f03b44 Merge branch '3.4.x' into 3.5.x
  • \n
  • 3d15c13 Next development version (v3.4.13-SNAPSHOT)
  • \n
  • dc140df Upgrade to Spring Framework 7.0.1
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
\n\n> **Note**\n> Automatic rebases have been disabled on this pull request as it has been open for over 30 days.\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2168/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/hub4j/github-api/issues/2168/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2167", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2167/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2167/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2167/events", + "html_url": "https://github.com/hub4j/github-api/pull/2167", + "id": 3678905236, + "node_id": "PR_kwDOAAlq-s62PCbD", + "number": 2167, + "title": "Chore(deps-dev): Bump org.mockito:mockito-core from 5.16.1 to 5.20.0", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-12-01T02:08:53Z", + "updated_at": "2025-12-24T01:54:57Z", + "closed_at": "2025-12-24T01:54:37Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2167", + "html_url": "https://github.com/hub4j/github-api/pull/2167", + "diff_url": "https://github.com/hub4j/github-api/pull/2167.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2167.patch", + "merged_at": "2025-12-24T01:54:37Z" + }, + "body": "Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.16.1 to 5.20.0.\n
\nRelease notes\n

Sourced from org.mockito:mockito-core's releases.

\n
\n

v5.20.0

\n

Changelog generated by Shipkit Changelog Gradle Plugin

\n

5.20.0

\n\n

v5.19.0

\n

Changelog generated by Shipkit Changelog Gradle Plugin

\n

5.19.0

\n\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 3a1a19e Add support for generic types in MockedConstruction and MockedStatic (#3729)
  • \n
  • f3c957a Bump org.assertj:assertj-core from 3.27.4 to 3.27.5 (#3730)
  • \n
  • 3cfbd42 Bump graalvm/setup-graalvm from 1.3.6 to 1.3.7 (#3725)
  • \n
  • 6f9a04b Bump com.gradle.develocity from 4.1.1 to 4.2 (#3726)
  • \n
  • c75dfb8 Bump org.eclipse.platform:org.eclipse.osgi from 3.23.100 to 3.23.200 (#3720)
  • \n
  • 54474fa Bump graalvm/setup-graalvm from 1.3.5 to 1.3.6 (#3719)
  • \n
  • bc06f21 Use Assume.assumeThat for SequencedCollection tests (#3711)
  • \n
  • a10aed0 Bump actions/setup-java from 4 to 5 (#3715)
  • \n
  • 37bb3e5 Fix metadata generation on GraalVM (#3710)
  • \n
  • ef2fd6f Bump com.gradle.develocity from 4.1 to 4.1.1 (#3713)
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.mockito:mockito-core&package-manager=maven&previous-version=5.16.1&new-version=5.20.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2167/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/hub4j/github-api/issues/2167/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2164", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2164/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2164/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2164/events", + "html_url": "https://github.com/hub4j/github-api/pull/2164", + "id": 3577024168, + "node_id": "PR_kwDOAAlq-s6w8R4i", + "number": 2164, + "title": "Chore(deps): Bump com.squareup.okhttp3:okhttp from 4.12.0 to 5.3.0", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-11-01T02:02:30Z", + "updated_at": "2025-12-01T02:11:43Z", + "closed_at": "2025-12-01T02:11:42Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2164", + "html_url": "https://github.com/hub4j/github-api/pull/2164", + "diff_url": "https://github.com/hub4j/github-api/pull/2164.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2164.patch", + "merged_at": null + }, + "body": "Bumps [com.squareup.okhttp3:okhttp](https://github.com/square/okhttp) from 4.12.0 to 5.3.0.\n
\nChangelog\n

Sourced from com.squareup.okhttp3:okhttp's changelog.

\n
\n

Version 5.3.0

\n

2025-10-30

\n
    \n
  • \n

    New: Add tags to Call, including computable tags. Use this to attach application-specific\nmetadata to a Call in an EventListener or Interceptor. The tag can be read in any other\nEventListener or Interceptor.

    \n
      override fun intercept(chain: Interceptor.Chain): Response {\n    chain.call().tag(MyAnalyticsTag::class) {\n      MyAnalyticsTag(...)\n    }\n
    return chain.proceed(chain.request())\n
    \n

    }\n

    \n
  • \n
  • \n

    New: Support request bodies on HTTP/1.1 connection upgrades.

    \n
  • \n
  • \n

    New: EventListener.plus() makes it easier to observe events in multiple listeners.

    \n
  • \n
  • \n

    Fix: Don't spam logs with ‘Method isLoggable in android.util.Log not mocked.’ when using\nOkHttp in Robolectric and Paparazzi tests.

    \n
  • \n
  • \n

    Upgrade: [Kotlin 2.2.21][kotlin_2_2_21].

    \n
  • \n
  • \n

    Upgrade: [Okio 3.16.2][okio_3_16_2].

    \n
  • \n
  • \n

    Upgrade: [ZSTD-KMP 0.4.0][zstd_kmp_0_4_0]. This update fixes a bug that caused APKs to fail\n[16 KB ELF alignment checks][elf_alignment].

    \n
  • \n
\n

Version 5.2.1

\n

2025-10-09

\n
    \n
  • \n

    Fix: Don't crash when calling Socket.shutdownOutput() or shutdownInput() on an SSLSocket\non Android API 21 through 23. This method throws an UnsupportedOperationException, so we now\ncatch that and close the underlying stream instead.

    \n
  • \n
  • \n

    Upgrade: [Okio 3.16.1][okio_3_16_1].

    \n
  • \n
\n

Version 5.2.0

\n

2025-10-07

\n
    \n
  • \n

    New: Support [HTTP 101] responses with Response.socket. This mechanism is only supported on\nHTTP/1.1. We also reimplemented our websocket client to use this new mechanism.

    \n
  • \n
  • \n

    New: The okhttp-zstd module negotiates [Zstandard (zstd)][zstd] compression with servers that\nsupport it. It integrates a new (unstable) [ZSTD-KMP] library, also from Square. Enable it like\nthis:

    \n
  • \n
\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 0960b47 Prepare for release 5.3.0.
  • \n
  • bfb24eb Support Request Bodies on HTTP1.1 Connection Upgrades (#9159)
  • \n
  • cf4a864 Update Gradle to v9.2.0 (#9171)
  • \n
  • 4e7dbec Update dependency com.puppycrawl.tools:checkstyle to v12.1.1 (#9169)
  • \n
  • 0470853 Add tags to calls, including computable tags (#9168)
  • \n
  • 2b70b39 Catch UnsatisfiedLinkError in AndroidLog (#9137)
  • \n
  • 3573555 Update dependency com.github.jnr:jnr-unixsocket to v0.38.24 (#9166)
  • \n
  • af8cf30 Update actions/upload-artifact action to v5 (#9167)
  • \n
  • 478e99c Build an computeIfAbsent() mechanism for tags (#9165)
  • \n
  • d393c86 Use Tags in okhttp3.Request (#9164)
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.squareup.okhttp3:okhttp&package-manager=maven&previous-version=4.12.0&new-version=5.3.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
\n\n> **Note**\n> Automatic rebases have been disabled on this pull request as it has been open for over 30 days.\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2164/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/hub4j/github-api/issues/2164/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2163", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2163/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2163/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2163/events", + "html_url": "https://github.com/hub4j/github-api/pull/2163", + "id": 3577021215, + "node_id": "PR_kwDOAAlq-s6w8RP2", + "number": 2163, + "title": "Chore(deps): Bump org.apache.maven.plugins:maven-javadoc-plugin from 3.11.2 to 3.12.0", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-11-01T02:01:00Z", + "updated_at": "2025-11-26T17:03:35Z", + "closed_at": "2025-11-26T17:03:26Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2163", + "html_url": "https://github.com/hub4j/github-api/pull/2163", + "diff_url": "https://github.com/hub4j/github-api/pull/2163.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2163.patch", + "merged_at": "2025-11-26T17:03:26Z" + }, + "body": "Bumps [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.11.2 to 3.12.0.\n
\nRelease notes\n

Sourced from org.apache.maven.plugins:maven-javadoc-plugin's releases.

\n
\n

3.12.0

\n\n

:boom: Breaking changes

\n\n

🐛 Bug Fixes

\n\n

đŸ‘ģ Maintenance

\n\n

đŸ“Ļ Dependency updates

\n\n

3.11.3

\n\n

🚨 Removed

\n\n

🚀 New features and improvements

\n\n

🐛 Bug Fixes

\n\n

📝 Documentation updates

\n\n

đŸ‘ģ Maintenance

\n\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 2a06bed [maven-release-plugin] prepare release maven-javadoc-plugin-3.12.0
  • \n
  • a71ecf9 bump version 3.12.0-SNAPSHOT
  • \n
  • 88f2b71 [maven-release-plugin] prepare for next development iteration
  • \n
  • 7e18956 [maven-release-plugin] prepare release maven-javadoc-plugin-3.11.4
  • \n
  • c11b76c In legacyMode, don't use -sourcepath, unless excludePackageNames is not empty...
  • \n
  • bc9904b remove fix mojo (#1263)
  • \n
  • f310135 Fix package {...} does not exist in legacyMode (#1243)
  • \n
  • c8270f9 detectOfflineLinks is now false per default for all jar mojo issue #1258 ...
  • \n
  • 953e609 Delete flaky test (#1260)
  • \n
  • 2bba7a4 Bump org.codehaus.mojo:mrm-maven-plugin from 1.6.0 to 1.7.0
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.apache.maven.plugins:maven-javadoc-plugin&package-manager=maven&previous-version=3.11.2&new-version=3.12.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2163/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/hub4j/github-api/issues/2163/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2162", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2162/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2162/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2162/events", + "html_url": "https://github.com/hub4j/github-api/pull/2162", + "id": 3577021147, + "node_id": "PR_kwDOAAlq-s6w8RPA", + "number": 2162, + "title": "Chore(deps): Bump com.github.spotbugs:spotbugs-maven-plugin from 4.9.3.0 to 4.9.8.1", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-11-01T02:00:56Z", + "updated_at": "2025-11-26T17:03:57Z", + "closed_at": "2025-11-26T17:03:45Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2162", + "html_url": "https://github.com/hub4j/github-api/pull/2162", + "diff_url": "https://github.com/hub4j/github-api/pull/2162.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2162.patch", + "merged_at": "2025-11-26T17:03:45Z" + }, + "body": "Bumps [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) from 4.9.3.0 to 4.9.8.1.\n
\nRelease notes\n

Sourced from com.github.spotbugs:spotbugs-maven-plugin's releases.

\n
\n

Spotbugs Maven Plugin 4.9.8.1

\n

Bug fix with SpotbugsInfo.EOF error (was meant to be SpotbugsInfo.EOL).

\n

Spotbugs Maven Plugin 4.9.8.0

\n

Bug fix release supporting spotbugs 4.9.8.

\n

Spotbugs Maven Plugin 4.9.7.0

\n\n

Spotbugs Maven Plugin 4.9.6.0

\n
    \n
  • Supports spotbugs 4.9.6
  • \n
  • note: 4.9.5 had a defect with detection of jakarta in servlets that was unexpected and quickly patched for this release.
  • \n
\n

Spotbugs Maven Plugin 4.9.5.0

\n
    \n
  • Support spotbugs 4.9.5
  • \n
\n

Spotbugs Maven Plugin 4.9.4.2

\n

Consumer

\n
    \n
  • Add support for 'chooseVisitors'
  • \n
  • Minor code cleanup
  • \n
  • Still supports spotbugs 4.9.4
  • \n
\n

Producer

\n
    \n
  • Remove add opens from jvm.config as no longer needed
  • \n
\n

Spotbugs Maven Plugin 4.9.4.1

\n

Consumer

\n
    \n
  • Cleanup readme to better support plugin
  • \n
  • Dropped direct usage of plexus utils and commons io
  • \n
  • Groovy 5 now run engine
  • \n
  • Correct issue since 4.9.2.0 resulting in most runs getting spotbugs.html file incorrectly. This has been refactored to restore doxia 1 overrides to produce xml report only when not running in site lifecycle
  • \n
  • Correct defects with handling of various files on disk such as exclusion filters that were introduced into 4.9.4.0. Integration tests have been applied to prevent future regression.
  • \n
  • Commons io fileutils replaced by files.walk with detailed output moved to debug collection only rather than all runs
  • \n
  • Normalization of path to linux style
  • \n
  • Any regex usage is now precompiled
  • \n
  • Use re-entrant lock for source indexer
  • \n
  • Correct locale usage to use default if not given
  • \n
  • Block doctype and XXE when processing xml files
  • \n
  • Cleanup some fields from resources and in code never used
  • \n
\n

Producer

\n
    \n
  • Pin versions of github actions tools
  • \n
  • Run maven 3.6.3 integration test on windows to get more broad support
  • \n
  • Run maven integration test on mac to get more broad support
  • \n
  • Maven 4 integration tests will continue on linux
  • \n
  • Fix maven wrapper perceived path traversal issue
  • \n
  • Corrections to invoker to re-establish integration test verification's
  • \n
  • Fix bugs in integration tests
  • \n
\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 8eb6aa9 [maven-release-plugin] prepare release spotbugs-maven-plugin-4.9.8.1
  • \n
  • 4ff769f Fix: Correct reported issue with 'EOF' where it should be 'EOL'
  • \n
  • c210782 Merge pull request #1241 from spotbugs/renovate/execpluginversion
  • \n
  • 662fa1e Update dependency org.codehaus.mojo:exec-maven-plugin to v3.6.2
  • \n
  • 8cd9648 [maven-release-plugin] prepare for next development iteration
  • \n
  • d8d4c69 [maven-release-plugin] prepare release spotbugs-maven-plugin-4.9.8.0
  • \n
  • 52cdf26 [ci] Add note about pom entries to update for testing upstream master
  • \n
  • 9b8e387 [pom] Prepare for 4.9.8 release
  • \n
  • 0a8ac5a Merge pull request #1238 from spotbugs/renovate/github-codeql-action-digest
  • \n
  • 4b02d8d Merge pull request #1240 from spotbugs/renovate/spotbugs.version
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.github.spotbugs:spotbugs-maven-plugin&package-manager=maven&previous-version=4.9.3.0&new-version=4.9.8.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2162/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/hub4j/github-api/issues/2162/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2161", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2161/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2161/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2161/events", + "html_url": "https://github.com/hub4j/github-api/pull/2161", + "id": 3577021087, + "node_id": "PR_kwDOAAlq-s6w8RON", + "number": 2161, + "title": "Chore(deps): Bump actions/upload-artifact from 4 to 5", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391660, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjYw", + "url": "https://api.github.com/repos/hub4j/github-api/labels/github_actions", + "name": "github_actions", + "color": "000000", + "default": false, + "description": "Pull requests that update Github_actions code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-11-01T02:00:52Z", + "updated_at": "2025-11-12T23:02:35Z", + "closed_at": "2025-11-12T23:01:16Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2161", + "html_url": "https://github.com/hub4j/github-api/pull/2161", + "diff_url": "https://github.com/hub4j/github-api/pull/2161.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2161.patch", + "merged_at": "2025-11-12T23:01:16Z" + }, + "body": "Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.\n
\nRelease notes\n

Sourced from actions/upload-artifact's releases.

\n
\n

v5.0.0

\n

What's Changed

\n

BREAKING CHANGE: this update supports Node v24.x. This is not a breaking change per-se but we're treating it as such.

\n\n

New Contributors

\n\n

Full Changelog: https://github.com/actions/upload-artifact/compare/v4...v5.0.0

\n

v4.6.2

\n

What's Changed

\n\n

New Contributors

\n\n

Full Changelog: https://github.com/actions/upload-artifact/compare/v4...v4.6.2

\n

v4.6.1

\n

What's Changed

\n\n

Full Changelog: https://github.com/actions/upload-artifact/compare/v4...v4.6.1

\n

v4.6.0

\n

What's Changed

\n\n

Full Changelog: https://github.com/actions/upload-artifact/compare/v4...v4.6.0

\n

v4.5.0

\n

What's Changed

\n\n

New Contributors

\n\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 330a01c Merge pull request #734 from actions/danwkennedy/prepare-5.0.0
  • \n
  • 03f2824 Update github.dep.yml
  • \n
  • 905a1ec Prepare v5.0.0
  • \n
  • 2d9f9cd Merge pull request #725 from patrikpolyak/patch-1
  • \n
  • 9687587 Merge branch 'main' into patch-1
  • \n
  • 2848b2c Merge pull request #727 from danwkennedy/patch-1
  • \n
  • 9b51177 Spell out the first use of GHES
  • \n
  • cd231ca Update GHES guidance to include reference to Node 20 version
  • \n
  • de65e23 Merge pull request #712 from actions/nebuk89-patch-1
  • \n
  • 8747d8c Update README.md
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/upload-artifact&package-manager=github_actions&previous-version=4&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2161/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/hub4j/github-api/issues/2161/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2160", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2160/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2160/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2160/events", + "html_url": "https://github.com/hub4j/github-api/pull/2160", + "id": 3577021065, + "node_id": "PR_kwDOAAlq-s6w8RN7", + "number": 2160, + "title": "Chore(deps-dev): Bump com.google.code.gson:gson from 2.12.1 to 2.13.2", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-11-01T02:00:51Z", + "updated_at": "2025-11-12T23:03:51Z", + "closed_at": "2025-11-12T23:02:36Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2160", + "html_url": "https://github.com/hub4j/github-api/pull/2160", + "diff_url": "https://github.com/hub4j/github-api/pull/2160.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2160.patch", + "merged_at": "2025-11-12T23:02:36Z" + }, + "body": "Bumps [com.google.code.gson:gson](https://github.com/google/gson) from 2.12.1 to 2.13.2.\n
\nRelease notes\n

Sourced from com.google.code.gson:gson's releases.

\n
\n

Gson 2.13.2

\n

The main changes in this release are just newer dependencies.

\n

What's Changed

\n\n

New Contributors

\n\n

Full Changelog: https://github.com/google/gson/compare/gson-parent-2.13.1...gson-parent-2.13.2

\n

Gson 2.13.1

\n

What's Changed

\n\n

New Contributors

\n\n

Full Changelog: https://github.com/google/gson/compare/gson-parent-2.13.0...gson-parent-2.13.1

\n

Gson 2.13.0

\n

What's Changed

\n
    \n
  • \n

    A bug in deserializing collections has been fixed. Previously, if you did something like this:

    \n
    gson.fromJson(jsonString, new TypeToken<ImmutableList<String>>() {})\n
    \n

    then the inferred type would be ImmutableList<String>, but Gson actually gave you an ArrayList<String>. Usually that would lead to an immediate ClassCastException, but in some circumstances the code might sometimes succeed despite the wrong type. Now you will see an exception like this:

    \n
    com.google.gson.JsonIOException: Abstract classes can't be instantiated!\nAdjust the R8 configuration or register an InstanceCreator or a TypeAdapter for this type.\nClass name: com.google.common.collect.ImmutableList\n
    \n

    because Gson now really is trying to create an ImmutableList through its constructor, but that isn't possible.\nEither change the requested type (in the TypeToken) to List<String>, or register a TypeAdapter or JsonDeserializer for ImmutableList.

    \n
  • \n
  • \n

    The internal classes $Gson$Types and $Gson$Preconditions have been renamed to remove the $ characters. Since these are internal classes (as signaled not only by the package name but by the $ characters), client code should not be affected. If your code was depending on these classes then we suggest making a copy of the class (subject to the license) rather than depending on the new names.

    \n
  • \n
\n

Full Changelog: https://github.com/google/gson/compare/gson-parent-2.12.1...gson-parent-2.13.0

\n
\n
\n
\nCommits\n
    \n
  • 686fad7 [maven-release-plugin] prepare release gson-parent-2.13.2
  • \n
  • c2d252a Switch to using central-publishing-maven-plugin. (#2900)
  • \n
  • 69cb755 Bump the github-actions group with 5 updates (#2894)
  • \n
  • ea552c2 Bump the maven group across 1 directory with 3 updates (#2898)
  • \n
  • fdc616d Set top-level permissions for CodeQL workflow (#2889)
  • \n
  • 9334715 Create scorecard.yml (#2888)
  • \n
  • f7de5c2 Bump the maven group with 8 updates (#2885)
  • \n
  • 8c23cd3 Update sources to satisfy a new Error Prone check. (#2887)
  • \n
  • 5eab3ed Bump the github-actions group with 2 updates (#2886)
  • \n
  • 5f5c200 Bump the maven group across 1 directory with 10 updates (#2872)
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.google.code.gson:gson&package-manager=maven&previous-version=2.12.1&new-version=2.13.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2160/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/hub4j/github-api/issues/2160/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2159", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2159/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2159/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2159/events", + "html_url": "https://github.com/hub4j/github-api/pull/2159", + "id": 3577021033, + "node_id": "PR_kwDOAAlq-s6w8RNf", + "number": 2159, + "title": "Chore(deps): Bump github/codeql-action from 3 to 4", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391660, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjYw", + "url": "https://api.github.com/repos/hub4j/github-api/labels/github_actions", + "name": "github_actions", + "color": "000000", + "default": false, + "description": "Pull requests that update Github_actions code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-11-01T02:00:49Z", + "updated_at": "2025-11-12T23:01:40Z", + "closed_at": "2025-11-12T23:00:52Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2159", + "html_url": "https://github.com/hub4j/github-api/pull/2159", + "diff_url": "https://github.com/hub4j/github-api/pull/2159.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2159.patch", + "merged_at": "2025-11-12T23:00:52Z" + }, + "body": "Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.\n
\nRelease notes\n

Sourced from github/codeql-action's releases.

\n
\n

v3.31.2

\n

CodeQL Action Changelog

\n

See the releases page for the relevant changes to the CodeQL CLI and language packs.

\n

3.31.2 - 30 Oct 2025

\n

No user facing changes.

\n

See the full CHANGELOG.md for more information.

\n

v3.31.1

\n

CodeQL Action Changelog

\n

See the releases page for the relevant changes to the CodeQL CLI and language packs.

\n

3.31.1 - 30 Oct 2025

\n
    \n
  • The add-snippets input has been removed from the analyze action. This input has been deprecated since CodeQL Action 3.26.4 in August 2024 when this removal was announced.
  • \n
\n

See the full CHANGELOG.md for more information.

\n

v3.31.0

\n

CodeQL Action Changelog

\n

See the releases page for the relevant changes to the CodeQL CLI and language packs.

\n

3.31.0 - 24 Oct 2025

\n
    \n
  • Bump minimum CodeQL bundle version to 2.17.6. #3223
  • \n
  • When SARIF files are uploaded by the analyze or upload-sarif actions, the CodeQL Action automatically performs post-processing steps to prepare the data for the upload. Previously, these post-processing steps were only performed before an upload took place. We are now changing this so that the post-processing steps will always be performed, even when the SARIF files are not uploaded. This does not change anything for the upload-sarif action. For analyze, this may affect Advanced Setup for CodeQL users who specify a value other than always for the upload input. #3222
  • \n
\n

See the full CHANGELOG.md for more information.

\n

v3.30.9

\n

CodeQL Action Changelog

\n

See the releases page for the relevant changes to the CodeQL CLI and language packs.

\n

3.30.9 - 17 Oct 2025

\n
    \n
  • Update default CodeQL bundle version to 2.23.3. #3205
  • \n
  • Experimental: A new setup-codeql action has been added which is similar to init, except it only installs the CodeQL CLI and does not initialize a database. Do not use this in production as it is part of an internal experiment and subject to change at any time. #3204
  • \n
\n

See the full CHANGELOG.md for more information.

\n

v3.30.8

\n

CodeQL Action Changelog

\n

See the releases page for the relevant changes to the CodeQL CLI and language packs.

\n\n
\n

... (truncated)

\n
\n
\nChangelog\n

Sourced from github/codeql-action's changelog.

\n
\n

4.31.2 - 30 Oct 2025

\n

No user facing changes.

\n

4.31.1 - 30 Oct 2025

\n
    \n
  • The add-snippets input has been removed from the analyze action. This input has been deprecated since CodeQL Action 3.26.4 in August 2024 when this removal was announced.
  • \n
\n

4.31.0 - 24 Oct 2025

\n
    \n
  • Bump minimum CodeQL bundle version to 2.17.6. #3223
  • \n
  • When SARIF files are uploaded by the analyze or upload-sarif actions, the CodeQL Action automatically performs post-processing steps to prepare the data for the upload. Previously, these post-processing steps were only performed before an upload took place. We are now changing this so that the post-processing steps will always be performed, even when the SARIF files are not uploaded. This does not change anything for the upload-sarif action. For analyze, this may affect Advanced Setup for CodeQL users who specify a value other than always for the upload input. #3222
  • \n
\n

4.30.9 - 17 Oct 2025

\n
    \n
  • Update default CodeQL bundle version to 2.23.3. #3205
  • \n
  • Experimental: A new setup-codeql action has been added which is similar to init, except it only installs the CodeQL CLI and does not initialize a database. Do not use this in production as it is part of an internal experiment and subject to change at any time. #3204
  • \n
\n

4.30.8 - 10 Oct 2025

\n

No user facing changes.

\n

4.30.7 - 06 Oct 2025

\n
    \n
  • [v4+ only] The CodeQL Action now runs on Node.js v24. #3169
  • \n
\n

3.30.6 - 02 Oct 2025

\n
    \n
  • Update default CodeQL bundle version to 2.23.2. #3168
  • \n
\n

3.30.5 - 26 Sep 2025

\n
    \n
  • We fixed a bug that was introduced in 3.30.4 with upload-sarif which resulted in files without a .sarif extension not getting uploaded. #3160
  • \n
\n

3.30.4 - 25 Sep 2025

\n
    \n
  • We have improved the CodeQL Action's ability to validate that the workflow it is used in does not use different versions of the CodeQL Action for different workflow steps. Mixing different versions of the CodeQL Action in the same workflow is unsupported and can lead to unpredictable results. A warning will now be emitted from the codeql-action/init step if different versions of the CodeQL Action are detected in the workflow file. Additionally, an error will now be thrown by the other CodeQL Action steps if they load a configuration file that was generated by a different version of the codeql-action/init step. #3099 and #3100
  • \n
  • We added support for reducing the size of dependency caches for Java analyses, which will reduce cache usage and speed up workflows. This will be enabled automatically at a later time. #3107
  • \n
  • You can now run the latest CodeQL nightly bundle by passing tools: nightly to the init action. In general, the nightly bundle is unstable and we only recommend running it when directed by GitHub staff. #3130
  • \n
  • Update default CodeQL bundle version to 2.23.1. #3118
  • \n
\n

3.30.3 - 10 Sep 2025

\n

No user facing changes.

\n

3.30.2 - 09 Sep 2025

\n
    \n
  • Fixed a bug which could cause language autodetection to fail. #3084
  • \n
  • Experimental: The quality-queries input that was added in 3.29.2 as part of an internal experiment is now deprecated and will be removed in an upcoming version of the CodeQL Action. It has been superseded by a new analysis-kinds input, which is part of the same internal experiment. Do not use this in production as it is subject to change at any time. #3064
  • \n
\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 74c8748 Update analyze/action.yml
  • \n
  • 34c50c1 Merge pull request #3251 from github/mbg/user-error/enablement
  • \n
  • 4ae68af Warn if the add-snippets input is used
  • \n
  • 52a7bd7 Check for 403 status
  • \n
  • 194ba0e Make error message tests less brittle
  • \n
  • 53acf0b Turn enablement errors into configuration errors
  • \n
  • ac9aeee Merge pull request #3249 from github/henrymercer/api-logging
  • \n
  • d49e837 Merge branch 'main' into henrymercer/api-logging
  • \n
  • 3d988b2 Pass minimal copy of core
  • \n
  • 8cc18ac Merge pull request #3250 from github/henrymercer/prefer-fs-delete
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action&package-manager=github_actions&previous-version=3&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2159/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/hub4j/github-api/issues/2159/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2158", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2158/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2158/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2158/events", + "html_url": "https://github.com/hub4j/github-api/pull/2158", + "id": 3577020646, + "node_id": "PR_kwDOAAlq-s6w8RHt", + "number": 2158, + "title": "Chore(deps): Bump stefanzweifel/git-auto-commit-action from 6 to 7", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391660, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjYw", + "url": "https://api.github.com/repos/hub4j/github-api/labels/github_actions", + "name": "github_actions", + "color": "000000", + "default": false, + "description": "Pull requests that update Github_actions code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2025-11-01T02:00:43Z", + "updated_at": "2026-01-23T19:01:10Z", + "closed_at": "2026-01-23T19:01:02Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2158", + "html_url": "https://github.com/hub4j/github-api/pull/2158", + "diff_url": "https://github.com/hub4j/github-api/pull/2158.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2158.patch", + "merged_at": "2026-01-23T19:01:02Z" + }, + "body": "Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 6 to 7.\n
\nRelease notes\n

Sourced from stefanzweifel/git-auto-commit-action's releases.

\n
\n

v7.0.0

\n

Added

\n\n

Changed

\n\n

Dependency Updates

\n\n

v6.0.1

\n

Fixed

\n\n
\n
\n
\nChangelog\n

Sourced from stefanzweifel/git-auto-commit-action's changelog.

\n
\n

Changelog

\n

All notable changes to this project will be documented in this file.

\n

The format is based on Keep a Changelog\nand this project adheres to Semantic Versioning.

\n

Unreleased

\n
\n

TBD

\n
\n

v7.0.0 - 2025-10-12

\n

Added

\n\n

Changed

\n\n

Dependency Updates

\n\n

v6.0.1 - 2025-06-11

\n

Fixed

\n\n

v6.0.0 - 2025-06-10

\n

Added

\n
    \n
  • Throw error early if repository is in a detached state (#357)
  • \n
\n

Fixed

\n\n

Removed

\n
    \n
  • Remove support for create_branch, skip_checkout, skip_Fetch (#314)
  • \n
\n

v5.2.0 - 2025-04-19

\n

Added

\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 28e16e8 Release preparations for v7 (#394)
  • \n
  • 698fd76 Merge pull request #391 from EliasBoulharts/custom-tag-message
  • \n
  • c40819a Update README
  • \n
  • d7ee275 Change internal variable names
  • \n
  • e8684eb Fix Tests
  • \n
  • 1949701 Merge branch 'master' into pr/391
  • \n
  • a88dc49 Merge pull request #388 from stefanzweifel/v7-next
  • \n
  • a531dec Merge pull request #386 from stefanzweifel/dependabot/github_actions/actions/...
  • \n
  • acbe8b1 Merge pull request #393 from stefanzweifel/v7-warn-detached-head
  • \n
  • d185485 Enable Detached State Check
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=stefanzweifel/git-auto-commit-action&package-manager=github_actions&previous-version=6&new-version=7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
\n\n> **Note**\n> Automatic rebases have been disabled on this pull request as it has been open for over 30 days.\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2158/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/hub4j/github-api/issues/2158/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2157", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2157/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2157/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2157/events", + "html_url": "https://github.com/hub4j/github-api/pull/2157", + "id": 3577020608, + "node_id": "PR_kwDOAAlq-s6w8RHN", + "number": 2157, + "title": "Chore(deps): Bump actions/download-artifact from 5 to 6", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391660, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjYw", + "url": "https://api.github.com/repos/hub4j/github-api/labels/github_actions", + "name": "github_actions", + "color": "000000", + "default": false, + "description": "Pull requests that update Github_actions code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-11-01T02:00:40Z", + "updated_at": "2025-11-12T23:00:14Z", + "closed_at": "2025-11-12T22:59:40Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2157", + "html_url": "https://github.com/hub4j/github-api/pull/2157", + "diff_url": "https://github.com/hub4j/github-api/pull/2157.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2157.patch", + "merged_at": "2025-11-12T22:59:40Z" + }, + "body": "Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6.\n
\nRelease notes\n

Sourced from actions/download-artifact's releases.

\n
\n

v6.0.0

\n

What's Changed

\n

BREAKING CHANGE: this update supports Node v24.x. This is not a breaking change per-se but we're treating it as such.

\n\n

New Contributors

\n\n

Full Changelog: https://github.com/actions/download-artifact/compare/v5...v6.0.0

\n
\n
\n
\nCommits\n
    \n
  • 018cc2c Merge pull request #438 from actions/danwkennedy/prepare-6.0.0
  • \n
  • 815651c Revert "Remove github.dep.yml"
  • \n
  • bb3a066 Remove github.dep.yml
  • \n
  • fa1ce46 Prepare v6.0.0
  • \n
  • 4a24838 Merge pull request #431 from danwkennedy/patch-1
  • \n
  • 5e3251c Readme: spell out the first use of GHES
  • \n
  • abefc31 Merge pull request #424 from actions/yacaovsnc/update_readme
  • \n
  • ac43a60 Update README with artifact extraction details
  • \n
  • de96f46 Merge pull request #417 from actions/yacaovsnc/update_readme
  • \n
  • 7993cb4 Remove migration guide for artifact download changes
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/download-artifact&package-manager=github_actions&previous-version=5&new-version=6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2157/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/hub4j/github-api/issues/2157/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2152", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2152/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2152/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2152/events", + "html_url": "https://github.com/hub4j/github-api/pull/2152", + "id": 3546846920, + "node_id": "PR_kwDOAAlq-s6vYTSI", + "number": 2152, + "title": "Improve ArchUnit class name test", + "user": { + "login": "bitwiseman", + "id": 1958953, + "node_id": "MDQ6VXNlcjE5NTg5NTM=", + "avatar_url": "https://avatars.githubusercontent.com/u/1958953?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bitwiseman", + "html_url": "https://github.com/bitwiseman", + "followers_url": "https://api.github.com/users/bitwiseman/followers", + "following_url": "https://api.github.com/users/bitwiseman/following{/other_user}", + "gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions", + "organizations_url": "https://api.github.com/users/bitwiseman/orgs", + "repos_url": "https://api.github.com/users/bitwiseman/repos", + "events_url": "https://api.github.com/users/bitwiseman/events{/privacy}", + "received_events_url": "https://api.github.com/users/bitwiseman/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-10-23T22:37:24Z", + "updated_at": "2025-10-24T15:20:33Z", + "closed_at": "2025-10-24T15:20:20Z", + "author_association": "MEMBER", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2152", + "html_url": "https://github.com/hub4j/github-api/pull/2152", + "diff_url": "https://github.com/hub4j/github-api/pull/2152.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2152.patch", + "merged_at": "2025-10-24T15:20:20Z" + }, + "body": "# Description\r\n\r\n\r\n\r\n# Before submitting a PR:\r\n\r\n- [ ] Changes must not break binary backwards compatibility. If you are unclear on how to make the change you think is needed while maintaining backward compatibility, [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [ ] Add JavaDocs and other comments explaining the behavior. \r\n- [ ] When adding or updating methods that fetch entities, add `@link` JavaDoc entries to the relevant documentation on https://docs.github.com/en/rest . \r\n- [ ] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [ ] Run `mvn -D enable-ci clean install site \"-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED\"` locally. If this command doesn't succeed, your change will not pass CI.\r\n- [ ] Push your changes to a branch other than `main`. You will create your PR from that branch.\r\n\r\n# When creating a PR: \r\n\r\n- [ ] Fill in the \"Description\" above with clear summary of the changes. This includes:\r\n - [ ] If this PR fixes one or more issues, include \"Fixes #\" lines for each issue. \r\n - [ ] Provide links to relevant documentation on https://docs.github.com/en/rest where possible. If not including links, explain why not.\r\n- [ ] All lines of new code should be covered by tests as reported by code coverage. Any lines that are not covered must have PR comments explaining why they cannot be covered. For example, \"Reaching this particular exception is hard and is not a particular common scenario.\"\r\n- [ ] Enable \"Allow edits from maintainers\".\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2152/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/hub4j/github-api/issues/2152/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2151", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2151/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2151/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2151/events", + "html_url": "https://github.com/hub4j/github-api/pull/2151", + "id": 3495810417, + "node_id": "PR_kwDOAAlq-s6suMRC", + "number": 2151, + "title": "Add DYNAMIC event type to GHEvent enum", + "user": { + "login": "kkroner8451", + "id": 14809736, + "node_id": "MDQ6VXNlcjE0ODA5NzM2", + "avatar_url": "https://avatars.githubusercontent.com/u/14809736?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/kkroner8451", + "html_url": "https://github.com/kkroner8451", + "followers_url": "https://api.github.com/users/kkroner8451/followers", + "following_url": "https://api.github.com/users/kkroner8451/following{/other_user}", + "gists_url": "https://api.github.com/users/kkroner8451/gists{/gist_id}", + "starred_url": "https://api.github.com/users/kkroner8451/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/kkroner8451/subscriptions", + "organizations_url": "https://api.github.com/users/kkroner8451/orgs", + "repos_url": "https://api.github.com/users/kkroner8451/repos", + "events_url": "https://api.github.com/users/kkroner8451/events{/privacy}", + "received_events_url": "https://api.github.com/users/kkroner8451/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-10-08T14:57:53Z", + "updated_at": "2025-10-23T00:51:40Z", + "closed_at": "2025-10-23T00:51:32Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2151", + "html_url": "https://github.com/hub4j/github-api/pull/2151", + "diff_url": "https://github.com/hub4j/github-api/pull/2151.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2151.patch", + "merged_at": "2025-10-23T00:51:32Z" + }, + "body": "\r\n# Description\r\n\r\nAdded `DYNAMIC` event type to GHEvent enum for handling new `dynamic` events. \r\nFixes #2150 \r\n\r\n- No docs linked as I can't find any published docs, but looking at the data it appears to be dynamic runs of workflows for things like Dependabot, etc. \r\n- No tests added as not all enum values currently had unit tests.\r\n\r\n# Before submitting a PR:\r\n\r\n- [x] Changes must not break binary backwards compatibility. If you are unclear on how to make the change you think is needed while maintaining backward compatibility, [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Add JavaDocs and other comments explaining the behavior. \r\n- [x] When adding or updating methods that fetch entities, add `@link` JavaDoc entries to the relevant documentation on https://docs.github.com/en/rest . \r\n- [x] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Run `mvn -D enable-ci clean install site \"-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED\"` locally. If this command doesn't succeed, your change will not pass CI.\r\n- [x] Push your changes to a branch other than `main`. You will create your PR from that branch.\r\n\r\n# When creating a PR: \r\n\r\n- [x] Fill in the \"Description\" above with clear summary of the changes. This includes:\r\n - [x] If this PR fixes one or more issues, include \"Fixes #\" lines for each issue. \r\n - [x] Provide links to relevant documentation on https://docs.github.com/en/rest where possible. If not including links, explain why not.\r\n- [x] All lines of new code should be covered by tests as reported by code coverage. Any lines that are not covered must have PR comments explaining why they cannot be covered. For example, \"Reaching this particular exception is hard and is not a particular common scenario.\"\r\n- [x] Enable \"Allow edits from maintainers\".\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2151/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/hub4j/github-api/issues/2151/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2149", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2149/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2149/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2149/events", + "html_url": "https://github.com/hub4j/github-api/pull/2149", + "id": 3471705142, + "node_id": "PR_kwDOAAlq-s6rdSoE", + "number": 2149, + "title": "Chore(deps): Bump org.apache.commons:commons-lang3 from 3.18.0 to 3.19.0", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-10-01T02:01:20Z", + "updated_at": "2025-10-23T00:59:43Z", + "closed_at": "2025-10-23T00:59:35Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2149", + "html_url": "https://github.com/hub4j/github-api/pull/2149", + "diff_url": "https://github.com/hub4j/github-api/pull/2149.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2149.patch", + "merged_at": "2025-10-23T00:59:35Z" + }, + "body": "Bumps org.apache.commons:commons-lang3 from 3.18.0 to 3.19.0.\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.apache.commons:commons-lang3&package-manager=maven&previous-version=3.18.0&new-version=3.19.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2149/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/hub4j/github-api/issues/2149/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2148", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2148/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2148/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2148/events", + "html_url": "https://github.com/hub4j/github-api/pull/2148", + "id": 3471704908, + "node_id": "PR_kwDOAAlq-s6rdSkx", + "number": 2148, + "title": "Chore(deps): Bump org.junit:junit-bom from 5.13.4 to 6.0.0", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 2, + "created_at": "2025-10-01T02:01:14Z", + "updated_at": "2025-10-23T00:59:15Z", + "closed_at": "2025-10-23T00:58:56Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2148", + "html_url": "https://github.com/hub4j/github-api/pull/2148", + "diff_url": "https://github.com/hub4j/github-api/pull/2148.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2148.patch", + "merged_at": null + }, + "body": "Bumps [org.junit:junit-bom](https://github.com/junit-team/junit-framework) from 5.13.4 to 6.0.0.\n
\nRelease notes\n

Sourced from org.junit:junit-bom's releases.

\n
\n

JUnit 6.0.0 = Platform 6.0.0 + Jupiter 6.0.0 + Vintage 6.0.0

\n

See Release Notes.

\n

New Contributors

\n\n

Full Changelog: https://github.com/junit-team/junit-framework/compare/r5.14.0...r6.0.0

\n

JUnit 6.0.0-RC3 = Platform 6.0.0-RC3 + Jupiter 6.0.0-RC3 + Vintage 6.0.0-RC3

\n

See Release Notes.

\n

New Contributors

\n\n

Full Changelog: https://github.com/junit-team/junit-framework/compare/r6.0.0-RC2...r6.0.0-RC3

\n

JUnit 6.0.0-RC2 = Platform 6.0.0-RC2 + Jupiter 6.0.0-RC2 + Vintage 6.0.0-RC2

\n

See Release Notes.

\n

Full Changelog: https://github.com/junit-team/junit-framework/compare/r6.0.0-RC1...r6.0.0-RC2

\n

JUnit 6.0.0-RC1 = Platform 6.0.0-RC1 + Jupiter 6.0.0-RC1 + Vintage 6.0.0-RC1

\n

See Release Notes.

\n

New Contributors

\n\n

Full Changelog: https://github.com/junit-team/junit-framework/compare/r6.0.0-M2...r6.0.0-RC1

\n

JUnit 6.0.0-M2 = Platform 6.0.0-M2 + Jupiter 6.0.0-M2 + Vintage 6.0.0-M2

\n

See Release Notes.

\n

New Contributors

\n\n

Full Changelog: https://github.com/junit-team/junit-framework/compare/r6.0.0-M1...r6.0.0-M2

\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 4f79594 Release 6.0.0
  • \n
  • 55af30a Revert "Use develop/6.x branch for junit-examples during release build"
  • \n
  • df3cfdd Release 5.14.0
  • \n
  • fcb84a2 Disable backward compatibility check when offline
  • \n
  • c9c8344 Prune 5.14.0 release notes
  • \n
  • 03d8a72 Update broken link to using API Gaurdian with bndtools
  • \n
  • 3a0b29b Use temporary JUnit 6 logo
  • \n
  • 6603caa Rename eclipseClasspath to eclipseConventions to avoid confusion
  • \n
  • ab3470b Make sealed MediaType work in Eclipse
  • \n
  • a8cd41e Remove annotations not visible in Eclipse
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.junit:junit-bom&package-manager=maven&previous-version=5.13.4&new-version=6.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2148/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/hub4j/github-api/issues/2148/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2147", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2147/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2147/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2147/events", + "html_url": "https://github.com/hub4j/github-api/pull/2147", + "id": 3471704649, + "node_id": "PR_kwDOAAlq-s6rdShP", + "number": 2147, + "title": "Chore(deps): Bump codecov/codecov-action from 5.5.0 to 5.5.1", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391660, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjYw", + "url": "https://api.github.com/repos/hub4j/github-api/labels/github_actions", + "name": "github_actions", + "color": "000000", + "default": false, + "description": "Pull requests that update Github_actions code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-10-01T02:01:07Z", + "updated_at": "2025-10-23T01:00:05Z", + "closed_at": "2025-10-23T00:59:57Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2147", + "html_url": "https://github.com/hub4j/github-api/pull/2147", + "diff_url": "https://github.com/hub4j/github-api/pull/2147.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2147.patch", + "merged_at": "2025-10-23T00:59:57Z" + }, + "body": "Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.5.0 to 5.5.1.\n
\nRelease notes\n

Sourced from codecov/codecov-action's releases.

\n
\n

v5.5.1

\n

What's Changed

\n\n

New Contributors

\n\n

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.5.0...v5.5.1

\n
\n
\n
\nChangelog\n

Sourced from codecov/codecov-action's changelog.

\n
\n

v5.5.1

\n

What's Changed

\n\n

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.5.0..v5.5.1

\n
\n
\n
\nCommits\n\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=codecov/codecov-action&package-manager=github_actions&previous-version=5.5.0&new-version=5.5.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2147/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/hub4j/github-api/issues/2147/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2143", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2143/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2143/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2143/events", + "html_url": "https://github.com/hub4j/github-api/pull/2143", + "id": 3387881462, + "node_id": "PR_kwDOAAlq-s6nEJSs", + "number": 2143, + "title": "feat: add force-cancel workflow run", + "user": { + "login": "cyrilico", + "id": 19289022, + "node_id": "MDQ6VXNlcjE5Mjg5MDIy", + "avatar_url": "https://avatars.githubusercontent.com/u/19289022?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/cyrilico", + "html_url": "https://github.com/cyrilico", + "followers_url": "https://api.github.com/users/cyrilico/followers", + "following_url": "https://api.github.com/users/cyrilico/following{/other_user}", + "gists_url": "https://api.github.com/users/cyrilico/gists{/gist_id}", + "starred_url": "https://api.github.com/users/cyrilico/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/cyrilico/subscriptions", + "organizations_url": "https://api.github.com/users/cyrilico/orgs", + "repos_url": "https://api.github.com/users/cyrilico/repos", + "events_url": "https://api.github.com/users/cyrilico/events{/privacy}", + "received_events_url": "https://api.github.com/users/cyrilico/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-09-05T15:06:56Z", + "updated_at": "2025-09-06T21:04:17Z", + "closed_at": "2025-09-06T19:47:04Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2143", + "html_url": "https://github.com/hub4j/github-api/pull/2143", + "diff_url": "https://github.com/hub4j/github-api/pull/2143.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2143.patch", + "merged_at": "2025-09-06T19:47:04Z" + }, + "body": "# Description\r\n\r\nPretty similar to the existing `cancel` method, but the forced version provided by Github: https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#force-cancel-a-workflow-run\r\n\r\nWe've been using this library extensively and we have a valid use case for force-cancel. This way we don't have go navigate around the library just for this request.\r\n\r\n# Before submitting a PR:\r\n\r\n- [x] Changes must not break binary backwards compatibility. If you are unclear on how to make the change you think is needed while maintaining backward compatibility, [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Add JavaDocs and other comments explaining the behavior. \r\n- [x] When adding or updating methods that fetch entities, add `@link` JavaDoc entries to the relevant documentation on https://docs.github.com/en/rest . \r\n- [x] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Run `mvn -D enable-ci clean install site \"-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED\"` locally. If this command doesn't succeed, your change will not pass CI.\r\n- [x] Push your changes to a branch other than `main`. You will create your PR from that branch.\r\n\r\n# When creating a PR: \r\n\r\n- [x] Fill in the \"Description\" above with clear summary of the changes. This includes:\r\n - [x] If this PR fixes one or more issues, include \"Fixes #\" lines for each issue. \r\n - [x] Provide links to relevant documentation on https://docs.github.com/en/rest where possible. If not including links, explain why not.\r\n- [x] All lines of new code should be covered by tests as reported by code coverage. Any lines that are not covered must have PR comments explaining why they cannot be covered. For example, \"Reaching this particular exception is hard and is not a particular common scenario.\"\r\n- [x] Enable \"Allow edits from maintainers\".\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2143/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/hub4j/github-api/issues/2143/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + } + ] +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchPullRequestsOnly/__files/3-search_issues.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchPullRequestsOnly/__files/3-search_issues.json new file mode 100644 index 0000000000..bdce10bd5b --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchPullRequestsOnly/__files/3-search_issues.json @@ -0,0 +1,2554 @@ +{ + "total_count": 1370, + "incomplete_results": false, + "items": [ + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2193", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2193/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2193/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2193/events", + "html_url": "https://github.com/hub4j/github-api/pull/2193", + "id": 3880789387, + "node_id": "PR_kwDOAAlq-s7ApqDL", + "number": 2193, + "title": "Chore(deps): Bump org.jacoco:jacoco-maven-plugin from 0.8.13 to 0.8.14", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-02-01T02:02:49Z", + "updated_at": "2026-02-10T07:47:33Z", + "closed_at": "2026-02-10T07:47:20Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2193", + "html_url": "https://github.com/hub4j/github-api/pull/2193", + "diff_url": "https://github.com/hub4j/github-api/pull/2193.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2193.patch", + "merged_at": "2026-02-10T07:47:19Z" + }, + "body": "Bumps [org.jacoco:jacoco-maven-plugin](https://github.com/jacoco/jacoco) from 0.8.13 to 0.8.14.\n
\nRelease notes\n

Sourced from org.jacoco:jacoco-maven-plugin's releases.

\n
\n

0.8.14

\n

New Features

\n
    \n
  • JaCoCo now officially supports Java 25 (GitHub #1950).
  • \n
  • Experimental support for Java 26 class files (GitHub #1870).
  • \n
  • Branches added by the Kotlin compiler for default argument number 33 or higher are filtered out during generation of report (GitHub #1655).
  • \n
  • Part of bytecode generated by the Kotlin compiler for elvis operator that follows safe call operator is filtered out during generation of report (GitHub #1814, #1954).
  • \n
  • Part of bytecode generated by the Kotlin compiler for more cases of chained safe call operators is filtered out during generation of report (GitHub #1956).
  • \n
  • Part of bytecode generated by the Kotlin compiler for invocations of suspendCoroutineUninterceptedOrReturn intrinsic is filtered out during generation of report (GitHub #1929).
  • \n
  • Part of bytecode generated by the Kotlin compiler for suspending lambdas with parameters is filtered out during generation of report (GitHub #1945).
  • \n
  • Part of bytecode generated by the Kotlin compiler for suspending functions and lambdas with suspension points that return inline value class is filtered out during generation of report (GitHub #1871).
  • \n
  • Part of bytecode generated by the Kotlin Compose compiler plugin for pausable composition is filtered out during generation of report (GitHub #1911).
  • \n
  • Methods generated by the Kotlin serialization compiler plugin are filtered out (GitHub #1885, #1970, #1971).
  • \n
\n

Fixed bugs

\n
    \n
  • Fixed handling of implicit else clause of when with String subject in Kotlin (GitHub #1813, #1940).
  • \n
  • Fixed handling of implicit default clause of switch by String in Java when compiled by ECJ (GitHub #1813, #1940).\nFixed handling of exceptions in chains of safe call operators in Kotlin (GitHub #1819).
  • \n
\n

Non-functional Changes

\n
    \n
  • JaCoCo now depends on ASM 9.9 (GitHub #1965).
  • \n
\n
\n
\n
\nCommits\n
    \n
  • 2eb2483 Prepare release v0.8.14
  • \n
  • de76181 KotlinSerializableFilter should filter more methods (#1971)
  • \n
  • 89c4bd5 Fix NPE in KotlinSerializableFilter (#1970)
  • \n
  • 0981128 Migrate release staging to the Central Publisher Portal (#1968)
  • \n
  • d07bc6b Add filter for bytecode generated by Kotlin serialization compiler plugin (#1...
  • \n
  • 5e35fd5 Upgrade maven-dependency-plugin to 3.9.0 (#1966)
  • \n
  • c2fe5cc Upgrade ASM to 9.9 (#1965)
  • \n
  • b0f8e23 KotlinSafeCallOperatorFilter should filter "unoptimized" safe call followed b...
  • \n
  • c7bd3f4 Upgrade spotless-maven-plugin to 3.0.0 (#1961)
  • \n
  • faa289d KotlinSafeCallOperatorFilter should not be affected by presence of pseudo ins...
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.jacoco:jacoco-maven-plugin&package-manager=maven&previous-version=0.8.13&new-version=0.8.14)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2193/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/hub4j/github-api/issues/2193/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2191", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2191/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2191/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2191/events", + "html_url": "https://github.com/hub4j/github-api/pull/2191", + "id": 3880788559, + "node_id": "PR_kwDOAAlq-s7App3r", + "number": 2191, + "title": "Chore(deps): Bump org.apache.maven.plugins:maven-gpg-plugin from 3.2.7 to 3.2.8", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-02-01T02:02:28Z", + "updated_at": "2026-02-10T07:48:12Z", + "closed_at": "2026-02-10T07:48:04Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2191", + "html_url": "https://github.com/hub4j/github-api/pull/2191", + "diff_url": "https://github.com/hub4j/github-api/pull/2191.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2191.patch", + "merged_at": "2026-02-10T07:48:04Z" + }, + "body": "Bumps [org.apache.maven.plugins:maven-gpg-plugin](https://github.com/apache/maven-gpg-plugin) from 3.2.7 to 3.2.8.\n
\nRelease notes\n

Sourced from org.apache.maven.plugins:maven-gpg-plugin's releases.

\n
\n

3.2.8

\n\n

🐛 Bug Fixes

\n\n

📝 Documentation updates

\n\n

đŸ‘ģ Maintenance

\n\n

đŸ“Ļ Dependency updates

\n\n
\n
\n
\nCommits\n
    \n
  • 8a46455 [maven-release-plugin] prepare release maven-gpg-plugin-3.2.8
  • \n
  • 7012821 Fix issueManagement, ciManagement system and url
  • \n
  • a9a8c84 Make empty classifier null (not empty string) (#287)
  • \n
  • a8368b0 Add .mvn
  • \n
  • f0e45e0 Update parent POM to 45 (#284)
  • \n
  • cb1236c Bump bouncycastleVersion from 1.78.1 to 1.80 (#127)
  • \n
  • 5377a10 Bump commons-io:commons-io from 2.18.0 to 2.19.0 (#133)
  • \n
  • 8b63932 Bump org.apache.maven.plugins:maven-invoker-plugin from 3.8.0 to 3.9.0 (#125)
  • \n
  • 54ea518 Bump org.simplify4u.plugins:pgpverify-maven-plugin from 1.18.2 to 1.19.1
  • \n
  • a6a412d Remove old JIRA issue link
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.apache.maven.plugins:maven-gpg-plugin&package-manager=maven&previous-version=3.2.7&new-version=3.2.8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2191/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/hub4j/github-api/issues/2191/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2190", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2190/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2190/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2190/events", + "html_url": "https://github.com/hub4j/github-api/pull/2190", + "id": 3858243660, + "node_id": "PR_kwDOAAlq-s6_e9RM", + "number": 2190, + "title": "fix: override GHPullRequest isPullRequest", + "user": { + "login": "Anonycoders", + "id": 40047636, + "node_id": "MDQ6VXNlcjQwMDQ3NjM2", + "avatar_url": "https://avatars.githubusercontent.com/u/40047636?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Anonycoders", + "html_url": "https://github.com/Anonycoders", + "followers_url": "https://api.github.com/users/Anonycoders/followers", + "following_url": "https://api.github.com/users/Anonycoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Anonycoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Anonycoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Anonycoders/subscriptions", + "organizations_url": "https://api.github.com/users/Anonycoders/orgs", + "repos_url": "https://api.github.com/users/Anonycoders/repos", + "events_url": "https://api.github.com/users/Anonycoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Anonycoders/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-01-27T00:18:17Z", + "updated_at": "2026-02-10T07:49:48Z", + "closed_at": "2026-02-10T07:49:34Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2190", + "html_url": "https://github.com/hub4j/github-api/pull/2190", + "diff_url": "https://github.com/hub4j/github-api/pull/2190.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2190.patch", + "merged_at": "2026-02-10T07:49:34Z" + }, + "body": "# Description\r\n\r\n* Fixes #2061\r\n\r\n# Before submitting a PR:\r\n\r\n- [x] Changes must not break binary backwards compatibility. If you are unclear on how to make the change you think is needed while maintaining backward compatibility, [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Add JavaDocs and other comments explaining the behavior. \r\n- [x] When adding or updating methods that fetch entities, add `@link` JavaDoc entries to the relevant documentation on https://docs.github.com/en/rest . \r\n- [x] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Run `mvn -D enable-ci clean install site \"-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED\"` locally. If this command doesn't succeed, your change will not pass CI.\r\n- [x] Push your changes to a branch other than `main`. You will create your PR from that branch.\r\n\r\n# When creating a PR: \r\n\r\n- [x] Fill in the \"Description\" above with clear summary of the changes. This includes:\r\n - [x] If this PR fixes one or more issues, include \"Fixes #\" lines for each issue. \r\n - [ ] Provide links to relevant documentation on https://docs.github.com/en/rest where possible. If not including links, explain why not.\r\n- [x] All lines of new code should be covered by tests as reported by code coverage. Any lines that are not covered must have PR comments explaining why they cannot be covered. For example, \"Reaching this particular exception is hard and is not a particular common scenario.\"\r\n- [x] Enable \"Allow edits from maintainers\".\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2190/reactions", + "total_count": 1, + "+1": 1, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/hub4j/github-api/issues/2190/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2185", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2185/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2185/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2185/events", + "html_url": "https://github.com/hub4j/github-api/pull/2185", + "id": 3853746359, + "node_id": "PR_kwDOAAlq-s6_QWo_", + "number": 2185, + "title": "feat: paginated gh pull request query builder", + "user": { + "login": "Anonycoders", + "id": 40047636, + "node_id": "MDQ6VXNlcjQwMDQ3NjM2", + "avatar_url": "https://avatars.githubusercontent.com/u/40047636?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Anonycoders", + "html_url": "https://github.com/Anonycoders", + "followers_url": "https://api.github.com/users/Anonycoders/followers", + "following_url": "https://api.github.com/users/Anonycoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Anonycoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Anonycoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Anonycoders/subscriptions", + "organizations_url": "https://api.github.com/users/Anonycoders/orgs", + "repos_url": "https://api.github.com/users/Anonycoders/repos", + "events_url": "https://api.github.com/users/Anonycoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Anonycoders/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-01-25T19:29:46Z", + "updated_at": "2026-02-10T07:59:14Z", + "closed_at": "2026-02-10T07:58:24Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2185", + "html_url": "https://github.com/hub4j/github-api/pull/2185", + "diff_url": "https://github.com/hub4j/github-api/pull/2185.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2185.patch", + "merged_at": "2026-02-10T07:58:24Z" + }, + "body": "# Description\r\n\r\n* Fixes #2032 - (`GHPullRequestQueryBuilder` doesn't seem to support `pageSize(int)` but `GHIssueQueryBuilder` does)\r\n\r\nhttps://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#list-pull-requests\r\n\r\n# Before submitting a PR:\r\n\r\n- [x] Changes must not break binary backwards compatibility. If you are unclear on how to make the change you think is needed while maintaining backward compatibility, [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Add JavaDocs and other comments explaining the behavior. \r\n- [x] When adding or updating methods that fetch entities, add `@link` JavaDoc entries to the relevant documentation on https://docs.github.com/en/rest . \r\n- [x] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Run `mvn -D enable-ci clean install site \"-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED\"` locally. If this command doesn't succeed, your change will not pass CI.\r\n- [x] Push your changes to a branch other than `main`. You will create your PR from that branch.\r\n\r\n# When creating a PR: \r\n\r\n- [x] Fill in the \"Description\" above with clear summary of the changes. This includes:\r\n - [x] If this PR fixes one or more issues, include \"Fixes #\" lines for each issue. \r\n - [x] Provide links to relevant documentation on https://docs.github.com/en/rest where possible. If not including links, explain why not.\r\n- [x] All lines of new code should be covered by tests as reported by code coverage. Any lines that are not covered must have PR comments explaining why they cannot be covered. For example, \"Reaching this particular exception is hard and is not a particular common scenario.\"\r\n- [x] Enable \"Allow edits from maintainers\".\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2185/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/hub4j/github-api/issues/2185/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2184", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2184/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2184/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2184/events", + "html_url": "https://github.com/hub4j/github-api/pull/2184", + "id": 3852498861, + "node_id": "PR_kwDOAAlq-s6_MgOl", + "number": 2184, + "title": "feat: add GHPullRequest.markReadyForReview for draft PRs", + "user": { + "login": "Anonycoders", + "id": 40047636, + "node_id": "MDQ6VXNlcjQwMDQ3NjM2", + "avatar_url": "https://avatars.githubusercontent.com/u/40047636?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Anonycoders", + "html_url": "https://github.com/Anonycoders", + "followers_url": "https://api.github.com/users/Anonycoders/followers", + "following_url": "https://api.github.com/users/Anonycoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Anonycoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Anonycoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Anonycoders/subscriptions", + "organizations_url": "https://api.github.com/users/Anonycoders/orgs", + "repos_url": "https://api.github.com/users/Anonycoders/repos", + "events_url": "https://api.github.com/users/Anonycoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Anonycoders/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 2, + "created_at": "2026-01-25T03:46:29Z", + "updated_at": "2026-02-10T07:51:41Z", + "closed_at": "2026-02-10T07:51:18Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2184", + "html_url": "https://github.com/hub4j/github-api/pull/2184", + "diff_url": "https://github.com/hub4j/github-api/pull/2184.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2184.patch", + "merged_at": "2026-02-10T07:51:18Z" + }, + "body": "# Description\r\n\r\nThis change adds new functionality to allow `marking draft PRs as ready for review` using GraphQL as it's not supported by REST.\r\n\r\nhttps://docs.github.com/en/graphql/reference/mutations#markpullrequestreadyforreview\r\n\r\n# Before submitting a PR:\r\n\r\n- [x] Changes must not break binary backwards compatibility. If you are unclear on how to make the change you think is needed while maintaining backward compatibility, [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Add JavaDocs and other comments explaining the behavior. \r\n- [x] When adding or updating methods that fetch entities, add `@link` JavaDoc entries to the relevant documentation on https://docs.github.com/en/rest . \r\n- [x] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Run `mvn -D enable-ci clean install site \"-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED\"` locally. If this command doesn't succeed, your change will not pass CI.\r\n- [x] Push your changes to a branch other than `main`. You will create your PR from that branch.\r\n\r\n# When creating a PR: \r\n\r\n- [x] Fill in the \"Description\" above with clear summary of the changes. This includes:\r\n - [ ] If this PR fixes one or more issues, include \"Fixes #\" lines for each issue. \r\n - [x] Provide links to relevant documentation on https://docs.github.com/en/rest where possible. If not including links, explain why not.\r\n- [x] All lines of new code should be covered by tests as reported by code coverage. Any lines that are not covered must have PR comments explaining why they cannot be covered. For example, \"Reaching this particular exception is hard and is not a particular common scenario.\"\r\n- [x] Enable \"Allow edits from maintainers\".\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2184/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/hub4j/github-api/issues/2184/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2182", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2182/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2182/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2182/events", + "html_url": "https://github.com/hub4j/github-api/pull/2182", + "id": 3852417004, + "node_id": "PR_kwDOAAlq-s6_MQiJ", + "number": 2182, + "title": "Enable Jackson 3 - Phase 1", + "user": { + "login": "pvillard31", + "id": 11541012, + "node_id": "MDQ6VXNlcjExNTQxMDEy", + "avatar_url": "https://avatars.githubusercontent.com/u/11541012?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/pvillard31", + "html_url": "https://github.com/pvillard31", + "followers_url": "https://api.github.com/users/pvillard31/followers", + "following_url": "https://api.github.com/users/pvillard31/following{/other_user}", + "gists_url": "https://api.github.com/users/pvillard31/gists{/gist_id}", + "starred_url": "https://api.github.com/users/pvillard31/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/pvillard31/subscriptions", + "organizations_url": "https://api.github.com/users/pvillard31/orgs", + "repos_url": "https://api.github.com/users/pvillard31/repos", + "events_url": "https://api.github.com/users/pvillard31/events{/privacy}", + "received_events_url": "https://api.github.com/users/pvillard31/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-01-25T02:13:04Z", + "updated_at": "2026-01-25T03:20:51Z", + "closed_at": "2026-01-25T03:20:35Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2182", + "html_url": "https://github.com/hub4j/github-api/pull/2182", + "diff_url": "https://github.com/hub4j/github-api/pull/2182.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2182.patch", + "merged_at": "2026-01-25T03:20:35Z" + }, + "body": "# Description\r\n\r\nSee discussion in #2173.\r\nThis is a first PR to stay on Jackson 2.x but prepare for Jackson 3 support.\r\n\r\n# Before submitting a PR:\r\n\r\n- [x] Changes must not break binary backwards compatibility. If you are unclear on how to make the change you think is needed while maintaining backward compatibility, [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Add JavaDocs and other comments explaining the behavior. \r\n- [x] When adding or updating methods that fetch entities, add `@link` JavaDoc entries to the relevant documentation on https://docs.github.com/en/rest . \r\n- [x] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Run `mvn -D enable-ci clean install site \"-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED\"` locally. If this command doesn't succeed, your change will not pass CI.\r\n- [x] Push your changes to a branch other than `main`. You will create your PR from that branch.\r\n\r\n# When creating a PR: \r\n\r\n- [x] Fill in the \"Description\" above with clear summary of the changes. This includes:\r\n - [x] If this PR fixes one or more issues, include \"Fixes #\" lines for each issue. \r\n - [x] Provide links to relevant documentation on https://docs.github.com/en/rest where possible. If not including links, explain why not.\r\n- [x] All lines of new code should be covered by tests as reported by code coverage. Any lines that are not covered must have PR comments explaining why they cannot be covered. For example, \"Reaching this particular exception is hard and is not a particular common scenario.\"\r\n- [x] Enable \"Allow edits from maintainers\".\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2182/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/hub4j/github-api/issues/2182/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2180", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2180/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2180/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2180/events", + "html_url": "https://github.com/hub4j/github-api/pull/2180", + "id": 3839900916, + "node_id": "PR_kwDOAAlq-s6-iczp", + "number": 2180, + "title": "fix: adjust enterprise api url for graphql use case", + "user": { + "login": "Anonycoders", + "id": 40047636, + "node_id": "MDQ6VXNlcjQwMDQ3NjM2", + "avatar_url": "https://avatars.githubusercontent.com/u/40047636?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Anonycoders", + "html_url": "https://github.com/Anonycoders", + "followers_url": "https://api.github.com/users/Anonycoders/followers", + "following_url": "https://api.github.com/users/Anonycoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Anonycoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Anonycoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Anonycoders/subscriptions", + "organizations_url": "https://api.github.com/users/Anonycoders/orgs", + "repos_url": "https://api.github.com/users/Anonycoders/repos", + "events_url": "https://api.github.com/users/Anonycoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Anonycoders/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-01-21T20:26:10Z", + "updated_at": "2026-01-24T22:05:14Z", + "closed_at": "2026-01-24T22:05:06Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2180", + "html_url": "https://github.com/hub4j/github-api/pull/2180", + "diff_url": "https://github.com/hub4j/github-api/pull/2180.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2180.patch", + "merged_at": "2026-01-24T22:05:06Z" + }, + "body": "# Description\r\n\r\nFor `GitHub.com` this is `https://api.github.com/graphql`. For `GitHub Enterprise Server`, the GraphQL endpoint is at `/api/graphql (not /api/v3/graphql)`.\r\nThis change makes sure the URL constructed appropriately.\r\n\r\nhttps://docs.github.com/en/enterprise-cloud@latest/graphql/guides/managing-enterprise-accounts#3-setting-up-insomnia-to-use-the-github-graphql-api-with-enterprise-accounts\r\n\r\n# Before submitting a PR:\r\n\r\n- [x] Changes must not break binary backwards compatibility. If you are unclear on how to make the change you think is needed while maintaining backward compatibility, [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [ ] Add JavaDocs and other comments explaining the behavior. \r\n- [ ] When adding or updating methods that fetch entities, add `@link` JavaDoc entries to the relevant documentation on https://docs.github.com/en/rest . \r\n- [x] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Run `mvn -D enable-ci clean install site \"-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED\"` locally. If this command doesn't succeed, your change will not pass CI.\r\n- [x] Push your changes to a branch other than `main`. You will create your PR from that branch.\r\n\r\n# When creating a PR: \r\n\r\n- [x] Fill in the \"Description\" above with clear summary of the changes. This includes:\r\n - [ ] If this PR fixes one or more issues, include \"Fixes #\" lines for each issue. \r\n - [x] Provide links to relevant documentation on https://docs.github.com/en/rest where possible. If not including links, explain why not.\r\n- [x] All lines of new code should be covered by tests as reported by code coverage. Any lines that are not covered must have PR comments explaining why they cannot be covered. For example, \"Reaching this particular exception is hard and is not a particular common scenario.\"\r\n- [x] Enable \"Allow edits from maintainers\".\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2180/reactions", + "total_count": 1, + "+1": 1, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/hub4j/github-api/issues/2180/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2178", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2178/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2178/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2178/events", + "html_url": "https://github.com/hub4j/github-api/pull/2178", + "id": 3774013900, + "node_id": "PR_kwDOAAlq-s67KzV5", + "number": 2178, + "title": "Chore(deps): Bump jjwt.suite.version from 0.12.6 to 0.13.0", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-01-01T02:01:02Z", + "updated_at": "2026-01-24T21:50:50Z", + "closed_at": "2026-01-24T21:50:09Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2178", + "html_url": "https://github.com/hub4j/github-api/pull/2178", + "diff_url": "https://github.com/hub4j/github-api/pull/2178.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2178.patch", + "merged_at": "2026-01-24T21:50:09Z" + }, + "body": "Bumps `jjwt.suite.version` from 0.12.6 to 0.13.0.\nUpdates `io.jsonwebtoken:jjwt-api` from 0.12.6 to 0.13.0\n
\nRelease notes\n

Sourced from io.jsonwebtoken:jjwt-api's releases.

\n
\n

0.13.0

\n

This is the last minor JJWT release branch that will support Java 7.

\n

Any necessary emergency bug fixes will be fixed in subsequent 0.13.x patch releases, but all new development, including Java 8 compatible changes, will be in the next minor (0.14.0) release.

\n

All future JJWT major and minor versions ( 0.14.0 and later) will require Java 8 or later.

\n

What's Changed

\n

This release contains a single change:

\n
    \n
  • The previously private JacksonDeserializer(ObjectMapper objectMapper, Map<String, Class<?>> claimTypeMap) constructor is now public for those that want register a claims type converter on their own specified ObjectMapper instance. Thank you to @​kesrishubham2510 for PR #972. See Issue 914.
  • \n
\n

Full Changelog: https://github.com/jwtk/jjwt/compare/0.12.7...0.13.0

\n

0.12.7

\n

This patch release:

\n
    \n
  • \n

    Adds a new Maven BOM! This is useful for multi-module projects. See Issue 967.

    \n
  • \n
  • \n

    Allows the JwtParserBuilder to have empty nested algorithm collections, effectively disabling the parser's associated feature:

    \n
      \n
    • Emptying the zip() nested collection disables JWT decompression.
    • \n
    • Emptying the sig() nested collection disables JWS mac/signature verification (i.e. all JWSs will be unsupported/rejected).
    • \n
    • Emptying either the enc() or key() nested collections disables JWE decryption (i.e. all JWEs will be unsupported/rejected)
    • \n
    \n

    See Issue 996.

    \n
  • \n
  • \n

    Fixes bug 961 where JwtParserBuilder nested collection builders were not correctly replacing algorithms with the same id.

    \n
  • \n
  • \n

    Ensures a JwkSet's keys collection is no longer entirely secret/redacted by default. This was an overzealous default that was unnecessarily restrictive; the keys collection itself should always be public, and each individual key within should determine which fields should be redacted when printed. See Issue 976.

    \n
  • \n
  • \n

    Improves performance slightly by ensuring all jjwt-api utility methods that create *Builder instances (Jwts.builder(), Jwts.parserBuilder(), Jwks.builder(), etc) no longer use reflection.

    \n

    Instead,static factories are created via reflection only once during initial jjwt-api classloading, and then *Builders are created via standard instantiation using the new operator thereafter. This also benefits certain environments that may not have ideal ClassLoader implementations (e.g. Tomcat in some cases).

    \n

    NOTE: because this changes which classes are loaded via reflection, any environments that must explicitly reference reflective class names (e.g. GraalVM applications) will need to be updated to reflect the new factory class names.

    \n

    See Issue 988.

    \n
  • \n
  • \n

    Upgrades the Gson dependency to 2.11.0

    \n
  • \n
  • \n

    Upgrades the BouncyCastle dependency to 1.78.1

    \n
  • \n
\n

New Contributors

\n\n

Full Changelog: https://github.com/jwtk/jjwt/compare/0.12.6...0.12.7

\n
\n
\n
\nChangelog\n

Sourced from io.jsonwebtoken:jjwt-api's changelog.

\n
\n

0.13.0

\n

This is the last minor JJWT release branch that will support Java 7. Any necessary emergency bug fixes will be fixed in subsequent 0.13.x patch releases, but all new development, including Java 8 compatible changes, will be in the next minor (0.14.0) release.

\n

All future JJWT major and minor versions ( 0.14.0 and later) will require Java 8 or later.

\n

This 0.13.0 minor release has only one change:

\n
    \n
  • The previously private JacksonDeserializer(ObjectMapper objectMapper, Map<String, Class<?>> claimTypeMap) constructor is now public for those that want register a claims\ntype converter on their own specified ObjectMapper instance. See Issue 914.
  • \n
\n

0.12.7

\n

This patch release:

\n
    \n
  • \n

    Adds a new Maven BOM, useful for multi-module projects. See Issue 967.

    \n
  • \n
  • \n

    Allows the JwtParserBuilder to have empty nested algorithm collections, effectively disabling the parser's associated feature:

    \n
      \n
    • Emptying the zip() nested collection disables JWT decompression.
    • \n
    • Emptying the sig() nested collection disables JWS mac/signature verification (i.e. all JWSs will be unsupported/rejected).
    • \n
    • Emptying either the enc() or key() nested collections disables JWE decryption (i.e. all JWEs will be unsupported/rejected)
    • \n
    \n

    See Issue 996.

    \n
  • \n
  • \n

    Fixes bug 961 where JwtParserBuilder nested collection builders were not correctly replacing algorithms with the same id.

    \n
  • \n
  • \n

    Ensures a JwkSet's keys collection is no longer entirely secret/redacted by default. This was an overzealous default that was unnecessarily restrictive; the keys collection itself should always be public, and each individual key within should determine which fields should be redacted when printed. See Issue 976.

    \n
  • \n
  • \n

    Improves performance slightly by ensuring all jjwt-api utility methods that create *Builder instances (Jwts.builder(), Jwts.parserBuilder(), Jwks.builder(), etc) no longer use reflection.

    \n

    Instead,static factories are created via reflection only once during initial jjwt-api classloading, and then *Builders are created via standard instantiation using the new operator thereafter. This also benefits certain environments that may not have ideal ClassLoader implementations (e.g. Tomcat in some cases).

    \n

    NOTE: because this changes which classes are loaded via reflection, any environments that must explicitly reference reflective class names (e.g. GraalVM applications) will need to be updated to reflect the new factory class names.

    \n

    See Issue 988.

    \n
  • \n
  • \n

    Upgrades the Gson dependency to 2.11.0

    \n
  • \n
  • \n

    Upgrades the BouncyCastle dependency to 1.78.1

    \n
  • \n
\n
\n
\n
\nCommits\n\n
\n
\n\nUpdates `io.jsonwebtoken:jjwt-impl` from 0.12.6 to 0.13.0\n
\nRelease notes\n

Sourced from io.jsonwebtoken:jjwt-impl's releases.

\n
\n

0.13.0

\n

This is the last minor JJWT release branch that will support Java 7.

\n

Any necessary emergency bug fixes will be fixed in subsequent 0.13.x patch releases, but all new development, including Java 8 compatible changes, will be in the next minor (0.14.0) release.

\n

All future JJWT major and minor versions ( 0.14.0 and later) will require Java 8 or later.

\n

What's Changed

\n

This release contains a single change:

\n
    \n
  • The previously private JacksonDeserializer(ObjectMapper objectMapper, Map<String, Class<?>> claimTypeMap) constructor is now public for those that want register a claims type converter on their own specified ObjectMapper instance. Thank you to @​kesrishubham2510 for PR #972. See Issue 914.
  • \n
\n

Full Changelog: https://github.com/jwtk/jjwt/compare/0.12.7...0.13.0

\n

0.12.7

\n

This patch release:

\n
    \n
  • \n

    Adds a new Maven BOM! This is useful for multi-module projects. See Issue 967.

    \n
  • \n
  • \n

    Allows the JwtParserBuilder to have empty nested algorithm collections, effectively disabling the parser's associated feature:

    \n
      \n
    • Emptying the zip() nested collection disables JWT decompression.
    • \n
    • Emptying the sig() nested collection disables JWS mac/signature verification (i.e. all JWSs will be unsupported/rejected).
    • \n
    • Emptying either the enc() or key() nested collections disables JWE decryption (i.e. all JWEs will be unsupported/rejected)
    • \n
    \n

    See Issue 996.

    \n
  • \n
  • \n

    Fixes bug 961 where JwtParserBuilder nested collection builders were not correctly replacing algorithms with the same id.

    \n
  • \n
  • \n

    Ensures a JwkSet's keys collection is no longer entirely secret/redacted by default. This was an overzealous default that was unnecessarily restrictive; the keys collection itself should always be public, and each individual key within should determine which fields should be redacted when printed. See Issue 976.

    \n
  • \n
  • \n

    Improves performance slightly by ensuring all jjwt-api utility methods that create *Builder instances (Jwts.builder(), Jwts.parserBuilder(), Jwks.builder(), etc) no longer use reflection.

    \n

    Instead,static factories are created via reflection only once during initial jjwt-api classloading, and then *Builders are created via standard instantiation using the new operator thereafter. This also benefits certain environments that may not have ideal ClassLoader implementations (e.g. Tomcat in some cases).

    \n

    NOTE: because this changes which classes are loaded via reflection, any environments that must explicitly reference reflective class names (e.g. GraalVM applications) will need to be updated to reflect the new factory class names.

    \n

    See Issue 988.

    \n
  • \n
  • \n

    Upgrades the Gson dependency to 2.11.0

    \n
  • \n
  • \n

    Upgrades the BouncyCastle dependency to 1.78.1

    \n
  • \n
\n

New Contributors

\n\n

Full Changelog: https://github.com/jwtk/jjwt/compare/0.12.6...0.12.7

\n
\n
\n
\nChangelog\n

Sourced from io.jsonwebtoken:jjwt-impl's changelog.

\n
\n

0.13.0

\n

This is the last minor JJWT release branch that will support Java 7. Any necessary emergency bug fixes will be fixed in subsequent 0.13.x patch releases, but all new development, including Java 8 compatible changes, will be in the next minor (0.14.0) release.

\n

All future JJWT major and minor versions ( 0.14.0 and later) will require Java 8 or later.

\n

This 0.13.0 minor release has only one change:

\n
    \n
  • The previously private JacksonDeserializer(ObjectMapper objectMapper, Map<String, Class<?>> claimTypeMap) constructor is now public for those that want register a claims\ntype converter on their own specified ObjectMapper instance. See Issue 914.
  • \n
\n

0.12.7

\n

This patch release:

\n
    \n
  • \n

    Adds a new Maven BOM, useful for multi-module projects. See Issue 967.

    \n
  • \n
  • \n

    Allows the JwtParserBuilder to have empty nested algorithm collections, effectively disabling the parser's associated feature:

    \n
      \n
    • Emptying the zip() nested collection disables JWT decompression.
    • \n
    • Emptying the sig() nested collection disables JWS mac/signature verification (i.e. all JWSs will be unsupported/rejected).
    • \n
    • Emptying either the enc() or key() nested collections disables JWE decryption (i.e. all JWEs will be unsupported/rejected)
    • \n
    \n

    See Issue 996.

    \n
  • \n
  • \n

    Fixes bug 961 where JwtParserBuilder nested collection builders were not correctly replacing algorithms with the same id.

    \n
  • \n
  • \n

    Ensures a JwkSet's keys collection is no longer entirely secret/redacted by default. This was an overzealous default that was unnecessarily restrictive; the keys collection itself should always be public, and each individual key within should determine which fields should be redacted when printed. See Issue 976.

    \n
  • \n
  • \n

    Improves performance slightly by ensuring all jjwt-api utility methods that create *Builder instances (Jwts.builder(), Jwts.parserBuilder(), Jwks.builder(), etc) no longer use reflection.

    \n

    Instead,static factories are created via reflection only once during initial jjwt-api classloading, and then *Builders are created via standard instantiation using the new operator thereafter. This also benefits certain environments that may not have ideal ClassLoader implementations (e.g. Tomcat in some cases).

    \n

    NOTE: because this changes which classes are loaded via reflection, any environments that must explicitly reference reflective class names (e.g. GraalVM applications) will need to be updated to reflect the new factory class names.

    \n

    See Issue 988.

    \n
  • \n
  • \n

    Upgrades the Gson dependency to 2.11.0

    \n
  • \n
  • \n

    Upgrades the BouncyCastle dependency to 1.78.1

    \n
  • \n
\n
\n
\n
\nCommits\n\n
\n
\n\nUpdates `io.jsonwebtoken:jjwt-jackson` from 0.12.6 to 0.13.0\n\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2178/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/hub4j/github-api/issues/2178/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2177", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2177/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2177/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2177/events", + "html_url": "https://github.com/hub4j/github-api/pull/2177", + "id": 3774013874, + "node_id": "PR_kwDOAAlq-s67KzVi", + "number": 2177, + "title": "Chore(deps): Bump codecov/codecov-action from 5.5.1 to 5.5.2", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391660, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjYw", + "url": "https://api.github.com/repos/hub4j/github-api/labels/github_actions", + "name": "github_actions", + "color": "000000", + "default": false, + "description": "Pull requests that update Github_actions code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-01-01T02:01:00Z", + "updated_at": "2026-01-23T20:37:39Z", + "closed_at": "2026-01-23T20:36:48Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2177", + "html_url": "https://github.com/hub4j/github-api/pull/2177", + "diff_url": "https://github.com/hub4j/github-api/pull/2177.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2177.patch", + "merged_at": "2026-01-23T20:36:48Z" + }, + "body": "Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.5.1 to 5.5.2.\n
\nRelease notes\n

Sourced from codecov/codecov-action's releases.

\n
\n

v5.5.2

\n

What's Changed

\n\n

New Contributors

\n\n

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.5.1...v5.5.2

\n
\n
\n
\nChangelog\n

Sourced from codecov/codecov-action's changelog.

\n
\n

v5.5.2

\n

What's Changed

\n

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.5.1..v5.5.2

\n
\n
\n
\nCommits\n\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=codecov/codecov-action&package-manager=github_actions&previous-version=5.5.1&new-version=5.5.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2177/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/hub4j/github-api/issues/2177/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2176", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2176/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2176/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2176/events", + "html_url": "https://github.com/hub4j/github-api/pull/2176", + "id": 3774013831, + "node_id": "PR_kwDOAAlq-s67KzU-", + "number": 2176, + "title": "Chore(deps): Bump actions/download-artifact from 6 to 7", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391660, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjYw", + "url": "https://api.github.com/repos/hub4j/github-api/labels/github_actions", + "name": "github_actions", + "color": "000000", + "default": false, + "description": "Pull requests that update Github_actions code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-01-01T02:00:56Z", + "updated_at": "2026-01-24T21:51:17Z", + "closed_at": "2026-01-24T21:50:23Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2176", + "html_url": "https://github.com/hub4j/github-api/pull/2176", + "diff_url": "https://github.com/hub4j/github-api/pull/2176.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2176.patch", + "merged_at": "2026-01-24T21:50:23Z" + }, + "body": "Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.\n
\nRelease notes\n

Sourced from actions/download-artifact's releases.

\n
\n

v7.0.0

\n

v7 - What's new

\n
\n

[!IMPORTANT]\nactions/download-artifact@v7 now runs on Node.js 24 (runs.using: node24) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.

\n
\n

Node.js 24

\n

This release updates the runtime to Node.js 24. v6 had preliminary support for Node 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.

\n

What's Changed

\n\n

New Contributors

\n\n

Full Changelog: https://github.com/actions/download-artifact/compare/v6.0.0...v7.0.0

\n
\n
\n
\nCommits\n
    \n
  • 37930b1 Merge pull request #452 from actions/download-artifact-v7-release
  • \n
  • 72582b9 doc: update readme
  • \n
  • 0d2ec9d chore: release v7.0.0 for Node.js 24 support
  • \n
  • fd7ae8f Merge pull request #451 from actions/fix-storage-blob
  • \n
  • d484700 chore: restore minimatch.dep.yml license file
  • \n
  • 03a8080 chore: remove obsolete dependency license files
  • \n
  • 56fe6d9 chore: update @​actions/artifact license file to 5.0.1
  • \n
  • 8e3ebc4 chore: update package-lock.json with @​actions/artifact@​5.0.1
  • \n
  • 1e3c4b4 fix: update @​actions/artifact to ^5.0.0 for Node.js 24 punycode fix
  • \n
  • 458627d chore: use local @​actions/artifact package for Node.js 24 testing
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/download-artifact&package-manager=github_actions&previous-version=6&new-version=7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2176/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/hub4j/github-api/issues/2176/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2175", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2175/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2175/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2175/events", + "html_url": "https://github.com/hub4j/github-api/pull/2175", + "id": 3774013775, + "node_id": "PR_kwDOAAlq-s67KzUJ", + "number": 2175, + "title": "Chore(deps): Bump actions/upload-artifact from 5 to 6", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391660, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjYw", + "url": "https://api.github.com/repos/hub4j/github-api/labels/github_actions", + "name": "github_actions", + "color": "000000", + "default": false, + "description": "Pull requests that update Github_actions code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2026-01-01T02:00:52Z", + "updated_at": "2026-01-24T21:51:38Z", + "closed_at": "2026-01-24T21:50:40Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2175", + "html_url": "https://github.com/hub4j/github-api/pull/2175", + "diff_url": "https://github.com/hub4j/github-api/pull/2175.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2175.patch", + "merged_at": "2026-01-24T21:50:40Z" + }, + "body": "Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.\n
\nRelease notes\n

Sourced from actions/upload-artifact's releases.

\n
\n

v6.0.0

\n

v6 - What's new

\n
\n

[!IMPORTANT]\nactions/upload-artifact@v6 now runs on Node.js 24 (runs.using: node24) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.

\n
\n

Node.js 24

\n

This release updates the runtime to Node.js 24. v5 had preliminary support for Node.js 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.

\n

What's Changed

\n\n

Full Changelog: https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0

\n
\n
\n
\nCommits\n
    \n
  • b7c566a Merge pull request #745 from actions/upload-artifact-v6-release
  • \n
  • e516bc8 docs: correct description of Node.js 24 support in README
  • \n
  • ddc45ed docs: update README to correct action name for Node.js 24 support
  • \n
  • 615b319 chore: release v6.0.0 for Node.js 24 support
  • \n
  • 017748b Merge pull request #744 from actions/fix-storage-blob
  • \n
  • 38d4c79 chore: rebuild dist
  • \n
  • 7d27270 chore: add missing license cache files for @​actions/core, @​actions/io, and mi...
  • \n
  • 5f643d3 chore: update license files for @​actions/artifact@​5.0.1 dependencies
  • \n
  • 1df1684 chore: update package-lock.json with @​actions/artifact@​5.0.1
  • \n
  • b5b1a91 fix: update @​actions/artifact to ^5.0.0 for Node.js 24 punycode fix
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/upload-artifact&package-manager=github_actions&previous-version=5&new-version=6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2175/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/hub4j/github-api/issues/2175/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2174", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2174/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2174/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2174/events", + "html_url": "https://github.com/hub4j/github-api/pull/2174", + "id": 3774013768, + "node_id": "PR_kwDOAAlq-s67KzUC", + "number": 2174, + "title": "Chore(deps-dev): Bump org.mockito:mockito-core from 5.20.0 to 5.21.0", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 2, + "created_at": "2026-01-01T02:00:51Z", + "updated_at": "2026-01-24T21:51:51Z", + "closed_at": "2026-01-24T21:51:05Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2174", + "html_url": "https://github.com/hub4j/github-api/pull/2174", + "diff_url": "https://github.com/hub4j/github-api/pull/2174.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2174.patch", + "merged_at": "2026-01-24T21:51:05Z" + }, + "body": "Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.20.0 to 5.21.0.\n
\nRelease notes\n

Sourced from org.mockito:mockito-core's releases.

\n
\n

v5.21.0

\n

Changelog generated by Shipkit Changelog Gradle Plugin

\n

5.21.0

\n\n
\n
\n
\nCommits\n
    \n
  • 09d2230 Bump graalvm/setup-graalvm from 1.4.3 to 1.4.4 (#3768)
  • \n
  • df3e0cc Bump graalvm/setup-graalvm from 1.4.2 to 1.4.3 (#3767)
  • \n
  • 04a6e9f Bump actions/checkout from 5 to 6 (#3765)
  • \n
  • 756a3cf Add description of matchers to potential mismatch (#3760)
  • \n
  • 58ba445 Forbid mocking WeakReference with inline mock maker (#3759)
  • \n
  • 966d600 Bump actions/upload-artifact from 4 to 5 (#3756)
  • \n
  • 632bf7b Bump graalvm/setup-graalvm from 1.4.1 to 1.4.2 (#3755)
  • \n
  • 8564b43 Fix primitives support in GenericArrayReturnType for Android (#3753)
  • \n
  • bf3a809 Bump graalvm/setup-graalvm from 1.4.0 to 1.4.1 (#3744)
  • \n
  • cffddd4 Bump gradle/actions from 4 to 5 (#3743)
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.mockito:mockito-core&package-manager=maven&previous-version=5.20.0&new-version=5.21.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2174/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/hub4j/github-api/issues/2174/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2170", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2170/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2170/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2170/events", + "html_url": "https://github.com/hub4j/github-api/pull/2170", + "id": 3678909093, + "node_id": "PR_kwDOAAlq-s62PDSN", + "number": 2170, + "title": "Chore(deps): Bump actions/checkout from 5 to 6", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391660, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjYw", + "url": "https://api.github.com/repos/hub4j/github-api/labels/github_actions", + "name": "github_actions", + "color": "000000", + "default": false, + "description": "Pull requests that update Github_actions code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-12-01T02:10:52Z", + "updated_at": "2025-12-24T01:52:41Z", + "closed_at": "2025-12-24T01:52:29Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2170", + "html_url": "https://github.com/hub4j/github-api/pull/2170", + "diff_url": "https://github.com/hub4j/github-api/pull/2170.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2170.patch", + "merged_at": "2025-12-24T01:52:29Z" + }, + "body": "Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.\n
\nRelease notes\n

Sourced from actions/checkout's releases.

\n
\n

v6.0.0

\n

What's Changed

\n\n

Full Changelog: https://github.com/actions/checkout/compare/v5.0.0...v6.0.0

\n

v6-beta

\n

What's Changed

\n

Updated persist-credentials to store the credentials under $RUNNER_TEMP instead of directly in the local git config.

\n

This requires a minimum Actions Runner version of v2.329.0 to access the persisted credentials for Docker container action scenarios.

\n

v5.0.1

\n

What's Changed

\n\n

Full Changelog: https://github.com/actions/checkout/compare/v5...v5.0.1

\n
\n
\n
\nChangelog\n

Sourced from actions/checkout's changelog.

\n
\n

Changelog

\n

V6.0.0

\n\n

V5.0.1

\n\n

V5.0.0

\n\n

V4.3.1

\n\n

V4.3.0

\n\n

v4.2.2

\n\n

v4.2.1

\n\n

v4.2.0

\n\n

v4.1.7

\n\n

v4.1.6

\n\n

v4.1.5

\n\n\n
\n

... (truncated)

\n
\n
\nCommits\n\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=5&new-version=6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2170/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/hub4j/github-api/issues/2170/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2169", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2169/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2169/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2169/events", + "html_url": "https://github.com/hub4j/github-api/pull/2169", + "id": 3678905792, + "node_id": "PR_kwDOAAlq-s62PCjL", + "number": 2169, + "title": "Chore(deps): Bump com.github.spotbugs:spotbugs-maven-plugin from 4.9.8.1 to 4.9.8.2", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-12-01T02:09:11Z", + "updated_at": "2025-12-24T01:54:46Z", + "closed_at": "2025-12-24T01:54:19Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2169", + "html_url": "https://github.com/hub4j/github-api/pull/2169", + "diff_url": "https://github.com/hub4j/github-api/pull/2169.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2169.patch", + "merged_at": "2025-12-24T01:54:19Z" + }, + "body": "Bumps [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) from 4.9.8.1 to 4.9.8.2.\n
\nRelease notes\n

Sourced from com.github.spotbugs:spotbugs-maven-plugin's releases.

\n
\n

Spotbugs Maven Plugin 4.9.8.2

\n\n
\n
\n
\nCommits\n
    \n
  • a03feda [maven-release-plugin] prepare release spotbugs-maven-plugin-4.9.8.2
  • \n
  • 1c8063d [gha] Update actions
  • \n
  • f59d628 Merge pull request #1265 from spotbugs/renovate/actions-checkout-6.x
  • \n
  • 1c232fb chore(deps): update actions/checkout action to v6
  • \n
  • 436be13 Merge pull request #1263 from spotbugs/renovate/actions-checkout-digest
  • \n
  • 0708203 Merge pull request #1264 from spotbugs/renovate/github-codeql-action-digest
  • \n
  • fcd2d1b chore(deps): update github/codeql-action digest to e12f017
  • \n
  • 7c54b5b chore(deps): update actions/checkout digest to 93cb6ef
  • \n
  • 79d724e Merge pull request #1262 from spotbugs/renovate/lang3.version
  • \n
  • b9bbed3 fix(deps): update dependency org.apache.commons:commons-lang3 to v3.20.0
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.github.spotbugs:spotbugs-maven-plugin&package-manager=maven&previous-version=4.9.8.1&new-version=4.9.8.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2169/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/hub4j/github-api/issues/2169/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2168", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2168/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2168/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2168/events", + "html_url": "https://github.com/hub4j/github-api/pull/2168", + "id": 3678905639, + "node_id": "PR_kwDOAAlq-s62PChC", + "number": 2168, + "title": "Chore(deps): Bump spring.boot.version from 3.4.5 to 4.0.0", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-12-01T02:09:06Z", + "updated_at": "2026-02-01T02:02:44Z", + "closed_at": "2026-02-01T02:02:43Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2168", + "html_url": "https://github.com/hub4j/github-api/pull/2168", + "diff_url": "https://github.com/hub4j/github-api/pull/2168.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2168.patch", + "merged_at": null + }, + "body": "Bumps `spring.boot.version` from 3.4.5 to 4.0.0.\nUpdates `org.springframework.boot:spring-boot-dependencies` from 3.4.5 to 4.0.0\n
\nRelease notes\n

Sourced from org.springframework.boot:spring-boot-dependencies's releases.

\n
\n

v4.0.0

\n

Full release notes for Spring Boot 4.0 are available on the wiki. There is also a migration guide to help you upgrade from Spring Boot 3.5.

\n

:star: New Features

\n
    \n
  • Change tomcat and jetty runtime modules to starters #48175
  • \n
  • Rename spring-boot-kotlin-serialization to align with the name of the Kotlinx module that it pulls in #48076
  • \n
\n

:lady_beetle: Bug Fixes

\n
    \n
  • Error properties are a general web concern and should not be located beneath server.* #48201
  • \n
  • With both Jackson 2 and 3 on the classpath, @JsonTest fails due to duplicate jacksonTesterFactoryBean #48198
  • \n
  • Gradle war task does not exclude starter POMs from lib-provided #48197
  • \n
  • spring.test.webclient.mockrestserviceserver.enabled is not aligned with its module's name #48193
  • \n
  • SslMeterBinder doesn't register metrics for dynamically added bundles if no bundles exist at bind time #48182
  • \n
  • Properties bound in the child management context ignore the parent's environment prefix #48177
  • \n
  • ssl.chain.expiry metrics doesn't update for dynamically registered SSL bundles #48171
  • \n
  • Starter for spring-boot-micrometer-metrics is missing #48161
  • \n
  • Elasticsearch client's sniffer functionality should not be enabled by default #48155
  • \n
  • spring-boot-starter-elasticsearch should depend on elasticsearch-java #48141
  • \n
  • Auto-configuration exclusions are checked using a different class loader to the one that loads auto-configuration classes #48132
  • \n
  • New arm64 macbooks fail to bootBuildImage due to incorrect platform image #48128
  • \n
  • Properties for configuring an isolated JsonMapper or ObjectMapper are incorrectly named #48116
  • \n
  • Buildpack fails with recent Docker installs due to hardcoded version in URL #48103
  • \n
  • Image building may fail when specifying a platform if an image has already been built with a different platform #48099
  • \n
  • Default values of Kotlinx Serialization JSON configuration properties are not documented #48097
  • \n
  • Custom XML converters should override defaults in HttpMessageConverters #48096
  • \n
  • Kotlin serialization is used too aggressively when other JSON libraries are available #48070
  • \n
  • PortInUseException incorrectly thrown on failure to bind port due to Netty IP misconfiguration #48059
  • \n
  • Auto-configured JCacheMetrics cannot be customized #48057
  • \n
  • WebSecurityCustomizer beans are excluded by WebMvcTest #48055
  • \n
  • Deprecated EnvironmentPostProcessor does not resolve arguments #48047
  • \n
  • RetryPolicySettings should refer to maxRetries, not maxAttempts #48023
  • \n
  • Devtools Restarter does not work with a parameterless main method #47996
  • \n
  • Dependency management for Kafka should not manage Scala 2.12 libraries #47991
  • \n
  • spring-boot-mail should depend on jakarta.mail:jakarta.mail-api and org.eclipse.angus:angus-mail instead of org.eclipse.angus:jakarta.mail #47983
  • \n
  • spring-boot-starter-data-mongodb-reactive has dependency on reactor-test #47982
  • \n
  • Support for ReactiveElasticsearchClient is in the wrong module #47848
  • \n
\n

:notebook_with_decorative_cover: Documentation

\n
    \n
  • Removed property spring.test.webclient.register-rest-template is still documented #48199
  • \n
  • Mention support for detecting AWS ECS in "Deploying to the Cloud" #48170
  • \n
  • Revise AWS section of "Deploying to the Cloud" in reference manual #48163
  • \n
  • Fix typo in PortInUseException Javadoc #48134
  • \n
  • Correct section about required setters in "Type-safe Configuration Properties" #48131
  • \n
  • Use since attribute in configuration properties deprecation consistently #48122
  • \n
  • Document EndpointJsonMapper and management.endpoints.jackson.isolated-json-mapper #48115
  • \n
  • Document support for configuring servlet context init parameters using properties #48112
  • \n
  • Some configuration properties are not documented in the appendix #48095
  • \n
\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 1c0e08b Release v4.0.0
  • \n
  • 3487928 Merge branch '3.5.x'
  • \n
  • 29b8e96 Switch make-default in preparation for Spring Boot 4.0.0
  • \n
  • 88da0dd Merge branch '3.5.x'
  • \n
  • 56feeaa Next development version (v3.5.9-SNAPSHOT)
  • \n
  • 3becdc7 Move server.error properties to spring.web.error
  • \n
  • 2b30632 Merge branch '3.5.x'
  • \n
  • 4f03b44 Merge branch '3.4.x' into 3.5.x
  • \n
  • 3d15c13 Next development version (v3.4.13-SNAPSHOT)
  • \n
  • dc140df Upgrade to Spring Framework 7.0.1
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\nUpdates `org.springframework.boot:spring-boot-maven-plugin` from 3.4.5 to 4.0.0\n
\nRelease notes\n

Sourced from org.springframework.boot:spring-boot-maven-plugin's releases.

\n
\n

v4.0.0

\n

Full release notes for Spring Boot 4.0 are available on the wiki. There is also a migration guide to help you upgrade from Spring Boot 3.5.

\n

:star: New Features

\n
    \n
  • Change tomcat and jetty runtime modules to starters #48175
  • \n
  • Rename spring-boot-kotlin-serialization to align with the name of the Kotlinx module that it pulls in #48076
  • \n
\n

:lady_beetle: Bug Fixes

\n
    \n
  • Error properties are a general web concern and should not be located beneath server.* #48201
  • \n
  • With both Jackson 2 and 3 on the classpath, @JsonTest fails due to duplicate jacksonTesterFactoryBean #48198
  • \n
  • Gradle war task does not exclude starter POMs from lib-provided #48197
  • \n
  • spring.test.webclient.mockrestserviceserver.enabled is not aligned with its module's name #48193
  • \n
  • SslMeterBinder doesn't register metrics for dynamically added bundles if no bundles exist at bind time #48182
  • \n
  • Properties bound in the child management context ignore the parent's environment prefix #48177
  • \n
  • ssl.chain.expiry metrics doesn't update for dynamically registered SSL bundles #48171
  • \n
  • Starter for spring-boot-micrometer-metrics is missing #48161
  • \n
  • Elasticsearch client's sniffer functionality should not be enabled by default #48155
  • \n
  • spring-boot-starter-elasticsearch should depend on elasticsearch-java #48141
  • \n
  • Auto-configuration exclusions are checked using a different class loader to the one that loads auto-configuration classes #48132
  • \n
  • New arm64 macbooks fail to bootBuildImage due to incorrect platform image #48128
  • \n
  • Properties for configuring an isolated JsonMapper or ObjectMapper are incorrectly named #48116
  • \n
  • Buildpack fails with recent Docker installs due to hardcoded version in URL #48103
  • \n
  • Image building may fail when specifying a platform if an image has already been built with a different platform #48099
  • \n
  • Default values of Kotlinx Serialization JSON configuration properties are not documented #48097
  • \n
  • Custom XML converters should override defaults in HttpMessageConverters #48096
  • \n
  • Kotlin serialization is used too aggressively when other JSON libraries are available #48070
  • \n
  • PortInUseException incorrectly thrown on failure to bind port due to Netty IP misconfiguration #48059
  • \n
  • Auto-configured JCacheMetrics cannot be customized #48057
  • \n
  • WebSecurityCustomizer beans are excluded by WebMvcTest #48055
  • \n
  • Deprecated EnvironmentPostProcessor does not resolve arguments #48047
  • \n
  • RetryPolicySettings should refer to maxRetries, not maxAttempts #48023
  • \n
  • Devtools Restarter does not work with a parameterless main method #47996
  • \n
  • Dependency management for Kafka should not manage Scala 2.12 libraries #47991
  • \n
  • spring-boot-mail should depend on jakarta.mail:jakarta.mail-api and org.eclipse.angus:angus-mail instead of org.eclipse.angus:jakarta.mail #47983
  • \n
  • spring-boot-starter-data-mongodb-reactive has dependency on reactor-test #47982
  • \n
  • Support for ReactiveElasticsearchClient is in the wrong module #47848
  • \n
\n

:notebook_with_decorative_cover: Documentation

\n
    \n
  • Removed property spring.test.webclient.register-rest-template is still documented #48199
  • \n
  • Mention support for detecting AWS ECS in "Deploying to the Cloud" #48170
  • \n
  • Revise AWS section of "Deploying to the Cloud" in reference manual #48163
  • \n
  • Fix typo in PortInUseException Javadoc #48134
  • \n
  • Correct section about required setters in "Type-safe Configuration Properties" #48131
  • \n
  • Use since attribute in configuration properties deprecation consistently #48122
  • \n
  • Document EndpointJsonMapper and management.endpoints.jackson.isolated-json-mapper #48115
  • \n
  • Document support for configuring servlet context init parameters using properties #48112
  • \n
  • Some configuration properties are not documented in the appendix #48095
  • \n
\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 1c0e08b Release v4.0.0
  • \n
  • 3487928 Merge branch '3.5.x'
  • \n
  • 29b8e96 Switch make-default in preparation for Spring Boot 4.0.0
  • \n
  • 88da0dd Merge branch '3.5.x'
  • \n
  • 56feeaa Next development version (v3.5.9-SNAPSHOT)
  • \n
  • 3becdc7 Move server.error properties to spring.web.error
  • \n
  • 2b30632 Merge branch '3.5.x'
  • \n
  • 4f03b44 Merge branch '3.4.x' into 3.5.x
  • \n
  • 3d15c13 Next development version (v3.4.13-SNAPSHOT)
  • \n
  • dc140df Upgrade to Spring Framework 7.0.1
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
\n\n> **Note**\n> Automatic rebases have been disabled on this pull request as it has been open for over 30 days.\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2168/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/hub4j/github-api/issues/2168/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2167", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2167/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2167/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2167/events", + "html_url": "https://github.com/hub4j/github-api/pull/2167", + "id": 3678905236, + "node_id": "PR_kwDOAAlq-s62PCbD", + "number": 2167, + "title": "Chore(deps-dev): Bump org.mockito:mockito-core from 5.16.1 to 5.20.0", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-12-01T02:08:53Z", + "updated_at": "2025-12-24T01:54:57Z", + "closed_at": "2025-12-24T01:54:37Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2167", + "html_url": "https://github.com/hub4j/github-api/pull/2167", + "diff_url": "https://github.com/hub4j/github-api/pull/2167.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2167.patch", + "merged_at": "2025-12-24T01:54:37Z" + }, + "body": "Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.16.1 to 5.20.0.\n
\nRelease notes\n

Sourced from org.mockito:mockito-core's releases.

\n
\n

v5.20.0

\n

Changelog generated by Shipkit Changelog Gradle Plugin

\n

5.20.0

\n\n

v5.19.0

\n

Changelog generated by Shipkit Changelog Gradle Plugin

\n

5.19.0

\n\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 3a1a19e Add support for generic types in MockedConstruction and MockedStatic (#3729)
  • \n
  • f3c957a Bump org.assertj:assertj-core from 3.27.4 to 3.27.5 (#3730)
  • \n
  • 3cfbd42 Bump graalvm/setup-graalvm from 1.3.6 to 1.3.7 (#3725)
  • \n
  • 6f9a04b Bump com.gradle.develocity from 4.1.1 to 4.2 (#3726)
  • \n
  • c75dfb8 Bump org.eclipse.platform:org.eclipse.osgi from 3.23.100 to 3.23.200 (#3720)
  • \n
  • 54474fa Bump graalvm/setup-graalvm from 1.3.5 to 1.3.6 (#3719)
  • \n
  • bc06f21 Use Assume.assumeThat for SequencedCollection tests (#3711)
  • \n
  • a10aed0 Bump actions/setup-java from 4 to 5 (#3715)
  • \n
  • 37bb3e5 Fix metadata generation on GraalVM (#3710)
  • \n
  • ef2fd6f Bump com.gradle.develocity from 4.1 to 4.1.1 (#3713)
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.mockito:mockito-core&package-manager=maven&previous-version=5.16.1&new-version=5.20.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2167/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/hub4j/github-api/issues/2167/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2164", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2164/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2164/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2164/events", + "html_url": "https://github.com/hub4j/github-api/pull/2164", + "id": 3577024168, + "node_id": "PR_kwDOAAlq-s6w8R4i", + "number": 2164, + "title": "Chore(deps): Bump com.squareup.okhttp3:okhttp from 4.12.0 to 5.3.0", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-11-01T02:02:30Z", + "updated_at": "2025-12-01T02:11:43Z", + "closed_at": "2025-12-01T02:11:42Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2164", + "html_url": "https://github.com/hub4j/github-api/pull/2164", + "diff_url": "https://github.com/hub4j/github-api/pull/2164.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2164.patch", + "merged_at": null + }, + "body": "Bumps [com.squareup.okhttp3:okhttp](https://github.com/square/okhttp) from 4.12.0 to 5.3.0.\n
\nChangelog\n

Sourced from com.squareup.okhttp3:okhttp's changelog.

\n
\n

Version 5.3.0

\n

2025-10-30

\n
    \n
  • \n

    New: Add tags to Call, including computable tags. Use this to attach application-specific\nmetadata to a Call in an EventListener or Interceptor. The tag can be read in any other\nEventListener or Interceptor.

    \n
      override fun intercept(chain: Interceptor.Chain): Response {\n    chain.call().tag(MyAnalyticsTag::class) {\n      MyAnalyticsTag(...)\n    }\n
    return chain.proceed(chain.request())\n
    \n

    }\n

    \n
  • \n
  • \n

    New: Support request bodies on HTTP/1.1 connection upgrades.

    \n
  • \n
  • \n

    New: EventListener.plus() makes it easier to observe events in multiple listeners.

    \n
  • \n
  • \n

    Fix: Don't spam logs with ‘Method isLoggable in android.util.Log not mocked.’ when using\nOkHttp in Robolectric and Paparazzi tests.

    \n
  • \n
  • \n

    Upgrade: [Kotlin 2.2.21][kotlin_2_2_21].

    \n
  • \n
  • \n

    Upgrade: [Okio 3.16.2][okio_3_16_2].

    \n
  • \n
  • \n

    Upgrade: [ZSTD-KMP 0.4.0][zstd_kmp_0_4_0]. This update fixes a bug that caused APKs to fail\n[16 KB ELF alignment checks][elf_alignment].

    \n
  • \n
\n

Version 5.2.1

\n

2025-10-09

\n
    \n
  • \n

    Fix: Don't crash when calling Socket.shutdownOutput() or shutdownInput() on an SSLSocket\non Android API 21 through 23. This method throws an UnsupportedOperationException, so we now\ncatch that and close the underlying stream instead.

    \n
  • \n
  • \n

    Upgrade: [Okio 3.16.1][okio_3_16_1].

    \n
  • \n
\n

Version 5.2.0

\n

2025-10-07

\n
    \n
  • \n

    New: Support [HTTP 101] responses with Response.socket. This mechanism is only supported on\nHTTP/1.1. We also reimplemented our websocket client to use this new mechanism.

    \n
  • \n
  • \n

    New: The okhttp-zstd module negotiates [Zstandard (zstd)][zstd] compression with servers that\nsupport it. It integrates a new (unstable) [ZSTD-KMP] library, also from Square. Enable it like\nthis:

    \n
  • \n
\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 0960b47 Prepare for release 5.3.0.
  • \n
  • bfb24eb Support Request Bodies on HTTP1.1 Connection Upgrades (#9159)
  • \n
  • cf4a864 Update Gradle to v9.2.0 (#9171)
  • \n
  • 4e7dbec Update dependency com.puppycrawl.tools:checkstyle to v12.1.1 (#9169)
  • \n
  • 0470853 Add tags to calls, including computable tags (#9168)
  • \n
  • 2b70b39 Catch UnsatisfiedLinkError in AndroidLog (#9137)
  • \n
  • 3573555 Update dependency com.github.jnr:jnr-unixsocket to v0.38.24 (#9166)
  • \n
  • af8cf30 Update actions/upload-artifact action to v5 (#9167)
  • \n
  • 478e99c Build an computeIfAbsent() mechanism for tags (#9165)
  • \n
  • d393c86 Use Tags in okhttp3.Request (#9164)
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.squareup.okhttp3:okhttp&package-manager=maven&previous-version=4.12.0&new-version=5.3.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
\n\n> **Note**\n> Automatic rebases have been disabled on this pull request as it has been open for over 30 days.\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2164/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/hub4j/github-api/issues/2164/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2163", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2163/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2163/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2163/events", + "html_url": "https://github.com/hub4j/github-api/pull/2163", + "id": 3577021215, + "node_id": "PR_kwDOAAlq-s6w8RP2", + "number": 2163, + "title": "Chore(deps): Bump org.apache.maven.plugins:maven-javadoc-plugin from 3.11.2 to 3.12.0", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-11-01T02:01:00Z", + "updated_at": "2025-11-26T17:03:35Z", + "closed_at": "2025-11-26T17:03:26Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2163", + "html_url": "https://github.com/hub4j/github-api/pull/2163", + "diff_url": "https://github.com/hub4j/github-api/pull/2163.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2163.patch", + "merged_at": "2025-11-26T17:03:26Z" + }, + "body": "Bumps [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.11.2 to 3.12.0.\n
\nRelease notes\n

Sourced from org.apache.maven.plugins:maven-javadoc-plugin's releases.

\n
\n

3.12.0

\n\n

:boom: Breaking changes

\n\n

🐛 Bug Fixes

\n\n

đŸ‘ģ Maintenance

\n\n

đŸ“Ļ Dependency updates

\n\n

3.11.3

\n\n

🚨 Removed

\n\n

🚀 New features and improvements

\n\n

🐛 Bug Fixes

\n\n

📝 Documentation updates

\n\n

đŸ‘ģ Maintenance

\n\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 2a06bed [maven-release-plugin] prepare release maven-javadoc-plugin-3.12.0
  • \n
  • a71ecf9 bump version 3.12.0-SNAPSHOT
  • \n
  • 88f2b71 [maven-release-plugin] prepare for next development iteration
  • \n
  • 7e18956 [maven-release-plugin] prepare release maven-javadoc-plugin-3.11.4
  • \n
  • c11b76c In legacyMode, don't use -sourcepath, unless excludePackageNames is not empty...
  • \n
  • bc9904b remove fix mojo (#1263)
  • \n
  • f310135 Fix package {...} does not exist in legacyMode (#1243)
  • \n
  • c8270f9 detectOfflineLinks is now false per default for all jar mojo issue #1258 ...
  • \n
  • 953e609 Delete flaky test (#1260)
  • \n
  • 2bba7a4 Bump org.codehaus.mojo:mrm-maven-plugin from 1.6.0 to 1.7.0
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.apache.maven.plugins:maven-javadoc-plugin&package-manager=maven&previous-version=3.11.2&new-version=3.12.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2163/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/hub4j/github-api/issues/2163/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2162", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2162/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2162/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2162/events", + "html_url": "https://github.com/hub4j/github-api/pull/2162", + "id": 3577021147, + "node_id": "PR_kwDOAAlq-s6w8RPA", + "number": 2162, + "title": "Chore(deps): Bump com.github.spotbugs:spotbugs-maven-plugin from 4.9.3.0 to 4.9.8.1", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-11-01T02:00:56Z", + "updated_at": "2025-11-26T17:03:57Z", + "closed_at": "2025-11-26T17:03:45Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2162", + "html_url": "https://github.com/hub4j/github-api/pull/2162", + "diff_url": "https://github.com/hub4j/github-api/pull/2162.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2162.patch", + "merged_at": "2025-11-26T17:03:45Z" + }, + "body": "Bumps [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) from 4.9.3.0 to 4.9.8.1.\n
\nRelease notes\n

Sourced from com.github.spotbugs:spotbugs-maven-plugin's releases.

\n
\n

Spotbugs Maven Plugin 4.9.8.1

\n

Bug fix with SpotbugsInfo.EOF error (was meant to be SpotbugsInfo.EOL).

\n

Spotbugs Maven Plugin 4.9.8.0

\n

Bug fix release supporting spotbugs 4.9.8.

\n

Spotbugs Maven Plugin 4.9.7.0

\n\n

Spotbugs Maven Plugin 4.9.6.0

\n
    \n
  • Supports spotbugs 4.9.6
  • \n
  • note: 4.9.5 had a defect with detection of jakarta in servlets that was unexpected and quickly patched for this release.
  • \n
\n

Spotbugs Maven Plugin 4.9.5.0

\n
    \n
  • Support spotbugs 4.9.5
  • \n
\n

Spotbugs Maven Plugin 4.9.4.2

\n

Consumer

\n
    \n
  • Add support for 'chooseVisitors'
  • \n
  • Minor code cleanup
  • \n
  • Still supports spotbugs 4.9.4
  • \n
\n

Producer

\n
    \n
  • Remove add opens from jvm.config as no longer needed
  • \n
\n

Spotbugs Maven Plugin 4.9.4.1

\n

Consumer

\n
    \n
  • Cleanup readme to better support plugin
  • \n
  • Dropped direct usage of plexus utils and commons io
  • \n
  • Groovy 5 now run engine
  • \n
  • Correct issue since 4.9.2.0 resulting in most runs getting spotbugs.html file incorrectly. This has been refactored to restore doxia 1 overrides to produce xml report only when not running in site lifecycle
  • \n
  • Correct defects with handling of various files on disk such as exclusion filters that were introduced into 4.9.4.0. Integration tests have been applied to prevent future regression.
  • \n
  • Commons io fileutils replaced by files.walk with detailed output moved to debug collection only rather than all runs
  • \n
  • Normalization of path to linux style
  • \n
  • Any regex usage is now precompiled
  • \n
  • Use re-entrant lock for source indexer
  • \n
  • Correct locale usage to use default if not given
  • \n
  • Block doctype and XXE when processing xml files
  • \n
  • Cleanup some fields from resources and in code never used
  • \n
\n

Producer

\n
    \n
  • Pin versions of github actions tools
  • \n
  • Run maven 3.6.3 integration test on windows to get more broad support
  • \n
  • Run maven integration test on mac to get more broad support
  • \n
  • Maven 4 integration tests will continue on linux
  • \n
  • Fix maven wrapper perceived path traversal issue
  • \n
  • Corrections to invoker to re-establish integration test verification's
  • \n
  • Fix bugs in integration tests
  • \n
\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 8eb6aa9 [maven-release-plugin] prepare release spotbugs-maven-plugin-4.9.8.1
  • \n
  • 4ff769f Fix: Correct reported issue with 'EOF' where it should be 'EOL'
  • \n
  • c210782 Merge pull request #1241 from spotbugs/renovate/execpluginversion
  • \n
  • 662fa1e Update dependency org.codehaus.mojo:exec-maven-plugin to v3.6.2
  • \n
  • 8cd9648 [maven-release-plugin] prepare for next development iteration
  • \n
  • d8d4c69 [maven-release-plugin] prepare release spotbugs-maven-plugin-4.9.8.0
  • \n
  • 52cdf26 [ci] Add note about pom entries to update for testing upstream master
  • \n
  • 9b8e387 [pom] Prepare for 4.9.8 release
  • \n
  • 0a8ac5a Merge pull request #1238 from spotbugs/renovate/github-codeql-action-digest
  • \n
  • 4b02d8d Merge pull request #1240 from spotbugs/renovate/spotbugs.version
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.github.spotbugs:spotbugs-maven-plugin&package-manager=maven&previous-version=4.9.3.0&new-version=4.9.8.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2162/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/hub4j/github-api/issues/2162/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2161", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2161/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2161/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2161/events", + "html_url": "https://github.com/hub4j/github-api/pull/2161", + "id": 3577021087, + "node_id": "PR_kwDOAAlq-s6w8RON", + "number": 2161, + "title": "Chore(deps): Bump actions/upload-artifact from 4 to 5", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391660, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjYw", + "url": "https://api.github.com/repos/hub4j/github-api/labels/github_actions", + "name": "github_actions", + "color": "000000", + "default": false, + "description": "Pull requests that update Github_actions code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-11-01T02:00:52Z", + "updated_at": "2025-11-12T23:02:35Z", + "closed_at": "2025-11-12T23:01:16Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2161", + "html_url": "https://github.com/hub4j/github-api/pull/2161", + "diff_url": "https://github.com/hub4j/github-api/pull/2161.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2161.patch", + "merged_at": "2025-11-12T23:01:16Z" + }, + "body": "Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.\n
\nRelease notes\n

Sourced from actions/upload-artifact's releases.

\n
\n

v5.0.0

\n

What's Changed

\n

BREAKING CHANGE: this update supports Node v24.x. This is not a breaking change per-se but we're treating it as such.

\n\n

New Contributors

\n\n

Full Changelog: https://github.com/actions/upload-artifact/compare/v4...v5.0.0

\n

v4.6.2

\n

What's Changed

\n\n

New Contributors

\n\n

Full Changelog: https://github.com/actions/upload-artifact/compare/v4...v4.6.2

\n

v4.6.1

\n

What's Changed

\n\n

Full Changelog: https://github.com/actions/upload-artifact/compare/v4...v4.6.1

\n

v4.6.0

\n

What's Changed

\n\n

Full Changelog: https://github.com/actions/upload-artifact/compare/v4...v4.6.0

\n

v4.5.0

\n

What's Changed

\n\n

New Contributors

\n\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 330a01c Merge pull request #734 from actions/danwkennedy/prepare-5.0.0
  • \n
  • 03f2824 Update github.dep.yml
  • \n
  • 905a1ec Prepare v5.0.0
  • \n
  • 2d9f9cd Merge pull request #725 from patrikpolyak/patch-1
  • \n
  • 9687587 Merge branch 'main' into patch-1
  • \n
  • 2848b2c Merge pull request #727 from danwkennedy/patch-1
  • \n
  • 9b51177 Spell out the first use of GHES
  • \n
  • cd231ca Update GHES guidance to include reference to Node 20 version
  • \n
  • de65e23 Merge pull request #712 from actions/nebuk89-patch-1
  • \n
  • 8747d8c Update README.md
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/upload-artifact&package-manager=github_actions&previous-version=4&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2161/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/hub4j/github-api/issues/2161/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2160", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2160/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2160/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2160/events", + "html_url": "https://github.com/hub4j/github-api/pull/2160", + "id": 3577021065, + "node_id": "PR_kwDOAAlq-s6w8RN7", + "number": 2160, + "title": "Chore(deps-dev): Bump com.google.code.gson:gson from 2.12.1 to 2.13.2", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-11-01T02:00:51Z", + "updated_at": "2025-11-12T23:03:51Z", + "closed_at": "2025-11-12T23:02:36Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2160", + "html_url": "https://github.com/hub4j/github-api/pull/2160", + "diff_url": "https://github.com/hub4j/github-api/pull/2160.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2160.patch", + "merged_at": "2025-11-12T23:02:36Z" + }, + "body": "Bumps [com.google.code.gson:gson](https://github.com/google/gson) from 2.12.1 to 2.13.2.\n
\nRelease notes\n

Sourced from com.google.code.gson:gson's releases.

\n
\n

Gson 2.13.2

\n

The main changes in this release are just newer dependencies.

\n

What's Changed

\n\n

New Contributors

\n\n

Full Changelog: https://github.com/google/gson/compare/gson-parent-2.13.1...gson-parent-2.13.2

\n

Gson 2.13.1

\n

What's Changed

\n\n

New Contributors

\n\n

Full Changelog: https://github.com/google/gson/compare/gson-parent-2.13.0...gson-parent-2.13.1

\n

Gson 2.13.0

\n

What's Changed

\n
    \n
  • \n

    A bug in deserializing collections has been fixed. Previously, if you did something like this:

    \n
    gson.fromJson(jsonString, new TypeToken<ImmutableList<String>>() {})\n
    \n

    then the inferred type would be ImmutableList<String>, but Gson actually gave you an ArrayList<String>. Usually that would lead to an immediate ClassCastException, but in some circumstances the code might sometimes succeed despite the wrong type. Now you will see an exception like this:

    \n
    com.google.gson.JsonIOException: Abstract classes can't be instantiated!\nAdjust the R8 configuration or register an InstanceCreator or a TypeAdapter for this type.\nClass name: com.google.common.collect.ImmutableList\n
    \n

    because Gson now really is trying to create an ImmutableList through its constructor, but that isn't possible.\nEither change the requested type (in the TypeToken) to List<String>, or register a TypeAdapter or JsonDeserializer for ImmutableList.

    \n
  • \n
  • \n

    The internal classes $Gson$Types and $Gson$Preconditions have been renamed to remove the $ characters. Since these are internal classes (as signaled not only by the package name but by the $ characters), client code should not be affected. If your code was depending on these classes then we suggest making a copy of the class (subject to the license) rather than depending on the new names.

    \n
  • \n
\n

Full Changelog: https://github.com/google/gson/compare/gson-parent-2.12.1...gson-parent-2.13.0

\n
\n
\n
\nCommits\n
    \n
  • 686fad7 [maven-release-plugin] prepare release gson-parent-2.13.2
  • \n
  • c2d252a Switch to using central-publishing-maven-plugin. (#2900)
  • \n
  • 69cb755 Bump the github-actions group with 5 updates (#2894)
  • \n
  • ea552c2 Bump the maven group across 1 directory with 3 updates (#2898)
  • \n
  • fdc616d Set top-level permissions for CodeQL workflow (#2889)
  • \n
  • 9334715 Create scorecard.yml (#2888)
  • \n
  • f7de5c2 Bump the maven group with 8 updates (#2885)
  • \n
  • 8c23cd3 Update sources to satisfy a new Error Prone check. (#2887)
  • \n
  • 5eab3ed Bump the github-actions group with 2 updates (#2886)
  • \n
  • 5f5c200 Bump the maven group across 1 directory with 10 updates (#2872)
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.google.code.gson:gson&package-manager=maven&previous-version=2.12.1&new-version=2.13.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2160/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/hub4j/github-api/issues/2160/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2159", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2159/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2159/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2159/events", + "html_url": "https://github.com/hub4j/github-api/pull/2159", + "id": 3577021033, + "node_id": "PR_kwDOAAlq-s6w8RNf", + "number": 2159, + "title": "Chore(deps): Bump github/codeql-action from 3 to 4", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391660, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjYw", + "url": "https://api.github.com/repos/hub4j/github-api/labels/github_actions", + "name": "github_actions", + "color": "000000", + "default": false, + "description": "Pull requests that update Github_actions code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-11-01T02:00:49Z", + "updated_at": "2025-11-12T23:01:40Z", + "closed_at": "2025-11-12T23:00:52Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2159", + "html_url": "https://github.com/hub4j/github-api/pull/2159", + "diff_url": "https://github.com/hub4j/github-api/pull/2159.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2159.patch", + "merged_at": "2025-11-12T23:00:52Z" + }, + "body": "Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.\n
\nRelease notes\n

Sourced from github/codeql-action's releases.

\n
\n

v3.31.2

\n

CodeQL Action Changelog

\n

See the releases page for the relevant changes to the CodeQL CLI and language packs.

\n

3.31.2 - 30 Oct 2025

\n

No user facing changes.

\n

See the full CHANGELOG.md for more information.

\n

v3.31.1

\n

CodeQL Action Changelog

\n

See the releases page for the relevant changes to the CodeQL CLI and language packs.

\n

3.31.1 - 30 Oct 2025

\n
    \n
  • The add-snippets input has been removed from the analyze action. This input has been deprecated since CodeQL Action 3.26.4 in August 2024 when this removal was announced.
  • \n
\n

See the full CHANGELOG.md for more information.

\n

v3.31.0

\n

CodeQL Action Changelog

\n

See the releases page for the relevant changes to the CodeQL CLI and language packs.

\n

3.31.0 - 24 Oct 2025

\n
    \n
  • Bump minimum CodeQL bundle version to 2.17.6. #3223
  • \n
  • When SARIF files are uploaded by the analyze or upload-sarif actions, the CodeQL Action automatically performs post-processing steps to prepare the data for the upload. Previously, these post-processing steps were only performed before an upload took place. We are now changing this so that the post-processing steps will always be performed, even when the SARIF files are not uploaded. This does not change anything for the upload-sarif action. For analyze, this may affect Advanced Setup for CodeQL users who specify a value other than always for the upload input. #3222
  • \n
\n

See the full CHANGELOG.md for more information.

\n

v3.30.9

\n

CodeQL Action Changelog

\n

See the releases page for the relevant changes to the CodeQL CLI and language packs.

\n

3.30.9 - 17 Oct 2025

\n
    \n
  • Update default CodeQL bundle version to 2.23.3. #3205
  • \n
  • Experimental: A new setup-codeql action has been added which is similar to init, except it only installs the CodeQL CLI and does not initialize a database. Do not use this in production as it is part of an internal experiment and subject to change at any time. #3204
  • \n
\n

See the full CHANGELOG.md for more information.

\n

v3.30.8

\n

CodeQL Action Changelog

\n

See the releases page for the relevant changes to the CodeQL CLI and language packs.

\n\n
\n

... (truncated)

\n
\n
\nChangelog\n

Sourced from github/codeql-action's changelog.

\n
\n

4.31.2 - 30 Oct 2025

\n

No user facing changes.

\n

4.31.1 - 30 Oct 2025

\n
    \n
  • The add-snippets input has been removed from the analyze action. This input has been deprecated since CodeQL Action 3.26.4 in August 2024 when this removal was announced.
  • \n
\n

4.31.0 - 24 Oct 2025

\n
    \n
  • Bump minimum CodeQL bundle version to 2.17.6. #3223
  • \n
  • When SARIF files are uploaded by the analyze or upload-sarif actions, the CodeQL Action automatically performs post-processing steps to prepare the data for the upload. Previously, these post-processing steps were only performed before an upload took place. We are now changing this so that the post-processing steps will always be performed, even when the SARIF files are not uploaded. This does not change anything for the upload-sarif action. For analyze, this may affect Advanced Setup for CodeQL users who specify a value other than always for the upload input. #3222
  • \n
\n

4.30.9 - 17 Oct 2025

\n
    \n
  • Update default CodeQL bundle version to 2.23.3. #3205
  • \n
  • Experimental: A new setup-codeql action has been added which is similar to init, except it only installs the CodeQL CLI and does not initialize a database. Do not use this in production as it is part of an internal experiment and subject to change at any time. #3204
  • \n
\n

4.30.8 - 10 Oct 2025

\n

No user facing changes.

\n

4.30.7 - 06 Oct 2025

\n
    \n
  • [v4+ only] The CodeQL Action now runs on Node.js v24. #3169
  • \n
\n

3.30.6 - 02 Oct 2025

\n
    \n
  • Update default CodeQL bundle version to 2.23.2. #3168
  • \n
\n

3.30.5 - 26 Sep 2025

\n
    \n
  • We fixed a bug that was introduced in 3.30.4 with upload-sarif which resulted in files without a .sarif extension not getting uploaded. #3160
  • \n
\n

3.30.4 - 25 Sep 2025

\n
    \n
  • We have improved the CodeQL Action's ability to validate that the workflow it is used in does not use different versions of the CodeQL Action for different workflow steps. Mixing different versions of the CodeQL Action in the same workflow is unsupported and can lead to unpredictable results. A warning will now be emitted from the codeql-action/init step if different versions of the CodeQL Action are detected in the workflow file. Additionally, an error will now be thrown by the other CodeQL Action steps if they load a configuration file that was generated by a different version of the codeql-action/init step. #3099 and #3100
  • \n
  • We added support for reducing the size of dependency caches for Java analyses, which will reduce cache usage and speed up workflows. This will be enabled automatically at a later time. #3107
  • \n
  • You can now run the latest CodeQL nightly bundle by passing tools: nightly to the init action. In general, the nightly bundle is unstable and we only recommend running it when directed by GitHub staff. #3130
  • \n
  • Update default CodeQL bundle version to 2.23.1. #3118
  • \n
\n

3.30.3 - 10 Sep 2025

\n

No user facing changes.

\n

3.30.2 - 09 Sep 2025

\n
    \n
  • Fixed a bug which could cause language autodetection to fail. #3084
  • \n
  • Experimental: The quality-queries input that was added in 3.29.2 as part of an internal experiment is now deprecated and will be removed in an upcoming version of the CodeQL Action. It has been superseded by a new analysis-kinds input, which is part of the same internal experiment. Do not use this in production as it is subject to change at any time. #3064
  • \n
\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 74c8748 Update analyze/action.yml
  • \n
  • 34c50c1 Merge pull request #3251 from github/mbg/user-error/enablement
  • \n
  • 4ae68af Warn if the add-snippets input is used
  • \n
  • 52a7bd7 Check for 403 status
  • \n
  • 194ba0e Make error message tests less brittle
  • \n
  • 53acf0b Turn enablement errors into configuration errors
  • \n
  • ac9aeee Merge pull request #3249 from github/henrymercer/api-logging
  • \n
  • d49e837 Merge branch 'main' into henrymercer/api-logging
  • \n
  • 3d988b2 Pass minimal copy of core
  • \n
  • 8cc18ac Merge pull request #3250 from github/henrymercer/prefer-fs-delete
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action&package-manager=github_actions&previous-version=3&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2159/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/hub4j/github-api/issues/2159/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2158", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2158/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2158/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2158/events", + "html_url": "https://github.com/hub4j/github-api/pull/2158", + "id": 3577020646, + "node_id": "PR_kwDOAAlq-s6w8RHt", + "number": 2158, + "title": "Chore(deps): Bump stefanzweifel/git-auto-commit-action from 6 to 7", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391660, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjYw", + "url": "https://api.github.com/repos/hub4j/github-api/labels/github_actions", + "name": "github_actions", + "color": "000000", + "default": false, + "description": "Pull requests that update Github_actions code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2025-11-01T02:00:43Z", + "updated_at": "2026-01-23T19:01:10Z", + "closed_at": "2026-01-23T19:01:02Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2158", + "html_url": "https://github.com/hub4j/github-api/pull/2158", + "diff_url": "https://github.com/hub4j/github-api/pull/2158.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2158.patch", + "merged_at": "2026-01-23T19:01:02Z" + }, + "body": "Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 6 to 7.\n
\nRelease notes\n

Sourced from stefanzweifel/git-auto-commit-action's releases.

\n
\n

v7.0.0

\n

Added

\n\n

Changed

\n\n

Dependency Updates

\n\n

v6.0.1

\n

Fixed

\n\n
\n
\n
\nChangelog\n

Sourced from stefanzweifel/git-auto-commit-action's changelog.

\n
\n

Changelog

\n

All notable changes to this project will be documented in this file.

\n

The format is based on Keep a Changelog\nand this project adheres to Semantic Versioning.

\n

Unreleased

\n
\n

TBD

\n
\n

v7.0.0 - 2025-10-12

\n

Added

\n\n

Changed

\n\n

Dependency Updates

\n\n

v6.0.1 - 2025-06-11

\n

Fixed

\n\n

v6.0.0 - 2025-06-10

\n

Added

\n
    \n
  • Throw error early if repository is in a detached state (#357)
  • \n
\n

Fixed

\n\n

Removed

\n
    \n
  • Remove support for create_branch, skip_checkout, skip_Fetch (#314)
  • \n
\n

v5.2.0 - 2025-04-19

\n

Added

\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 28e16e8 Release preparations for v7 (#394)
  • \n
  • 698fd76 Merge pull request #391 from EliasBoulharts/custom-tag-message
  • \n
  • c40819a Update README
  • \n
  • d7ee275 Change internal variable names
  • \n
  • e8684eb Fix Tests
  • \n
  • 1949701 Merge branch 'master' into pr/391
  • \n
  • a88dc49 Merge pull request #388 from stefanzweifel/v7-next
  • \n
  • a531dec Merge pull request #386 from stefanzweifel/dependabot/github_actions/actions/...
  • \n
  • acbe8b1 Merge pull request #393 from stefanzweifel/v7-warn-detached-head
  • \n
  • d185485 Enable Detached State Check
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=stefanzweifel/git-auto-commit-action&package-manager=github_actions&previous-version=6&new-version=7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
\n\n> **Note**\n> Automatic rebases have been disabled on this pull request as it has been open for over 30 days.\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2158/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/hub4j/github-api/issues/2158/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2157", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2157/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2157/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2157/events", + "html_url": "https://github.com/hub4j/github-api/pull/2157", + "id": 3577020608, + "node_id": "PR_kwDOAAlq-s6w8RHN", + "number": 2157, + "title": "Chore(deps): Bump actions/download-artifact from 5 to 6", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391660, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjYw", + "url": "https://api.github.com/repos/hub4j/github-api/labels/github_actions", + "name": "github_actions", + "color": "000000", + "default": false, + "description": "Pull requests that update Github_actions code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-11-01T02:00:40Z", + "updated_at": "2025-11-12T23:00:14Z", + "closed_at": "2025-11-12T22:59:40Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2157", + "html_url": "https://github.com/hub4j/github-api/pull/2157", + "diff_url": "https://github.com/hub4j/github-api/pull/2157.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2157.patch", + "merged_at": "2025-11-12T22:59:40Z" + }, + "body": "Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6.\n
\nRelease notes\n

Sourced from actions/download-artifact's releases.

\n
\n

v6.0.0

\n

What's Changed

\n

BREAKING CHANGE: this update supports Node v24.x. This is not a breaking change per-se but we're treating it as such.

\n\n

New Contributors

\n\n

Full Changelog: https://github.com/actions/download-artifact/compare/v5...v6.0.0

\n
\n
\n
\nCommits\n
    \n
  • 018cc2c Merge pull request #438 from actions/danwkennedy/prepare-6.0.0
  • \n
  • 815651c Revert "Remove github.dep.yml"
  • \n
  • bb3a066 Remove github.dep.yml
  • \n
  • fa1ce46 Prepare v6.0.0
  • \n
  • 4a24838 Merge pull request #431 from danwkennedy/patch-1
  • \n
  • 5e3251c Readme: spell out the first use of GHES
  • \n
  • abefc31 Merge pull request #424 from actions/yacaovsnc/update_readme
  • \n
  • ac43a60 Update README with artifact extraction details
  • \n
  • de96f46 Merge pull request #417 from actions/yacaovsnc/update_readme
  • \n
  • 7993cb4 Remove migration guide for artifact download changes
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/download-artifact&package-manager=github_actions&previous-version=5&new-version=6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2157/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/hub4j/github-api/issues/2157/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2152", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2152/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2152/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2152/events", + "html_url": "https://github.com/hub4j/github-api/pull/2152", + "id": 3546846920, + "node_id": "PR_kwDOAAlq-s6vYTSI", + "number": 2152, + "title": "Improve ArchUnit class name test", + "user": { + "login": "bitwiseman", + "id": 1958953, + "node_id": "MDQ6VXNlcjE5NTg5NTM=", + "avatar_url": "https://avatars.githubusercontent.com/u/1958953?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bitwiseman", + "html_url": "https://github.com/bitwiseman", + "followers_url": "https://api.github.com/users/bitwiseman/followers", + "following_url": "https://api.github.com/users/bitwiseman/following{/other_user}", + "gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions", + "organizations_url": "https://api.github.com/users/bitwiseman/orgs", + "repos_url": "https://api.github.com/users/bitwiseman/repos", + "events_url": "https://api.github.com/users/bitwiseman/events{/privacy}", + "received_events_url": "https://api.github.com/users/bitwiseman/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-10-23T22:37:24Z", + "updated_at": "2025-10-24T15:20:33Z", + "closed_at": "2025-10-24T15:20:20Z", + "author_association": "MEMBER", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2152", + "html_url": "https://github.com/hub4j/github-api/pull/2152", + "diff_url": "https://github.com/hub4j/github-api/pull/2152.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2152.patch", + "merged_at": "2025-10-24T15:20:20Z" + }, + "body": "# Description\r\n\r\n\r\n\r\n# Before submitting a PR:\r\n\r\n- [ ] Changes must not break binary backwards compatibility. If you are unclear on how to make the change you think is needed while maintaining backward compatibility, [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [ ] Add JavaDocs and other comments explaining the behavior. \r\n- [ ] When adding or updating methods that fetch entities, add `@link` JavaDoc entries to the relevant documentation on https://docs.github.com/en/rest . \r\n- [ ] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [ ] Run `mvn -D enable-ci clean install site \"-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED\"` locally. If this command doesn't succeed, your change will not pass CI.\r\n- [ ] Push your changes to a branch other than `main`. You will create your PR from that branch.\r\n\r\n# When creating a PR: \r\n\r\n- [ ] Fill in the \"Description\" above with clear summary of the changes. This includes:\r\n - [ ] If this PR fixes one or more issues, include \"Fixes #\" lines for each issue. \r\n - [ ] Provide links to relevant documentation on https://docs.github.com/en/rest where possible. If not including links, explain why not.\r\n- [ ] All lines of new code should be covered by tests as reported by code coverage. Any lines that are not covered must have PR comments explaining why they cannot be covered. For example, \"Reaching this particular exception is hard and is not a particular common scenario.\"\r\n- [ ] Enable \"Allow edits from maintainers\".\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2152/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/hub4j/github-api/issues/2152/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2151", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2151/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2151/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2151/events", + "html_url": "https://github.com/hub4j/github-api/pull/2151", + "id": 3495810417, + "node_id": "PR_kwDOAAlq-s6suMRC", + "number": 2151, + "title": "Add DYNAMIC event type to GHEvent enum", + "user": { + "login": "kkroner8451", + "id": 14809736, + "node_id": "MDQ6VXNlcjE0ODA5NzM2", + "avatar_url": "https://avatars.githubusercontent.com/u/14809736?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/kkroner8451", + "html_url": "https://github.com/kkroner8451", + "followers_url": "https://api.github.com/users/kkroner8451/followers", + "following_url": "https://api.github.com/users/kkroner8451/following{/other_user}", + "gists_url": "https://api.github.com/users/kkroner8451/gists{/gist_id}", + "starred_url": "https://api.github.com/users/kkroner8451/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/kkroner8451/subscriptions", + "organizations_url": "https://api.github.com/users/kkroner8451/orgs", + "repos_url": "https://api.github.com/users/kkroner8451/repos", + "events_url": "https://api.github.com/users/kkroner8451/events{/privacy}", + "received_events_url": "https://api.github.com/users/kkroner8451/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-10-08T14:57:53Z", + "updated_at": "2025-10-23T00:51:40Z", + "closed_at": "2025-10-23T00:51:32Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2151", + "html_url": "https://github.com/hub4j/github-api/pull/2151", + "diff_url": "https://github.com/hub4j/github-api/pull/2151.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2151.patch", + "merged_at": "2025-10-23T00:51:32Z" + }, + "body": "\r\n# Description\r\n\r\nAdded `DYNAMIC` event type to GHEvent enum for handling new `dynamic` events. \r\nFixes #2150 \r\n\r\n- No docs linked as I can't find any published docs, but looking at the data it appears to be dynamic runs of workflows for things like Dependabot, etc. \r\n- No tests added as not all enum values currently had unit tests.\r\n\r\n# Before submitting a PR:\r\n\r\n- [x] Changes must not break binary backwards compatibility. If you are unclear on how to make the change you think is needed while maintaining backward compatibility, [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Add JavaDocs and other comments explaining the behavior. \r\n- [x] When adding or updating methods that fetch entities, add `@link` JavaDoc entries to the relevant documentation on https://docs.github.com/en/rest . \r\n- [x] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Run `mvn -D enable-ci clean install site \"-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED\"` locally. If this command doesn't succeed, your change will not pass CI.\r\n- [x] Push your changes to a branch other than `main`. You will create your PR from that branch.\r\n\r\n# When creating a PR: \r\n\r\n- [x] Fill in the \"Description\" above with clear summary of the changes. This includes:\r\n - [x] If this PR fixes one or more issues, include \"Fixes #\" lines for each issue. \r\n - [x] Provide links to relevant documentation on https://docs.github.com/en/rest where possible. If not including links, explain why not.\r\n- [x] All lines of new code should be covered by tests as reported by code coverage. Any lines that are not covered must have PR comments explaining why they cannot be covered. For example, \"Reaching this particular exception is hard and is not a particular common scenario.\"\r\n- [x] Enable \"Allow edits from maintainers\".\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2151/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/hub4j/github-api/issues/2151/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2149", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2149/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2149/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2149/events", + "html_url": "https://github.com/hub4j/github-api/pull/2149", + "id": 3471705142, + "node_id": "PR_kwDOAAlq-s6rdSoE", + "number": 2149, + "title": "Chore(deps): Bump org.apache.commons:commons-lang3 from 3.18.0 to 3.19.0", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-10-01T02:01:20Z", + "updated_at": "2025-10-23T00:59:43Z", + "closed_at": "2025-10-23T00:59:35Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2149", + "html_url": "https://github.com/hub4j/github-api/pull/2149", + "diff_url": "https://github.com/hub4j/github-api/pull/2149.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2149.patch", + "merged_at": "2025-10-23T00:59:35Z" + }, + "body": "Bumps org.apache.commons:commons-lang3 from 3.18.0 to 3.19.0.\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.apache.commons:commons-lang3&package-manager=maven&previous-version=3.18.0&new-version=3.19.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2149/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/hub4j/github-api/issues/2149/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2148", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2148/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2148/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2148/events", + "html_url": "https://github.com/hub4j/github-api/pull/2148", + "id": 3471704908, + "node_id": "PR_kwDOAAlq-s6rdSkx", + "number": 2148, + "title": "Chore(deps): Bump org.junit:junit-bom from 5.13.4 to 6.0.0", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391625, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjI1", + "url": "https://api.github.com/repos/hub4j/github-api/labels/java", + "name": "java", + "color": "ffa221", + "default": false, + "description": "Pull requests that update Java code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 2, + "created_at": "2025-10-01T02:01:14Z", + "updated_at": "2025-10-23T00:59:15Z", + "closed_at": "2025-10-23T00:58:56Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2148", + "html_url": "https://github.com/hub4j/github-api/pull/2148", + "diff_url": "https://github.com/hub4j/github-api/pull/2148.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2148.patch", + "merged_at": null + }, + "body": "Bumps [org.junit:junit-bom](https://github.com/junit-team/junit-framework) from 5.13.4 to 6.0.0.\n
\nRelease notes\n

Sourced from org.junit:junit-bom's releases.

\n
\n

JUnit 6.0.0 = Platform 6.0.0 + Jupiter 6.0.0 + Vintage 6.0.0

\n

See Release Notes.

\n

New Contributors

\n\n

Full Changelog: https://github.com/junit-team/junit-framework/compare/r5.14.0...r6.0.0

\n

JUnit 6.0.0-RC3 = Platform 6.0.0-RC3 + Jupiter 6.0.0-RC3 + Vintage 6.0.0-RC3

\n

See Release Notes.

\n

New Contributors

\n\n

Full Changelog: https://github.com/junit-team/junit-framework/compare/r6.0.0-RC2...r6.0.0-RC3

\n

JUnit 6.0.0-RC2 = Platform 6.0.0-RC2 + Jupiter 6.0.0-RC2 + Vintage 6.0.0-RC2

\n

See Release Notes.

\n

Full Changelog: https://github.com/junit-team/junit-framework/compare/r6.0.0-RC1...r6.0.0-RC2

\n

JUnit 6.0.0-RC1 = Platform 6.0.0-RC1 + Jupiter 6.0.0-RC1 + Vintage 6.0.0-RC1

\n

See Release Notes.

\n

New Contributors

\n\n

Full Changelog: https://github.com/junit-team/junit-framework/compare/r6.0.0-M2...r6.0.0-RC1

\n

JUnit 6.0.0-M2 = Platform 6.0.0-M2 + Jupiter 6.0.0-M2 + Vintage 6.0.0-M2

\n

See Release Notes.

\n

New Contributors

\n\n

Full Changelog: https://github.com/junit-team/junit-framework/compare/r6.0.0-M1...r6.0.0-M2

\n\n
\n

... (truncated)

\n
\n
\nCommits\n
    \n
  • 4f79594 Release 6.0.0
  • \n
  • 55af30a Revert "Use develop/6.x branch for junit-examples during release build"
  • \n
  • df3cfdd Release 5.14.0
  • \n
  • fcb84a2 Disable backward compatibility check when offline
  • \n
  • c9c8344 Prune 5.14.0 release notes
  • \n
  • 03d8a72 Update broken link to using API Gaurdian with bndtools
  • \n
  • 3a0b29b Use temporary JUnit 6 logo
  • \n
  • 6603caa Rename eclipseClasspath to eclipseConventions to avoid confusion
  • \n
  • ab3470b Make sealed MediaType work in Eclipse
  • \n
  • a8cd41e Remove annotations not visible in Eclipse
  • \n
  • Additional commits viewable in compare view
  • \n
\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.junit:junit-bom&package-manager=maven&previous-version=5.13.4&new-version=6.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2148/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/hub4j/github-api/issues/2148/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2147", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2147/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2147/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2147/events", + "html_url": "https://github.com/hub4j/github-api/pull/2147", + "id": 3471704649, + "node_id": "PR_kwDOAAlq-s6rdShP", + "number": 2147, + "title": "Chore(deps): Bump codecov/codecov-action from 5.5.0 to 5.5.1", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 1576019209, + "node_id": "MDU6TGFiZWwxNTc2MDE5MjA5", + "url": "https://api.github.com/repos/hub4j/github-api/labels/dependencies", + "name": "dependencies", + "color": "0366d6", + "default": false, + "description": "Pull requests that update a dependency file" + }, + { + "id": 2156391660, + "node_id": "MDU6TGFiZWwyMTU2MzkxNjYw", + "url": "https://api.github.com/repos/hub4j/github-api/labels/github_actions", + "name": "github_actions", + "color": "000000", + "default": false, + "description": "Pull requests that update Github_actions code" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-10-01T02:01:07Z", + "updated_at": "2025-10-23T01:00:05Z", + "closed_at": "2025-10-23T00:59:57Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2147", + "html_url": "https://github.com/hub4j/github-api/pull/2147", + "diff_url": "https://github.com/hub4j/github-api/pull/2147.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2147.patch", + "merged_at": "2025-10-23T00:59:57Z" + }, + "body": "Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.5.0 to 5.5.1.\n
\nRelease notes\n

Sourced from codecov/codecov-action's releases.

\n
\n

v5.5.1

\n

What's Changed

\n\n

New Contributors

\n\n

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.5.0...v5.5.1

\n
\n
\n
\nChangelog\n

Sourced from codecov/codecov-action's changelog.

\n
\n

v5.5.1

\n

What's Changed

\n\n

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.5.0..v5.5.1

\n
\n
\n
\nCommits\n\n
\n
\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=codecov/codecov-action&package-manager=github_actions&previous-version=5.5.0&new-version=5.5.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nYou can trigger a rebase of this PR by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n
\nDependabot commands and options\n
\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n
", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2147/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/hub4j/github-api/issues/2147/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2143", + "repository_url": "https://api.github.com/repos/hub4j/github-api", + "labels_url": "https://api.github.com/repos/hub4j/github-api/issues/2143/labels{/name}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/issues/2143/comments", + "events_url": "https://api.github.com/repos/hub4j/github-api/issues/2143/events", + "html_url": "https://github.com/hub4j/github-api/pull/2143", + "id": 3387881462, + "node_id": "PR_kwDOAAlq-s6nEJSs", + "number": 2143, + "title": "feat: add force-cancel workflow run", + "user": { + "login": "cyrilico", + "id": 19289022, + "node_id": "MDQ6VXNlcjE5Mjg5MDIy", + "avatar_url": "https://avatars.githubusercontent.com/u/19289022?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/cyrilico", + "html_url": "https://github.com/cyrilico", + "followers_url": "https://api.github.com/users/cyrilico/followers", + "following_url": "https://api.github.com/users/cyrilico/following{/other_user}", + "gists_url": "https://api.github.com/users/cyrilico/gists{/gist_id}", + "starred_url": "https://api.github.com/users/cyrilico/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/cyrilico/subscriptions", + "organizations_url": "https://api.github.com/users/cyrilico/orgs", + "repos_url": "https://api.github.com/users/cyrilico/repos", + "events_url": "https://api.github.com/users/cyrilico/events{/privacy}", + "received_events_url": "https://api.github.com/users/cyrilico/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2025-09-05T15:06:56Z", + "updated_at": "2025-09-06T21:04:17Z", + "closed_at": "2025-09-06T19:47:04Z", + "author_association": "CONTRIBUTOR", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/hub4j/github-api/pulls/2143", + "html_url": "https://github.com/hub4j/github-api/pull/2143", + "diff_url": "https://github.com/hub4j/github-api/pull/2143.diff", + "patch_url": "https://github.com/hub4j/github-api/pull/2143.patch", + "merged_at": "2025-09-06T19:47:04Z" + }, + "body": "# Description\r\n\r\nPretty similar to the existing `cancel` method, but the forced version provided by Github: https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#force-cancel-a-workflow-run\r\n\r\nWe've been using this library extensively and we have a valid use case for force-cancel. This way we don't have go navigate around the library just for this request.\r\n\r\n# Before submitting a PR:\r\n\r\n- [x] Changes must not break binary backwards compatibility. If you are unclear on how to make the change you think is needed while maintaining backward compatibility, [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Add JavaDocs and other comments explaining the behavior. \r\n- [x] When adding or updating methods that fetch entities, add `@link` JavaDoc entries to the relevant documentation on https://docs.github.com/en/rest . \r\n- [x] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.\r\n- [x] Run `mvn -D enable-ci clean install site \"-Dsurefire.argLine=--add-opens java.base/java.net=ALL-UNNAMED\"` locally. If this command doesn't succeed, your change will not pass CI.\r\n- [x] Push your changes to a branch other than `main`. You will create your PR from that branch.\r\n\r\n# When creating a PR: \r\n\r\n- [x] Fill in the \"Description\" above with clear summary of the changes. This includes:\r\n - [x] If this PR fixes one or more issues, include \"Fixes #\" lines for each issue. \r\n - [x] Provide links to relevant documentation on https://docs.github.com/en/rest where possible. If not including links, explain why not.\r\n- [x] All lines of new code should be covered by tests as reported by code coverage. Any lines that are not covered must have PR comments explaining why they cannot be covered. For example, \"Reaching this particular exception is hard and is not a particular common scenario.\"\r\n- [x] Enable \"Allow edits from maintainers\".\r\n", + "reactions": { + "url": "https://api.github.com/repos/hub4j/github-api/issues/2143/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/hub4j/github-api/issues/2143/timeline", + "performed_via_github_app": null, + "state_reason": null, + "score": 1 + } + ] +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchPullRequestsOnly/mappings/1-user.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchPullRequestsOnly/mappings/1-user.json new file mode 100644 index 0000000000..d5ecefd851 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchPullRequestsOnly/mappings/1-user.json @@ -0,0 +1,48 @@ +{ + "id": "a17f3938-e65d-490c-9039-255407e5c1d8", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "1-user.json", + "headers": { + "Date": "Tue, 10 Feb 2026 20:15:09 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"15d7e1ad92a3639b979fc55254902e63ee0bfa5c8f6766990bf989044d491ce1\"", + "Last-Modified": "Sat, 24 Jan 2026 22:07:12 GMT", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4903", + "X-RateLimit-Reset": "1770755531", + "X-RateLimit-Used": "97", + "X-RateLimit-Resource": "core", + "X-GitHub-Request-Id": "FC9A:50C44:9D92423:92184CD:698B91CD" + } + }, + "uuid": "a17f3938-e65d-490c-9039-255407e5c1d8", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchPullRequestsOnly/mappings/2-search_issues.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchPullRequestsOnly/mappings/2-search_issues.json new file mode 100644 index 0000000000..b683ec231d --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchPullRequestsOnly/mappings/2-search_issues.json @@ -0,0 +1,50 @@ +{ + "id": "29a7effd-f202-484d-852a-cdcaac4a38b6", + "name": "search_issues", + "request": { + "url": "/search/issues?sort=created&q=repo%3Ahub4j%2Fgithub-api+is%3Apr+is%3Aclosed", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "2-search_issues.json", + "headers": { + "Date": "Tue, 10 Feb 2026 20:15:10 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "no-cache", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "30", + "X-RateLimit-Remaining": "25", + "X-RateLimit-Reset": "1770754529", + "X-RateLimit-Used": "5", + "X-RateLimit-Resource": "search", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-GitHub-Request-Id": "FC9D:312B30:9B9970D:900825A:698B91CE", + "Link": "; rel=\"next\", ; rel=\"last\"" + } + }, + "uuid": "29a7effd-f202-484d-852a-cdcaac4a38b6", + "persistent": true, + "scenarioName": "scenario-1-search-issues", + "requiredScenarioState": "Started", + "newScenarioState": "scenario-1-search-issues-2", + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchPullRequestsOnly/mappings/3-search_issues.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchPullRequestsOnly/mappings/3-search_issues.json new file mode 100644 index 0000000000..9c88f073b6 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testIssueSearchPullRequestsOnly/mappings/3-search_issues.json @@ -0,0 +1,49 @@ +{ + "id": "79607d8c-24ba-464b-a958-aae8c88d16bd", + "name": "search_issues", + "request": { + "url": "/search/issues?sort=created&q=repo%3Ahub4j%2Fgithub-api+is%3Apr+is%3Aclosed", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "3-search_issues.json", + "headers": { + "Date": "Tue, 10 Feb 2026 20:15:11 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "no-cache", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "30", + "X-RateLimit-Remaining": "24", + "X-RateLimit-Reset": "1770754529", + "X-RateLimit-Used": "6", + "X-RateLimit-Resource": "search", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-GitHub-Request-Id": "FC9E:366D7C:9C761D0:90EB6B2:698B91CF", + "Link": "; rel=\"next\", ; rel=\"last\"" + } + }, + "uuid": "79607d8c-24ba-464b-a958-aae8c88d16bd", + "persistent": true, + "scenarioName": "scenario-1-search-issues", + "requiredScenarioState": "scenario-1-search-issues-2", + "insertionIndex": 3 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/__files/1-user.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/__files/1-user.json new file mode 100644 index 0000000000..98e8c76ba2 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/__files/1-user.json @@ -0,0 +1,45 @@ +{ + "login": "bitwiseman", + "id": 1958953, + "node_id": "MDQ6VXNlcjE5NTg5NTM=", + "avatar_url": "https://avatars3.githubusercontent.com/u/1958953?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bitwiseman", + "html_url": "https://github.com/bitwiseman", + "followers_url": "https://api.github.com/users/bitwiseman/followers", + "following_url": "https://api.github.com/users/bitwiseman/following{/other_user}", + "gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions", + "organizations_url": "https://api.github.com/users/bitwiseman/orgs", + "repos_url": "https://api.github.com/users/bitwiseman/repos", + "events_url": "https://api.github.com/users/bitwiseman/events{/privacy}", + "received_events_url": "https://api.github.com/users/bitwiseman/received_events", + "type": "User", + "site_admin": false, + "name": "Liam Newman", + "company": "Cloudbees, Inc.", + "blog": "", + "location": "Seattle, WA, USA", + "email": "bitwiseman@gmail.com", + "hireable": null, + "bio": "https://twitter.com/bitwiseman", + "public_repos": 178, + "public_gists": 7, + "followers": 144, + "following": 9, + "created_at": "2012-07-11T20:38:33Z", + "updated_at": "2019-12-18T01:26:55Z", + "private_gists": 7, + "total_private_repos": 10, + "owned_private_repos": 0, + "disk_usage": 33697, + "collaborators": 0, + "two_factor_authentication": true, + "plan": { + "name": "free", + "space": 976562499, + "collaborators": 0, + "private_repos": 10000 + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/__files/2-r_h_ghcontentintegrationtest.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/__files/2-r_h_ghcontentintegrationtest.json new file mode 100644 index 0000000000..68e3d4f428 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/__files/2-r_h_ghcontentintegrationtest.json @@ -0,0 +1,313 @@ +{ + "id": 40763577, + "node_id": "MDEwOlJlcG9zaXRvcnk0MDc2MzU3Nw==", + "name": "GHContentIntegrationTest", + "full_name": "hub4j-test-org/GHContentIntegrationTest", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest", + "description": "Repository used for integration test of github-api", + "fork": true, + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest", + "forks_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/deployments", + "created_at": "2015-08-15T14:14:57Z", + "updated_at": "2019-11-26T01:09:49Z", + "pushed_at": "2019-11-26T01:09:48Z", + "git_url": "git://github.com/hub4j-test-org/GHContentIntegrationTest.git", + "ssh_url": "git@github.com:hub4j-test-org/GHContentIntegrationTest.git", + "clone_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest.git", + "svn_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest", + "homepage": null, + "size": 52, + "stargazers_count": 1, + "watchers_count": 1, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 41, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 41, + "open_issues": 0, + "watchers": 1, + "default_branch": "main", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "organization": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "parent": { + "id": 19653852, + "node_id": "MDEwOlJlcG9zaXRvcnkxOTY1Mzg1Mg==", + "name": "GHContentIntegrationTest", + "full_name": "kohsuke2/GHContentIntegrationTest", + "private": false, + "owner": { + "login": "kohsuke2", + "id": 1329242, + "node_id": "MDQ6VXNlcjEzMjkyNDI=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1329242?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/kohsuke2", + "html_url": "https://github.com/kohsuke2", + "followers_url": "https://api.github.com/users/kohsuke2/followers", + "following_url": "https://api.github.com/users/kohsuke2/following{/other_user}", + "gists_url": "https://api.github.com/users/kohsuke2/gists{/gist_id}", + "starred_url": "https://api.github.com/users/kohsuke2/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/kohsuke2/subscriptions", + "organizations_url": "https://api.github.com/users/kohsuke2/orgs", + "repos_url": "https://api.github.com/users/kohsuke2/repos", + "events_url": "https://api.github.com/users/kohsuke2/events{/privacy}", + "received_events_url": "https://api.github.com/users/kohsuke2/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/kohsuke2/GHContentIntegrationTest", + "description": "Repository used for integration test of github-api", + "fork": true, + "url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest", + "forks_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/forks", + "keys_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/teams", + "hooks_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/hooks", + "issue_events_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/events", + "assignees_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/tags", + "blobs_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/languages", + "stargazers_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/stargazers", + "contributors_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/contributors", + "subscribers_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/subscribers", + "subscription_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/subscription", + "commits_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/merges", + "archive_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/downloads", + "issues_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/labels{/name}", + "releases_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/deployments", + "created_at": "2014-05-10T22:50:30Z", + "updated_at": "2018-11-07T15:36:19Z", + "pushed_at": "2018-11-07T15:36:18Z", + "git_url": "git://github.com/kohsuke2/GHContentIntegrationTest.git", + "ssh_url": "git@github.com:kohsuke2/GHContentIntegrationTest.git", + "clone_url": "https://github.com/kohsuke2/GHContentIntegrationTest.git", + "svn_url": "https://github.com/kohsuke2/GHContentIntegrationTest", + "homepage": null, + "size": 111, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 1, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 1, + "open_issues": 0, + "watchers": 0, + "default_branch": "main" + }, + "source": { + "id": 14779458, + "node_id": "MDEwOlJlcG9zaXRvcnkxNDc3OTQ1OA==", + "name": "github-api-test-1", + "full_name": "farmdawgnation/github-api-test-1", + "private": false, + "owner": { + "login": "farmdawgnation", + "id": 620189, + "node_id": "MDQ6VXNlcjYyMDE4OQ==", + "avatar_url": "https://avatars2.githubusercontent.com/u/620189?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/farmdawgnation", + "html_url": "https://github.com/farmdawgnation", + "followers_url": "https://api.github.com/users/farmdawgnation/followers", + "following_url": "https://api.github.com/users/farmdawgnation/following{/other_user}", + "gists_url": "https://api.github.com/users/farmdawgnation/gists{/gist_id}", + "starred_url": "https://api.github.com/users/farmdawgnation/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/farmdawgnation/subscriptions", + "organizations_url": "https://api.github.com/users/farmdawgnation/orgs", + "repos_url": "https://api.github.com/users/farmdawgnation/repos", + "events_url": "https://api.github.com/users/farmdawgnation/events{/privacy}", + "received_events_url": "https://api.github.com/users/farmdawgnation/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/farmdawgnation/github-api-test-1", + "description": "Repository used for integration test of github-api", + "fork": false, + "url": "https://api.github.com/repos/farmdawgnation/github-api-test-1", + "forks_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/forks", + "keys_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/teams", + "hooks_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/hooks", + "issue_events_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues/events{/number}", + "events_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/events", + "assignees_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/assignees{/user}", + "branches_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/branches{/branch}", + "tags_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/tags", + "blobs_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/statuses/{sha}", + "languages_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/languages", + "stargazers_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/stargazers", + "contributors_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/contributors", + "subscribers_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/subscribers", + "subscription_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/subscription", + "commits_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/contents/{+path}", + "compare_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/merges", + "archive_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/downloads", + "issues_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues{/number}", + "pulls_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/pulls{/number}", + "milestones_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/milestones{/number}", + "notifications_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/labels{/name}", + "releases_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/releases{/id}", + "deployments_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/deployments", + "created_at": "2013-11-28T14:46:38Z", + "updated_at": "2016-02-05T13:33:23Z", + "pushed_at": "2013-11-28T14:55:36Z", + "git_url": "git://github.com/farmdawgnation/github-api-test-1.git", + "ssh_url": "git@github.com:farmdawgnation/github-api-test-1.git", + "clone_url": "https://github.com/farmdawgnation/github-api-test-1.git", + "svn_url": "https://github.com/farmdawgnation/github-api-test-1", + "homepage": null, + "size": 89, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 60, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 60, + "open_issues": 0, + "watchers": 0, + "default_branch": "main" + }, + "network_count": 60, + "subscribers_count": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/__files/3-r_h_temp-testCreateWithAuthorCommitter.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/__files/3-r_h_temp-testCreateWithAuthorCommitter.json new file mode 100644 index 0000000000..afa1408145 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/__files/3-r_h_temp-testCreateWithAuthorCommitter.json @@ -0,0 +1,71 @@ +{ + "id": 999000001, + "node_id": "MDEwOlJlcG9zaXRvcnk5OTkwMDAwMDE=", + "name": "temp-testCreateWithAuthorCommitter", + "full_name": "hub4j-test-org/temp-testCreateWithAuthorCommitter", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/temp-testCreateWithAuthorCommitter", + "description": "A test repository for testing the github-api project: temp-testCreateWithAuthorCommitter", + "fork": false, + "url": "https://api.github.com/repos/hub4j-test-org/temp-testCreateWithAuthorCommitter", + "contents_url": "https://api.github.com/repos/hub4j-test-org/temp-testCreateWithAuthorCommitter/contents/{+path}", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z", + "pushed_at": "2024-01-01T00:00:00Z", + "git_url": "git://github.com/hub4j-test-org/temp-testCreateWithAuthorCommitter.git", + "ssh_url": "git@github.com:hub4j-test-org/temp-testCreateWithAuthorCommitter.git", + "clone_url": "https://github.com/hub4j-test-org/temp-testCreateWithAuthorCommitter.git", + "svn_url": "https://github.com/hub4j-test-org/temp-testCreateWithAuthorCommitter", + "homepage": "http://github-api.kohsuke.org/", + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "main", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "organization": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "type": "Organization", + "site_admin": false + }, + "network_count": 0, + "subscribers_count": 1 +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/__files/4-r_h_t_contents_author-committer-testmd.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/__files/4-r_h_t_contents_author-committer-testmd.json new file mode 100644 index 0000000000..d062b7b019 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/__files/4-r_h_t_contents_author-committer-testmd.json @@ -0,0 +1,52 @@ +{ + "content": { + "name": "author-committer-test.md", + "path": "author-committer-test.md", + "sha": "aabbccdd11223344556677889900aabbccddeeff", + "size": 13, + "url": "https://api.github.com/repos/hub4j-test-org/temp-testCreateWithAuthorCommitter/contents/author-committer-test.md?ref=main", + "html_url": "https://github.com/hub4j-test-org/temp-testCreateWithAuthorCommitter/blob/main/author-committer-test.md", + "git_url": "https://api.github.com/repos/hub4j-test-org/temp-testCreateWithAuthorCommitter/git/blobs/aabbccdd11223344556677889900aabbccddeeff", + "download_url": "https://raw.githubusercontent.com/hub4j-test-org/temp-testCreateWithAuthorCommitter/main/author-committer-test.md", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/hub4j-test-org/temp-testCreateWithAuthorCommitter/contents/author-committer-test.md?ref=main", + "git": "https://api.github.com/repos/hub4j-test-org/temp-testCreateWithAuthorCommitter/git/blobs/aabbccdd11223344556677889900aabbccddeeff", + "html": "https://github.com/hub4j-test-org/temp-testCreateWithAuthorCommitter/blob/main/author-committer-test.md" + } + }, + "commit": { + "sha": "1122334455667788990011223344556677889900", + "node_id": "MDY6Q29tbWl0OTk5MDAwMDAxOjExMjIzMzQ0NTU2Njc3ODg5OTAwMTEyMjMzNDQ1NTY2Nzc4ODk5MDA=", + "url": "https://api.github.com/repos/hub4j-test-org/temp-testCreateWithAuthorCommitter/git/commits/1122334455667788990011223344556677889900", + "html_url": "https://github.com/hub4j-test-org/temp-testCreateWithAuthorCommitter/commit/1122334455667788990011223344556677889900", + "author": { + "name": "John Doe", + "email": "john@example.com", + "date": "2024-01-01T00:00:00Z" + }, + "committer": { + "name": "Service Account", + "email": "service@example.com", + "date": "2024-01-01T00:00:00Z" + }, + "tree": { + "sha": "aabb00112233445566778899aabb00112233ccdd", + "url": "https://api.github.com/repos/hub4j-test-org/temp-testCreateWithAuthorCommitter/git/trees/aabb00112233445566778899aabb00112233ccdd" + }, + "message": "Creating with custom author and committer", + "parents": [ + { + "sha": "0000111122223333444455556666777788889999", + "url": "https://api.github.com/repos/hub4j-test-org/temp-testCreateWithAuthorCommitter/git/commits/0000111122223333444455556666777788889999", + "html_url": "https://github.com/hub4j-test-org/temp-testCreateWithAuthorCommitter/commit/0000111122223333444455556666777788889999" + } + ], + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null + } + } +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/mappings/1-user.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/mappings/1-user.json new file mode 100644 index 0000000000..867e80d5f8 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/mappings/1-user.json @@ -0,0 +1,48 @@ +{ + "id": "b9d02cd3-311a-4d49-8de8-aa4220400616", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "1-user.json", + "headers": { + "Date": "Sat, 21 Dec 2019 03:41:23 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4985", + "X-RateLimit-Reset": "1576903221", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding" + ], + "ETag": "W/\"be4370b3c906450f450e411f567ee839\"", + "Last-Modified": "Wed, 18 Dec 2019 01:26:55 GMT", + "X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "unknown, github.v3", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "C977:4ACE:948C05:B32660:5DFD9463" + } + }, + "uuid": "b9d02cd3-311a-4d49-8de8-aa4220400616", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/mappings/2-r_h_ghcontentintegrationtest.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/mappings/2-r_h_ghcontentintegrationtest.json new file mode 100644 index 0000000000..a96af5128d --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/mappings/2-r_h_ghcontentintegrationtest.json @@ -0,0 +1,48 @@ +{ + "id": "f60bd9fd-10cc-4488-b2ce-b618cac83ae2", + "name": "repos_hub4j-test-org_ghcontentintegrationtest", + "request": { + "url": "/repos/hub4j-test-org/GHContentIntegrationTest", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "2-r_h_ghcontentintegrationtest.json", + "headers": { + "Date": "Sat, 21 Dec 2019 03:41:25 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4981", + "X-RateLimit-Reset": "1576903221", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding" + ], + "ETag": "W/\"1e430d4199aa33f3d4673fee6fee2709\"", + "Last-Modified": "Tue, 26 Nov 2019 01:09:49 GMT", + "X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion", + "X-Accepted-OAuth-Scopes": "repo", + "X-GitHub-Media-Type": "unknown, github.v3", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "C977:4ACE:948C3A:B32669:5DFD9463" + } + }, + "uuid": "f60bd9fd-10cc-4488-b2ce-b618cac83ae2", + "persistent": true, + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/mappings/3-r_h_temp-testCreateWithAuthorCommitter.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/mappings/3-r_h_temp-testCreateWithAuthorCommitter.json new file mode 100644 index 0000000000..3786f6f96f --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/mappings/3-r_h_temp-testCreateWithAuthorCommitter.json @@ -0,0 +1,24 @@ +{ + "id": "a1b2c3d4-e5f6-7890-abcd-100000000001", + "name": "repos_hub4j-test-org_temp-testCreateWithAuthorCommitter", + "request": { + "url": "/repos/hub4j-test-org/temp-testCreateWithAuthorCommitter", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "3-r_h_temp-testCreateWithAuthorCommitter.json", + "headers": { + "Content-Type": "application/json; charset=utf-8", + "Status": "200 OK" + } + }, + "uuid": "a1b2c3d4-e5f6-7890-abcd-100000000001", + "persistent": true, + "insertionIndex": 3 +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/mappings/4-r_h_t_contents_author-committer-testmd.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/mappings/4-r_h_t_contents_author-committer-testmd.json new file mode 100644 index 0000000000..8088c308a8 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitter/mappings/4-r_h_t_contents_author-committer-testmd.json @@ -0,0 +1,31 @@ +{ + "id": "a1b2c3d4-e5f6-7890-abcd-100000000002", + "name": "repos_hub4j-test-org_temp-testCreateWithAuthorCommitter_contents_author-committer-testmd", + "request": { + "url": "/repos/hub4j-test-org/temp-testCreateWithAuthorCommitter/contents/author-committer-test.md", + "method": "PUT", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{\"path\":\"author-committer-test.md\",\"message\":\"Creating with custom author and committer\",\"content\":\"dGVzdCBjb250ZW50Cg==\",\"author\":{\"name\":\"John Doe\",\"email\":\"john@example.com\"},\"committer\":{\"name\":\"Service Account\",\"email\":\"service@example.com\"}}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 201, + "bodyFileName": "4-r_h_t_contents_author-committer-testmd.json", + "headers": { + "Content-Type": "application/json; charset=utf-8", + "Status": "201 Created" + } + }, + "uuid": "a1b2c3d4-e5f6-7890-abcd-100000000002", + "persistent": true, + "insertionIndex": 4 +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/__files/1-user.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/__files/1-user.json new file mode 100644 index 0000000000..98e8c76ba2 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/__files/1-user.json @@ -0,0 +1,45 @@ +{ + "login": "bitwiseman", + "id": 1958953, + "node_id": "MDQ6VXNlcjE5NTg5NTM=", + "avatar_url": "https://avatars3.githubusercontent.com/u/1958953?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bitwiseman", + "html_url": "https://github.com/bitwiseman", + "followers_url": "https://api.github.com/users/bitwiseman/followers", + "following_url": "https://api.github.com/users/bitwiseman/following{/other_user}", + "gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions", + "organizations_url": "https://api.github.com/users/bitwiseman/orgs", + "repos_url": "https://api.github.com/users/bitwiseman/repos", + "events_url": "https://api.github.com/users/bitwiseman/events{/privacy}", + "received_events_url": "https://api.github.com/users/bitwiseman/received_events", + "type": "User", + "site_admin": false, + "name": "Liam Newman", + "company": "Cloudbees, Inc.", + "blog": "", + "location": "Seattle, WA, USA", + "email": "bitwiseman@gmail.com", + "hireable": null, + "bio": "https://twitter.com/bitwiseman", + "public_repos": 178, + "public_gists": 7, + "followers": 144, + "following": 9, + "created_at": "2012-07-11T20:38:33Z", + "updated_at": "2019-12-18T01:26:55Z", + "private_gists": 7, + "total_private_repos": 10, + "owned_private_repos": 0, + "disk_usage": 33697, + "collaborators": 0, + "two_factor_authentication": true, + "plan": { + "name": "free", + "space": 976562499, + "collaborators": 0, + "private_repos": 10000 + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/__files/2-r_h_ghcontentintegrationtest.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/__files/2-r_h_ghcontentintegrationtest.json new file mode 100644 index 0000000000..68e3d4f428 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/__files/2-r_h_ghcontentintegrationtest.json @@ -0,0 +1,313 @@ +{ + "id": 40763577, + "node_id": "MDEwOlJlcG9zaXRvcnk0MDc2MzU3Nw==", + "name": "GHContentIntegrationTest", + "full_name": "hub4j-test-org/GHContentIntegrationTest", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest", + "description": "Repository used for integration test of github-api", + "fork": true, + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest", + "forks_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/deployments", + "created_at": "2015-08-15T14:14:57Z", + "updated_at": "2019-11-26T01:09:49Z", + "pushed_at": "2019-11-26T01:09:48Z", + "git_url": "git://github.com/hub4j-test-org/GHContentIntegrationTest.git", + "ssh_url": "git@github.com:hub4j-test-org/GHContentIntegrationTest.git", + "clone_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest.git", + "svn_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest", + "homepage": null, + "size": 52, + "stargazers_count": 1, + "watchers_count": 1, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 41, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 41, + "open_issues": 0, + "watchers": 1, + "default_branch": "main", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "organization": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "parent": { + "id": 19653852, + "node_id": "MDEwOlJlcG9zaXRvcnkxOTY1Mzg1Mg==", + "name": "GHContentIntegrationTest", + "full_name": "kohsuke2/GHContentIntegrationTest", + "private": false, + "owner": { + "login": "kohsuke2", + "id": 1329242, + "node_id": "MDQ6VXNlcjEzMjkyNDI=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1329242?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/kohsuke2", + "html_url": "https://github.com/kohsuke2", + "followers_url": "https://api.github.com/users/kohsuke2/followers", + "following_url": "https://api.github.com/users/kohsuke2/following{/other_user}", + "gists_url": "https://api.github.com/users/kohsuke2/gists{/gist_id}", + "starred_url": "https://api.github.com/users/kohsuke2/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/kohsuke2/subscriptions", + "organizations_url": "https://api.github.com/users/kohsuke2/orgs", + "repos_url": "https://api.github.com/users/kohsuke2/repos", + "events_url": "https://api.github.com/users/kohsuke2/events{/privacy}", + "received_events_url": "https://api.github.com/users/kohsuke2/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/kohsuke2/GHContentIntegrationTest", + "description": "Repository used for integration test of github-api", + "fork": true, + "url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest", + "forks_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/forks", + "keys_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/teams", + "hooks_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/hooks", + "issue_events_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/events", + "assignees_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/tags", + "blobs_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/languages", + "stargazers_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/stargazers", + "contributors_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/contributors", + "subscribers_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/subscribers", + "subscription_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/subscription", + "commits_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/merges", + "archive_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/downloads", + "issues_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/labels{/name}", + "releases_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/deployments", + "created_at": "2014-05-10T22:50:30Z", + "updated_at": "2018-11-07T15:36:19Z", + "pushed_at": "2018-11-07T15:36:18Z", + "git_url": "git://github.com/kohsuke2/GHContentIntegrationTest.git", + "ssh_url": "git@github.com:kohsuke2/GHContentIntegrationTest.git", + "clone_url": "https://github.com/kohsuke2/GHContentIntegrationTest.git", + "svn_url": "https://github.com/kohsuke2/GHContentIntegrationTest", + "homepage": null, + "size": 111, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 1, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 1, + "open_issues": 0, + "watchers": 0, + "default_branch": "main" + }, + "source": { + "id": 14779458, + "node_id": "MDEwOlJlcG9zaXRvcnkxNDc3OTQ1OA==", + "name": "github-api-test-1", + "full_name": "farmdawgnation/github-api-test-1", + "private": false, + "owner": { + "login": "farmdawgnation", + "id": 620189, + "node_id": "MDQ6VXNlcjYyMDE4OQ==", + "avatar_url": "https://avatars2.githubusercontent.com/u/620189?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/farmdawgnation", + "html_url": "https://github.com/farmdawgnation", + "followers_url": "https://api.github.com/users/farmdawgnation/followers", + "following_url": "https://api.github.com/users/farmdawgnation/following{/other_user}", + "gists_url": "https://api.github.com/users/farmdawgnation/gists{/gist_id}", + "starred_url": "https://api.github.com/users/farmdawgnation/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/farmdawgnation/subscriptions", + "organizations_url": "https://api.github.com/users/farmdawgnation/orgs", + "repos_url": "https://api.github.com/users/farmdawgnation/repos", + "events_url": "https://api.github.com/users/farmdawgnation/events{/privacy}", + "received_events_url": "https://api.github.com/users/farmdawgnation/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/farmdawgnation/github-api-test-1", + "description": "Repository used for integration test of github-api", + "fork": false, + "url": "https://api.github.com/repos/farmdawgnation/github-api-test-1", + "forks_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/forks", + "keys_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/teams", + "hooks_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/hooks", + "issue_events_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues/events{/number}", + "events_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/events", + "assignees_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/assignees{/user}", + "branches_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/branches{/branch}", + "tags_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/tags", + "blobs_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/statuses/{sha}", + "languages_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/languages", + "stargazers_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/stargazers", + "contributors_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/contributors", + "subscribers_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/subscribers", + "subscription_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/subscription", + "commits_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/contents/{+path}", + "compare_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/merges", + "archive_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/downloads", + "issues_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues{/number}", + "pulls_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/pulls{/number}", + "milestones_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/milestones{/number}", + "notifications_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/labels{/name}", + "releases_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/releases{/id}", + "deployments_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/deployments", + "created_at": "2013-11-28T14:46:38Z", + "updated_at": "2016-02-05T13:33:23Z", + "pushed_at": "2013-11-28T14:55:36Z", + "git_url": "git://github.com/farmdawgnation/github-api-test-1.git", + "ssh_url": "git@github.com:farmdawgnation/github-api-test-1.git", + "clone_url": "https://github.com/farmdawgnation/github-api-test-1.git", + "svn_url": "https://github.com/farmdawgnation/github-api-test-1", + "homepage": null, + "size": 89, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 60, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 60, + "open_issues": 0, + "watchers": 0, + "default_branch": "main" + }, + "network_count": 60, + "subscribers_count": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/__files/3-r_h_temp-testCreateWithAuthorCommitterAndDate.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/__files/3-r_h_temp-testCreateWithAuthorCommitterAndDate.json new file mode 100644 index 0000000000..09e91125cd --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/__files/3-r_h_temp-testCreateWithAuthorCommitterAndDate.json @@ -0,0 +1,71 @@ +{ + "id": 999000001, + "node_id": "MDEwOlJlcG9zaXRvcnk5OTkwMDAwMDE=", + "name": "temp-testCreateWithAuthorCommitterAndDate", + "full_name": "hub4j-test-org/temp-testCreateWithAuthorCommitterAndDate", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/temp-testCreateWithAuthorCommitterAndDate", + "description": "A test repository for testing the github-api project: temp-testCreateWithAuthorCommitterAndDate", + "fork": false, + "url": "https://api.github.com/repos/hub4j-test-org/temp-testCreateWithAuthorCommitterAndDate", + "contents_url": "https://api.github.com/repos/hub4j-test-org/temp-testCreateWithAuthorCommitterAndDate/contents/{+path}", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z", + "pushed_at": "2024-01-01T00:00:00Z", + "git_url": "git://github.com/hub4j-test-org/temp-testCreateWithAuthorCommitterAndDate.git", + "ssh_url": "git@github.com:hub4j-test-org/temp-testCreateWithAuthorCommitterAndDate.git", + "clone_url": "https://github.com/hub4j-test-org/temp-testCreateWithAuthorCommitterAndDate.git", + "svn_url": "https://github.com/hub4j-test-org/temp-testCreateWithAuthorCommitterAndDate", + "homepage": "http://github-api.kohsuke.org/", + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "main", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "organization": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "type": "Organization", + "site_admin": false + }, + "network_count": 0, + "subscribers_count": 1 +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/__files/4-r_h_t_contents_author-committer-testmd.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/__files/4-r_h_t_contents_author-committer-testmd.json new file mode 100644 index 0000000000..25d2ad11f3 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/__files/4-r_h_t_contents_author-committer-testmd.json @@ -0,0 +1,52 @@ +{ + "content": { + "name": "author-committer-test.md", + "path": "author-committer-test.md", + "sha": "aabbccdd11223344556677889900aabbccddeeff", + "size": 13, + "url": "https://api.github.com/repos/hub4j-test-org/temp-testCreateWithAuthorCommitter/contents/author-committer-test.md?ref=main", + "html_url": "https://github.com/hub4j-test-org/temp-testCreateWithAuthorCommitter/blob/main/author-committer-test.md", + "git_url": "https://api.github.com/repos/hub4j-test-org/temp-testCreateWithAuthorCommitter/git/blobs/aabbccdd11223344556677889900aabbccddeeff", + "download_url": "https://raw.githubusercontent.com/hub4j-test-org/temp-testCreateWithAuthorCommitter/main/author-committer-test.md", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/hub4j-test-org/temp-testCreateWithAuthorCommitter/contents/author-committer-test.md?ref=main", + "git": "https://api.github.com/repos/hub4j-test-org/temp-testCreateWithAuthorCommitter/git/blobs/aabbccdd11223344556677889900aabbccddeeff", + "html": "https://github.com/hub4j-test-org/temp-testCreateWithAuthorCommitter/blob/main/author-committer-test.md" + } + }, + "commit": { + "sha": "1122334455667788990011223344556677889900", + "node_id": "MDY6Q29tbWl0OTk5MDAwMDAxOjExMjIzMzQ0NTU2Njc3ODg5OTAwMTEyMjMzNDQ1NTY2Nzc4ODk5MDA=", + "url": "https://api.github.com/repos/hub4j-test-org/temp-testCreateWithAuthorCommitter/git/commits/1122334455667788990011223344556677889900", + "html_url": "https://github.com/hub4j-test-org/temp-testCreateWithAuthorCommitter/commit/1122334455667788990011223344556677889900", + "author": { + "name": "John Doe", + "email": "john@example.com", + "date": "2009-02-13T23:31:30Z" + }, + "committer": { + "name": "Service Account", + "email": "service@example.com", + "date": "2009-02-13T23:31:30Z" + }, + "tree": { + "sha": "aabb00112233445566778899aabb00112233ccdd", + "url": "https://api.github.com/repos/hub4j-test-org/temp-testCreateWithAuthorCommitter/git/trees/aabb00112233445566778899aabb00112233ccdd" + }, + "message": "Creating with custom author and committer", + "parents": [ + { + "sha": "0000111122223333444455556666777788889999", + "url": "https://api.github.com/repos/hub4j-test-org/temp-testCreateWithAuthorCommitter/git/commits/0000111122223333444455556666777788889999", + "html_url": "https://github.com/hub4j-test-org/temp-testCreateWithAuthorCommitter/commit/0000111122223333444455556666777788889999" + } + ], + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null + } + } +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/mappings/1-user.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/mappings/1-user.json new file mode 100644 index 0000000000..867e80d5f8 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/mappings/1-user.json @@ -0,0 +1,48 @@ +{ + "id": "b9d02cd3-311a-4d49-8de8-aa4220400616", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "1-user.json", + "headers": { + "Date": "Sat, 21 Dec 2019 03:41:23 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4985", + "X-RateLimit-Reset": "1576903221", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding" + ], + "ETag": "W/\"be4370b3c906450f450e411f567ee839\"", + "Last-Modified": "Wed, 18 Dec 2019 01:26:55 GMT", + "X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "unknown, github.v3", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "C977:4ACE:948C05:B32660:5DFD9463" + } + }, + "uuid": "b9d02cd3-311a-4d49-8de8-aa4220400616", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/mappings/2-r_h_ghcontentintegrationtest.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/mappings/2-r_h_ghcontentintegrationtest.json new file mode 100644 index 0000000000..a96af5128d --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/mappings/2-r_h_ghcontentintegrationtest.json @@ -0,0 +1,48 @@ +{ + "id": "f60bd9fd-10cc-4488-b2ce-b618cac83ae2", + "name": "repos_hub4j-test-org_ghcontentintegrationtest", + "request": { + "url": "/repos/hub4j-test-org/GHContentIntegrationTest", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "2-r_h_ghcontentintegrationtest.json", + "headers": { + "Date": "Sat, 21 Dec 2019 03:41:25 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4981", + "X-RateLimit-Reset": "1576903221", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding" + ], + "ETag": "W/\"1e430d4199aa33f3d4673fee6fee2709\"", + "Last-Modified": "Tue, 26 Nov 2019 01:09:49 GMT", + "X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion", + "X-Accepted-OAuth-Scopes": "repo", + "X-GitHub-Media-Type": "unknown, github.v3", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "C977:4ACE:948C3A:B32669:5DFD9463" + } + }, + "uuid": "f60bd9fd-10cc-4488-b2ce-b618cac83ae2", + "persistent": true, + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/mappings/3-r_h_temp-testCreateWithAuthorCommitterAndDate.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/mappings/3-r_h_temp-testCreateWithAuthorCommitterAndDate.json new file mode 100644 index 0000000000..5935e09809 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/mappings/3-r_h_temp-testCreateWithAuthorCommitterAndDate.json @@ -0,0 +1,24 @@ +{ + "id": "a1b2c3d4-e5f6-7890-abcd-110000000001", + "name": "repos_hub4j-test-org_temp-testCreateWithAuthorCommitterAndDate", + "request": { + "url": "/repos/hub4j-test-org/temp-testCreateWithAuthorCommitterAndDate", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "3-r_h_temp-testCreateWithAuthorCommitterAndDate.json", + "headers": { + "Content-Type": "application/json; charset=utf-8", + "Status": "200 OK" + } + }, + "uuid": "a1b2c3d4-e5f6-7890-abcd-110000000001", + "persistent": true, + "insertionIndex": 3 +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/mappings/4-r_h_t_contents_author-committer-testmd.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/mappings/4-r_h_t_contents_author-committer-testmd.json new file mode 100644 index 0000000000..a858d2cef4 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testCreateWithAuthorCommitterAndDate/mappings/4-r_h_t_contents_author-committer-testmd.json @@ -0,0 +1,31 @@ +{ + "id": "a1b2c3d4-e5f6-7890-abcd-110000000002", + "name": "repos_hub4j-test-org_temp-testCreateWithAuthorCommitterAndDate_contents_author-committer-testmd", + "request": { + "url": "/repos/hub4j-test-org/temp-testCreateWithAuthorCommitterAndDate/contents/author-committer-test.md", + "method": "PUT", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{\"path\":\"author-committer-test.md\",\"message\":\"Creating with custom author and committer\",\"content\":\"dGVzdCBjb250ZW50Cg==\",\"author\":{\"name\":\"John Doe\",\"email\":\"john@example.com\",\"date\":\"2009-02-13T23:31:30Z\"},\"committer\":{\"name\":\"Service Account\",\"email\":\"service@example.com\",\"date\":\"2009-02-13T23:31:30Z\"}}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 201, + "bodyFileName": "4-r_h_t_contents_author-committer-testmd.json", + "headers": { + "Content-Type": "application/json; charset=utf-8", + "Status": "201 Created" + } + }, + "uuid": "a1b2c3d4-e5f6-7890-abcd-110000000002", + "persistent": true, + "insertionIndex": 4 +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/__files/1-user.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/__files/1-user.json new file mode 100644 index 0000000000..98e8c76ba2 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/__files/1-user.json @@ -0,0 +1,45 @@ +{ + "login": "bitwiseman", + "id": 1958953, + "node_id": "MDQ6VXNlcjE5NTg5NTM=", + "avatar_url": "https://avatars3.githubusercontent.com/u/1958953?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bitwiseman", + "html_url": "https://github.com/bitwiseman", + "followers_url": "https://api.github.com/users/bitwiseman/followers", + "following_url": "https://api.github.com/users/bitwiseman/following{/other_user}", + "gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions", + "organizations_url": "https://api.github.com/users/bitwiseman/orgs", + "repos_url": "https://api.github.com/users/bitwiseman/repos", + "events_url": "https://api.github.com/users/bitwiseman/events{/privacy}", + "received_events_url": "https://api.github.com/users/bitwiseman/received_events", + "type": "User", + "site_admin": false, + "name": "Liam Newman", + "company": "Cloudbees, Inc.", + "blog": "", + "location": "Seattle, WA, USA", + "email": "bitwiseman@gmail.com", + "hireable": null, + "bio": "https://twitter.com/bitwiseman", + "public_repos": 178, + "public_gists": 7, + "followers": 144, + "following": 9, + "created_at": "2012-07-11T20:38:33Z", + "updated_at": "2019-12-18T01:26:55Z", + "private_gists": 7, + "total_private_repos": 10, + "owned_private_repos": 0, + "disk_usage": 33697, + "collaborators": 0, + "two_factor_authentication": true, + "plan": { + "name": "free", + "space": 976562499, + "collaborators": 0, + "private_repos": 10000 + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/__files/2-r_h_ghcontentintegrationtest.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/__files/2-r_h_ghcontentintegrationtest.json new file mode 100644 index 0000000000..68e3d4f428 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/__files/2-r_h_ghcontentintegrationtest.json @@ -0,0 +1,313 @@ +{ + "id": 40763577, + "node_id": "MDEwOlJlcG9zaXRvcnk0MDc2MzU3Nw==", + "name": "GHContentIntegrationTest", + "full_name": "hub4j-test-org/GHContentIntegrationTest", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest", + "description": "Repository used for integration test of github-api", + "fork": true, + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest", + "forks_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/deployments", + "created_at": "2015-08-15T14:14:57Z", + "updated_at": "2019-11-26T01:09:49Z", + "pushed_at": "2019-11-26T01:09:48Z", + "git_url": "git://github.com/hub4j-test-org/GHContentIntegrationTest.git", + "ssh_url": "git@github.com:hub4j-test-org/GHContentIntegrationTest.git", + "clone_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest.git", + "svn_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest", + "homepage": null, + "size": 52, + "stargazers_count": 1, + "watchers_count": 1, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 41, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 41, + "open_issues": 0, + "watchers": 1, + "default_branch": "main", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "organization": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "parent": { + "id": 19653852, + "node_id": "MDEwOlJlcG9zaXRvcnkxOTY1Mzg1Mg==", + "name": "GHContentIntegrationTest", + "full_name": "kohsuke2/GHContentIntegrationTest", + "private": false, + "owner": { + "login": "kohsuke2", + "id": 1329242, + "node_id": "MDQ6VXNlcjEzMjkyNDI=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1329242?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/kohsuke2", + "html_url": "https://github.com/kohsuke2", + "followers_url": "https://api.github.com/users/kohsuke2/followers", + "following_url": "https://api.github.com/users/kohsuke2/following{/other_user}", + "gists_url": "https://api.github.com/users/kohsuke2/gists{/gist_id}", + "starred_url": "https://api.github.com/users/kohsuke2/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/kohsuke2/subscriptions", + "organizations_url": "https://api.github.com/users/kohsuke2/orgs", + "repos_url": "https://api.github.com/users/kohsuke2/repos", + "events_url": "https://api.github.com/users/kohsuke2/events{/privacy}", + "received_events_url": "https://api.github.com/users/kohsuke2/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/kohsuke2/GHContentIntegrationTest", + "description": "Repository used for integration test of github-api", + "fork": true, + "url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest", + "forks_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/forks", + "keys_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/teams", + "hooks_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/hooks", + "issue_events_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/events", + "assignees_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/tags", + "blobs_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/languages", + "stargazers_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/stargazers", + "contributors_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/contributors", + "subscribers_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/subscribers", + "subscription_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/subscription", + "commits_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/merges", + "archive_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/downloads", + "issues_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/labels{/name}", + "releases_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/deployments", + "created_at": "2014-05-10T22:50:30Z", + "updated_at": "2018-11-07T15:36:19Z", + "pushed_at": "2018-11-07T15:36:18Z", + "git_url": "git://github.com/kohsuke2/GHContentIntegrationTest.git", + "ssh_url": "git@github.com:kohsuke2/GHContentIntegrationTest.git", + "clone_url": "https://github.com/kohsuke2/GHContentIntegrationTest.git", + "svn_url": "https://github.com/kohsuke2/GHContentIntegrationTest", + "homepage": null, + "size": 111, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 1, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 1, + "open_issues": 0, + "watchers": 0, + "default_branch": "main" + }, + "source": { + "id": 14779458, + "node_id": "MDEwOlJlcG9zaXRvcnkxNDc3OTQ1OA==", + "name": "github-api-test-1", + "full_name": "farmdawgnation/github-api-test-1", + "private": false, + "owner": { + "login": "farmdawgnation", + "id": 620189, + "node_id": "MDQ6VXNlcjYyMDE4OQ==", + "avatar_url": "https://avatars2.githubusercontent.com/u/620189?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/farmdawgnation", + "html_url": "https://github.com/farmdawgnation", + "followers_url": "https://api.github.com/users/farmdawgnation/followers", + "following_url": "https://api.github.com/users/farmdawgnation/following{/other_user}", + "gists_url": "https://api.github.com/users/farmdawgnation/gists{/gist_id}", + "starred_url": "https://api.github.com/users/farmdawgnation/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/farmdawgnation/subscriptions", + "organizations_url": "https://api.github.com/users/farmdawgnation/orgs", + "repos_url": "https://api.github.com/users/farmdawgnation/repos", + "events_url": "https://api.github.com/users/farmdawgnation/events{/privacy}", + "received_events_url": "https://api.github.com/users/farmdawgnation/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/farmdawgnation/github-api-test-1", + "description": "Repository used for integration test of github-api", + "fork": false, + "url": "https://api.github.com/repos/farmdawgnation/github-api-test-1", + "forks_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/forks", + "keys_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/teams", + "hooks_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/hooks", + "issue_events_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues/events{/number}", + "events_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/events", + "assignees_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/assignees{/user}", + "branches_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/branches{/branch}", + "tags_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/tags", + "blobs_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/statuses/{sha}", + "languages_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/languages", + "stargazers_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/stargazers", + "contributors_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/contributors", + "subscribers_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/subscribers", + "subscription_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/subscription", + "commits_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/contents/{+path}", + "compare_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/merges", + "archive_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/downloads", + "issues_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues{/number}", + "pulls_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/pulls{/number}", + "milestones_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/milestones{/number}", + "notifications_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/labels{/name}", + "releases_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/releases{/id}", + "deployments_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/deployments", + "created_at": "2013-11-28T14:46:38Z", + "updated_at": "2016-02-05T13:33:23Z", + "pushed_at": "2013-11-28T14:55:36Z", + "git_url": "git://github.com/farmdawgnation/github-api-test-1.git", + "ssh_url": "git@github.com:farmdawgnation/github-api-test-1.git", + "clone_url": "https://github.com/farmdawgnation/github-api-test-1.git", + "svn_url": "https://github.com/farmdawgnation/github-api-test-1", + "homepage": null, + "size": 89, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 60, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 60, + "open_issues": 0, + "watchers": 0, + "default_branch": "main" + }, + "network_count": 60, + "subscribers_count": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/__files/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/__files/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json new file mode 100644 index 0000000000..b2bf0b6737 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/__files/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json @@ -0,0 +1,18 @@ +{ + "name": "a-file-with-content", + "path": "ghcontent-ro/a-file-with-content", + "sha": "901fd87750a8e53fe39a219cad50d4f7c80ca272", + "size": 22, + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content?ref=main", + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest/blob/main/ghcontent-ro/a-file-with-content", + "git_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs/901fd87750a8e53fe39a219cad50d4f7c80ca272", + "download_url": "https://raw.githubusercontent.com/hub4j-test-org/GHContentIntegrationTest/main/ghcontent-ro/a-file-with-content", + "type": "file", + "content": "dGhhbmtzIGZvciByZWFkaW5nIG1lCg==\n", + "encoding": "base64", + "_links": { + "self": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content?ref=main", + "git": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs/901fd87750a8e53fe39a219cad50d4f7c80ca272", + "html": "https://github.com/hub4j-test-org/GHContentIntegrationTest/blob/main/ghcontent-ro/a-file-with-content" + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/__files/4-r_h_g_contents_ghcontent-ro_a-file-with-content-delete.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/__files/4-r_h_g_contents_ghcontent-ro_a-file-with-content-delete.json new file mode 100644 index 0000000000..ffebfc933f --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/__files/4-r_h_g_contents_ghcontent-ro_a-file-with-content-delete.json @@ -0,0 +1,37 @@ +{ + "content": null, + "commit": { + "sha": "3344556677889900112233445566778899001122", + "node_id": "MDY6Q29tbWl0NDA3NjM1Nzc6MzM0NDU1NjY3Nzg4OTkwMDExMjIzMzQ0NTU2Njc3ODg5OTAwMTEyMg==", + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/commits/3344556677889900112233445566778899001122", + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest/commit/3344556677889900112233445566778899001122", + "author": { + "name": "John Doe", + "email": "john@example.com", + "date": "2024-01-01T00:00:00Z" + }, + "committer": { + "name": "Service Account", + "email": "service@example.com", + "date": "2024-01-01T00:00:00Z" + }, + "tree": { + "sha": "eeff00112233445566778899aabb00112233ddee", + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/trees/eeff00112233445566778899aabb00112233ddee" + }, + "message": "Deleting with custom author and committer", + "parents": [ + { + "sha": "bbbb111122223333444455556666777788889999", + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/commits/bbbb111122223333444455556666777788889999", + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest/commit/bbbb111122223333444455556666777788889999" + } + ], + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null + } + } +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/mappings/1-user.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/mappings/1-user.json new file mode 100644 index 0000000000..867e80d5f8 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/mappings/1-user.json @@ -0,0 +1,48 @@ +{ + "id": "b9d02cd3-311a-4d49-8de8-aa4220400616", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "1-user.json", + "headers": { + "Date": "Sat, 21 Dec 2019 03:41:23 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4985", + "X-RateLimit-Reset": "1576903221", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding" + ], + "ETag": "W/\"be4370b3c906450f450e411f567ee839\"", + "Last-Modified": "Wed, 18 Dec 2019 01:26:55 GMT", + "X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "unknown, github.v3", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "C977:4ACE:948C05:B32660:5DFD9463" + } + }, + "uuid": "b9d02cd3-311a-4d49-8de8-aa4220400616", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/mappings/2-r_h_ghcontentintegrationtest.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/mappings/2-r_h_ghcontentintegrationtest.json new file mode 100644 index 0000000000..a96af5128d --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/mappings/2-r_h_ghcontentintegrationtest.json @@ -0,0 +1,48 @@ +{ + "id": "f60bd9fd-10cc-4488-b2ce-b618cac83ae2", + "name": "repos_hub4j-test-org_ghcontentintegrationtest", + "request": { + "url": "/repos/hub4j-test-org/GHContentIntegrationTest", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "2-r_h_ghcontentintegrationtest.json", + "headers": { + "Date": "Sat, 21 Dec 2019 03:41:25 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4981", + "X-RateLimit-Reset": "1576903221", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding" + ], + "ETag": "W/\"1e430d4199aa33f3d4673fee6fee2709\"", + "Last-Modified": "Tue, 26 Nov 2019 01:09:49 GMT", + "X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion", + "X-Accepted-OAuth-Scopes": "repo", + "X-GitHub-Media-Type": "unknown, github.v3", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "C977:4ACE:948C3A:B32669:5DFD9463" + } + }, + "uuid": "f60bd9fd-10cc-4488-b2ce-b618cac83ae2", + "persistent": true, + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/mappings/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/mappings/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json new file mode 100644 index 0000000000..c661ffe5cb --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/mappings/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json @@ -0,0 +1,24 @@ +{ + "id": "a1b2c3d4-e5f6-7890-abcd-300000000001", + "name": "repos_hub4j-test-org_ghcontentintegrationtest_contents_ghcontent-ro_a-file-with-content", + "request": { + "url": "/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "3-r_h_g_contents_ghcontent-ro_a-file-with-content.json", + "headers": { + "Content-Type": "application/json; charset=utf-8", + "Status": "200 OK" + } + }, + "uuid": "a1b2c3d4-e5f6-7890-abcd-300000000001", + "persistent": true, + "insertionIndex": 3 +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/mappings/4-r_h_g_contents_ghcontent-ro_a-file-with-content-delete.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/mappings/4-r_h_g_contents_ghcontent-ro_a-file-with-content-delete.json new file mode 100644 index 0000000000..c6cfc36ccf --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitter/mappings/4-r_h_g_contents_ghcontent-ro_a-file-with-content-delete.json @@ -0,0 +1,31 @@ +{ + "id": "a1b2c3d4-e5f6-7890-abcd-300000000002", + "name": "repos_hub4j-test-org_ghcontentintegrationtest_contents_ghcontent-ro_a-file-with-content_delete", + "request": { + "url": "/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content", + "method": "DELETE", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{\"path\":\"ghcontent-ro/a-file-with-content\",\"sha\":\"901fd87750a8e53fe39a219cad50d4f7c80ca272\",\"message\":\"Deleting with custom author and committer\",\"branch\":\"main\",\"author\":{\"name\":\"John Doe\",\"email\":\"john@example.com\"},\"committer\":{\"name\":\"Service Account\",\"email\":\"service@example.com\"}}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 200, + "bodyFileName": "4-r_h_g_contents_ghcontent-ro_a-file-with-content-delete.json", + "headers": { + "Content-Type": "application/json; charset=utf-8", + "Status": "200 OK" + } + }, + "uuid": "a1b2c3d4-e5f6-7890-abcd-300000000002", + "persistent": true, + "insertionIndex": 4 +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/__files/1-user.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/__files/1-user.json new file mode 100644 index 0000000000..98e8c76ba2 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/__files/1-user.json @@ -0,0 +1,45 @@ +{ + "login": "bitwiseman", + "id": 1958953, + "node_id": "MDQ6VXNlcjE5NTg5NTM=", + "avatar_url": "https://avatars3.githubusercontent.com/u/1958953?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bitwiseman", + "html_url": "https://github.com/bitwiseman", + "followers_url": "https://api.github.com/users/bitwiseman/followers", + "following_url": "https://api.github.com/users/bitwiseman/following{/other_user}", + "gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions", + "organizations_url": "https://api.github.com/users/bitwiseman/orgs", + "repos_url": "https://api.github.com/users/bitwiseman/repos", + "events_url": "https://api.github.com/users/bitwiseman/events{/privacy}", + "received_events_url": "https://api.github.com/users/bitwiseman/received_events", + "type": "User", + "site_admin": false, + "name": "Liam Newman", + "company": "Cloudbees, Inc.", + "blog": "", + "location": "Seattle, WA, USA", + "email": "bitwiseman@gmail.com", + "hireable": null, + "bio": "https://twitter.com/bitwiseman", + "public_repos": 178, + "public_gists": 7, + "followers": 144, + "following": 9, + "created_at": "2012-07-11T20:38:33Z", + "updated_at": "2019-12-18T01:26:55Z", + "private_gists": 7, + "total_private_repos": 10, + "owned_private_repos": 0, + "disk_usage": 33697, + "collaborators": 0, + "two_factor_authentication": true, + "plan": { + "name": "free", + "space": 976562499, + "collaborators": 0, + "private_repos": 10000 + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/__files/2-r_h_ghcontentintegrationtest.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/__files/2-r_h_ghcontentintegrationtest.json new file mode 100644 index 0000000000..68e3d4f428 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/__files/2-r_h_ghcontentintegrationtest.json @@ -0,0 +1,313 @@ +{ + "id": 40763577, + "node_id": "MDEwOlJlcG9zaXRvcnk0MDc2MzU3Nw==", + "name": "GHContentIntegrationTest", + "full_name": "hub4j-test-org/GHContentIntegrationTest", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest", + "description": "Repository used for integration test of github-api", + "fork": true, + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest", + "forks_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/deployments", + "created_at": "2015-08-15T14:14:57Z", + "updated_at": "2019-11-26T01:09:49Z", + "pushed_at": "2019-11-26T01:09:48Z", + "git_url": "git://github.com/hub4j-test-org/GHContentIntegrationTest.git", + "ssh_url": "git@github.com:hub4j-test-org/GHContentIntegrationTest.git", + "clone_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest.git", + "svn_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest", + "homepage": null, + "size": 52, + "stargazers_count": 1, + "watchers_count": 1, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 41, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 41, + "open_issues": 0, + "watchers": 1, + "default_branch": "main", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "organization": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "parent": { + "id": 19653852, + "node_id": "MDEwOlJlcG9zaXRvcnkxOTY1Mzg1Mg==", + "name": "GHContentIntegrationTest", + "full_name": "kohsuke2/GHContentIntegrationTest", + "private": false, + "owner": { + "login": "kohsuke2", + "id": 1329242, + "node_id": "MDQ6VXNlcjEzMjkyNDI=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1329242?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/kohsuke2", + "html_url": "https://github.com/kohsuke2", + "followers_url": "https://api.github.com/users/kohsuke2/followers", + "following_url": "https://api.github.com/users/kohsuke2/following{/other_user}", + "gists_url": "https://api.github.com/users/kohsuke2/gists{/gist_id}", + "starred_url": "https://api.github.com/users/kohsuke2/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/kohsuke2/subscriptions", + "organizations_url": "https://api.github.com/users/kohsuke2/orgs", + "repos_url": "https://api.github.com/users/kohsuke2/repos", + "events_url": "https://api.github.com/users/kohsuke2/events{/privacy}", + "received_events_url": "https://api.github.com/users/kohsuke2/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/kohsuke2/GHContentIntegrationTest", + "description": "Repository used for integration test of github-api", + "fork": true, + "url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest", + "forks_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/forks", + "keys_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/teams", + "hooks_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/hooks", + "issue_events_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/events", + "assignees_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/tags", + "blobs_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/languages", + "stargazers_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/stargazers", + "contributors_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/contributors", + "subscribers_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/subscribers", + "subscription_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/subscription", + "commits_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/merges", + "archive_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/downloads", + "issues_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/labels{/name}", + "releases_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/deployments", + "created_at": "2014-05-10T22:50:30Z", + "updated_at": "2018-11-07T15:36:19Z", + "pushed_at": "2018-11-07T15:36:18Z", + "git_url": "git://github.com/kohsuke2/GHContentIntegrationTest.git", + "ssh_url": "git@github.com:kohsuke2/GHContentIntegrationTest.git", + "clone_url": "https://github.com/kohsuke2/GHContentIntegrationTest.git", + "svn_url": "https://github.com/kohsuke2/GHContentIntegrationTest", + "homepage": null, + "size": 111, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 1, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 1, + "open_issues": 0, + "watchers": 0, + "default_branch": "main" + }, + "source": { + "id": 14779458, + "node_id": "MDEwOlJlcG9zaXRvcnkxNDc3OTQ1OA==", + "name": "github-api-test-1", + "full_name": "farmdawgnation/github-api-test-1", + "private": false, + "owner": { + "login": "farmdawgnation", + "id": 620189, + "node_id": "MDQ6VXNlcjYyMDE4OQ==", + "avatar_url": "https://avatars2.githubusercontent.com/u/620189?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/farmdawgnation", + "html_url": "https://github.com/farmdawgnation", + "followers_url": "https://api.github.com/users/farmdawgnation/followers", + "following_url": "https://api.github.com/users/farmdawgnation/following{/other_user}", + "gists_url": "https://api.github.com/users/farmdawgnation/gists{/gist_id}", + "starred_url": "https://api.github.com/users/farmdawgnation/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/farmdawgnation/subscriptions", + "organizations_url": "https://api.github.com/users/farmdawgnation/orgs", + "repos_url": "https://api.github.com/users/farmdawgnation/repos", + "events_url": "https://api.github.com/users/farmdawgnation/events{/privacy}", + "received_events_url": "https://api.github.com/users/farmdawgnation/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/farmdawgnation/github-api-test-1", + "description": "Repository used for integration test of github-api", + "fork": false, + "url": "https://api.github.com/repos/farmdawgnation/github-api-test-1", + "forks_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/forks", + "keys_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/teams", + "hooks_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/hooks", + "issue_events_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues/events{/number}", + "events_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/events", + "assignees_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/assignees{/user}", + "branches_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/branches{/branch}", + "tags_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/tags", + "blobs_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/statuses/{sha}", + "languages_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/languages", + "stargazers_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/stargazers", + "contributors_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/contributors", + "subscribers_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/subscribers", + "subscription_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/subscription", + "commits_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/contents/{+path}", + "compare_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/merges", + "archive_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/downloads", + "issues_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues{/number}", + "pulls_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/pulls{/number}", + "milestones_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/milestones{/number}", + "notifications_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/labels{/name}", + "releases_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/releases{/id}", + "deployments_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/deployments", + "created_at": "2013-11-28T14:46:38Z", + "updated_at": "2016-02-05T13:33:23Z", + "pushed_at": "2013-11-28T14:55:36Z", + "git_url": "git://github.com/farmdawgnation/github-api-test-1.git", + "ssh_url": "git@github.com:farmdawgnation/github-api-test-1.git", + "clone_url": "https://github.com/farmdawgnation/github-api-test-1.git", + "svn_url": "https://github.com/farmdawgnation/github-api-test-1", + "homepage": null, + "size": 89, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 60, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 60, + "open_issues": 0, + "watchers": 0, + "default_branch": "main" + }, + "network_count": 60, + "subscribers_count": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/__files/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/__files/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json new file mode 100644 index 0000000000..b2bf0b6737 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/__files/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json @@ -0,0 +1,18 @@ +{ + "name": "a-file-with-content", + "path": "ghcontent-ro/a-file-with-content", + "sha": "901fd87750a8e53fe39a219cad50d4f7c80ca272", + "size": 22, + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content?ref=main", + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest/blob/main/ghcontent-ro/a-file-with-content", + "git_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs/901fd87750a8e53fe39a219cad50d4f7c80ca272", + "download_url": "https://raw.githubusercontent.com/hub4j-test-org/GHContentIntegrationTest/main/ghcontent-ro/a-file-with-content", + "type": "file", + "content": "dGhhbmtzIGZvciByZWFkaW5nIG1lCg==\n", + "encoding": "base64", + "_links": { + "self": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content?ref=main", + "git": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs/901fd87750a8e53fe39a219cad50d4f7c80ca272", + "html": "https://github.com/hub4j-test-org/GHContentIntegrationTest/blob/main/ghcontent-ro/a-file-with-content" + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/__files/4-r_h_g_contents_ghcontent-ro_a-file-with-content-delete.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/__files/4-r_h_g_contents_ghcontent-ro_a-file-with-content-delete.json new file mode 100644 index 0000000000..0498efff56 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/__files/4-r_h_g_contents_ghcontent-ro_a-file-with-content-delete.json @@ -0,0 +1,37 @@ +{ + "content": null, + "commit": { + "sha": "3344556677889900112233445566778899001122", + "node_id": "MDY6Q29tbWl0NDA3NjM1Nzc6MzM0NDU1NjY3Nzg4OTkwMDExMjIzMzQ0NTU2Njc3ODg5OTAwMTEyMg==", + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/commits/3344556677889900112233445566778899001122", + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest/commit/3344556677889900112233445566778899001122", + "author": { + "name": "John Doe", + "email": "john@example.com", + "date": "2009-02-13T23:31:30Z" + }, + "committer": { + "name": "Service Account", + "email": "service@example.com", + "date": "2009-02-13T23:31:30Z" + }, + "tree": { + "sha": "eeff00112233445566778899aabb00112233ddee", + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/trees/eeff00112233445566778899aabb00112233ddee" + }, + "message": "Deleting with custom author and committer", + "parents": [ + { + "sha": "bbbb111122223333444455556666777788889999", + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/commits/bbbb111122223333444455556666777788889999", + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest/commit/bbbb111122223333444455556666777788889999" + } + ], + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null + } + } +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/mappings/1-user.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/mappings/1-user.json new file mode 100644 index 0000000000..867e80d5f8 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/mappings/1-user.json @@ -0,0 +1,48 @@ +{ + "id": "b9d02cd3-311a-4d49-8de8-aa4220400616", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "1-user.json", + "headers": { + "Date": "Sat, 21 Dec 2019 03:41:23 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4985", + "X-RateLimit-Reset": "1576903221", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding" + ], + "ETag": "W/\"be4370b3c906450f450e411f567ee839\"", + "Last-Modified": "Wed, 18 Dec 2019 01:26:55 GMT", + "X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "unknown, github.v3", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "C977:4ACE:948C05:B32660:5DFD9463" + } + }, + "uuid": "b9d02cd3-311a-4d49-8de8-aa4220400616", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/mappings/2-r_h_ghcontentintegrationtest.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/mappings/2-r_h_ghcontentintegrationtest.json new file mode 100644 index 0000000000..a96af5128d --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/mappings/2-r_h_ghcontentintegrationtest.json @@ -0,0 +1,48 @@ +{ + "id": "f60bd9fd-10cc-4488-b2ce-b618cac83ae2", + "name": "repos_hub4j-test-org_ghcontentintegrationtest", + "request": { + "url": "/repos/hub4j-test-org/GHContentIntegrationTest", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "2-r_h_ghcontentintegrationtest.json", + "headers": { + "Date": "Sat, 21 Dec 2019 03:41:25 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4981", + "X-RateLimit-Reset": "1576903221", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding" + ], + "ETag": "W/\"1e430d4199aa33f3d4673fee6fee2709\"", + "Last-Modified": "Tue, 26 Nov 2019 01:09:49 GMT", + "X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion", + "X-Accepted-OAuth-Scopes": "repo", + "X-GitHub-Media-Type": "unknown, github.v3", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "C977:4ACE:948C3A:B32669:5DFD9463" + } + }, + "uuid": "f60bd9fd-10cc-4488-b2ce-b618cac83ae2", + "persistent": true, + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/mappings/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/mappings/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json new file mode 100644 index 0000000000..c661ffe5cb --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/mappings/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json @@ -0,0 +1,24 @@ +{ + "id": "a1b2c3d4-e5f6-7890-abcd-300000000001", + "name": "repos_hub4j-test-org_ghcontentintegrationtest_contents_ghcontent-ro_a-file-with-content", + "request": { + "url": "/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "3-r_h_g_contents_ghcontent-ro_a-file-with-content.json", + "headers": { + "Content-Type": "application/json; charset=utf-8", + "Status": "200 OK" + } + }, + "uuid": "a1b2c3d4-e5f6-7890-abcd-300000000001", + "persistent": true, + "insertionIndex": 3 +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/mappings/4-r_h_g_contents_ghcontent-ro_a-file-with-content-delete.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/mappings/4-r_h_g_contents_ghcontent-ro_a-file-with-content-delete.json new file mode 100644 index 0000000000..13cbaf6c73 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testDeleteWithAuthorCommitterAndDate/mappings/4-r_h_g_contents_ghcontent-ro_a-file-with-content-delete.json @@ -0,0 +1,31 @@ +{ + "id": "a1b2c3d4-e5f6-7890-abcd-310000000002", + "name": "repos_hub4j-test-org_ghcontentintegrationtest_contents_ghcontent-ro_a-file-with-content_delete_with_date", + "request": { + "url": "/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content", + "method": "DELETE", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{\"path\":\"ghcontent-ro/a-file-with-content\",\"sha\":\"901fd87750a8e53fe39a219cad50d4f7c80ca272\",\"message\":\"Deleting with custom author and committer\",\"branch\":\"main\",\"author\":{\"name\":\"John Doe\",\"email\":\"john@example.com\",\"date\":\"2009-02-13T23:31:30Z\"},\"committer\":{\"name\":\"Service Account\",\"email\":\"service@example.com\",\"date\":\"2009-02-13T23:31:30Z\"}}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 200, + "bodyFileName": "4-r_h_g_contents_ghcontent-ro_a-file-with-content-delete.json", + "headers": { + "Content-Type": "application/json; charset=utf-8", + "Status": "200 OK" + } + }, + "uuid": "a1b2c3d4-e5f6-7890-abcd-310000000002", + "persistent": true, + "insertionIndex": 4 +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/__files/1-user.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/__files/1-user.json new file mode 100644 index 0000000000..98e8c76ba2 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/__files/1-user.json @@ -0,0 +1,45 @@ +{ + "login": "bitwiseman", + "id": 1958953, + "node_id": "MDQ6VXNlcjE5NTg5NTM=", + "avatar_url": "https://avatars3.githubusercontent.com/u/1958953?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bitwiseman", + "html_url": "https://github.com/bitwiseman", + "followers_url": "https://api.github.com/users/bitwiseman/followers", + "following_url": "https://api.github.com/users/bitwiseman/following{/other_user}", + "gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions", + "organizations_url": "https://api.github.com/users/bitwiseman/orgs", + "repos_url": "https://api.github.com/users/bitwiseman/repos", + "events_url": "https://api.github.com/users/bitwiseman/events{/privacy}", + "received_events_url": "https://api.github.com/users/bitwiseman/received_events", + "type": "User", + "site_admin": false, + "name": "Liam Newman", + "company": "Cloudbees, Inc.", + "blog": "", + "location": "Seattle, WA, USA", + "email": "bitwiseman@gmail.com", + "hireable": null, + "bio": "https://twitter.com/bitwiseman", + "public_repos": 178, + "public_gists": 7, + "followers": 144, + "following": 9, + "created_at": "2012-07-11T20:38:33Z", + "updated_at": "2019-12-18T01:26:55Z", + "private_gists": 7, + "total_private_repos": 10, + "owned_private_repos": 0, + "disk_usage": 33697, + "collaborators": 0, + "two_factor_authentication": true, + "plan": { + "name": "free", + "space": 976562499, + "collaborators": 0, + "private_repos": 10000 + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/__files/2-r_h_ghcontentintegrationtest.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/__files/2-r_h_ghcontentintegrationtest.json new file mode 100644 index 0000000000..68e3d4f428 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/__files/2-r_h_ghcontentintegrationtest.json @@ -0,0 +1,313 @@ +{ + "id": 40763577, + "node_id": "MDEwOlJlcG9zaXRvcnk0MDc2MzU3Nw==", + "name": "GHContentIntegrationTest", + "full_name": "hub4j-test-org/GHContentIntegrationTest", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest", + "description": "Repository used for integration test of github-api", + "fork": true, + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest", + "forks_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/deployments", + "created_at": "2015-08-15T14:14:57Z", + "updated_at": "2019-11-26T01:09:49Z", + "pushed_at": "2019-11-26T01:09:48Z", + "git_url": "git://github.com/hub4j-test-org/GHContentIntegrationTest.git", + "ssh_url": "git@github.com:hub4j-test-org/GHContentIntegrationTest.git", + "clone_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest.git", + "svn_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest", + "homepage": null, + "size": 52, + "stargazers_count": 1, + "watchers_count": 1, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 41, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 41, + "open_issues": 0, + "watchers": 1, + "default_branch": "main", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "organization": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "parent": { + "id": 19653852, + "node_id": "MDEwOlJlcG9zaXRvcnkxOTY1Mzg1Mg==", + "name": "GHContentIntegrationTest", + "full_name": "kohsuke2/GHContentIntegrationTest", + "private": false, + "owner": { + "login": "kohsuke2", + "id": 1329242, + "node_id": "MDQ6VXNlcjEzMjkyNDI=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1329242?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/kohsuke2", + "html_url": "https://github.com/kohsuke2", + "followers_url": "https://api.github.com/users/kohsuke2/followers", + "following_url": "https://api.github.com/users/kohsuke2/following{/other_user}", + "gists_url": "https://api.github.com/users/kohsuke2/gists{/gist_id}", + "starred_url": "https://api.github.com/users/kohsuke2/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/kohsuke2/subscriptions", + "organizations_url": "https://api.github.com/users/kohsuke2/orgs", + "repos_url": "https://api.github.com/users/kohsuke2/repos", + "events_url": "https://api.github.com/users/kohsuke2/events{/privacy}", + "received_events_url": "https://api.github.com/users/kohsuke2/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/kohsuke2/GHContentIntegrationTest", + "description": "Repository used for integration test of github-api", + "fork": true, + "url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest", + "forks_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/forks", + "keys_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/teams", + "hooks_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/hooks", + "issue_events_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/events", + "assignees_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/tags", + "blobs_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/languages", + "stargazers_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/stargazers", + "contributors_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/contributors", + "subscribers_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/subscribers", + "subscription_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/subscription", + "commits_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/merges", + "archive_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/downloads", + "issues_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/labels{/name}", + "releases_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/deployments", + "created_at": "2014-05-10T22:50:30Z", + "updated_at": "2018-11-07T15:36:19Z", + "pushed_at": "2018-11-07T15:36:18Z", + "git_url": "git://github.com/kohsuke2/GHContentIntegrationTest.git", + "ssh_url": "git@github.com:kohsuke2/GHContentIntegrationTest.git", + "clone_url": "https://github.com/kohsuke2/GHContentIntegrationTest.git", + "svn_url": "https://github.com/kohsuke2/GHContentIntegrationTest", + "homepage": null, + "size": 111, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 1, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 1, + "open_issues": 0, + "watchers": 0, + "default_branch": "main" + }, + "source": { + "id": 14779458, + "node_id": "MDEwOlJlcG9zaXRvcnkxNDc3OTQ1OA==", + "name": "github-api-test-1", + "full_name": "farmdawgnation/github-api-test-1", + "private": false, + "owner": { + "login": "farmdawgnation", + "id": 620189, + "node_id": "MDQ6VXNlcjYyMDE4OQ==", + "avatar_url": "https://avatars2.githubusercontent.com/u/620189?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/farmdawgnation", + "html_url": "https://github.com/farmdawgnation", + "followers_url": "https://api.github.com/users/farmdawgnation/followers", + "following_url": "https://api.github.com/users/farmdawgnation/following{/other_user}", + "gists_url": "https://api.github.com/users/farmdawgnation/gists{/gist_id}", + "starred_url": "https://api.github.com/users/farmdawgnation/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/farmdawgnation/subscriptions", + "organizations_url": "https://api.github.com/users/farmdawgnation/orgs", + "repos_url": "https://api.github.com/users/farmdawgnation/repos", + "events_url": "https://api.github.com/users/farmdawgnation/events{/privacy}", + "received_events_url": "https://api.github.com/users/farmdawgnation/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/farmdawgnation/github-api-test-1", + "description": "Repository used for integration test of github-api", + "fork": false, + "url": "https://api.github.com/repos/farmdawgnation/github-api-test-1", + "forks_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/forks", + "keys_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/teams", + "hooks_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/hooks", + "issue_events_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues/events{/number}", + "events_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/events", + "assignees_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/assignees{/user}", + "branches_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/branches{/branch}", + "tags_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/tags", + "blobs_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/statuses/{sha}", + "languages_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/languages", + "stargazers_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/stargazers", + "contributors_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/contributors", + "subscribers_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/subscribers", + "subscription_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/subscription", + "commits_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/contents/{+path}", + "compare_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/merges", + "archive_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/downloads", + "issues_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues{/number}", + "pulls_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/pulls{/number}", + "milestones_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/milestones{/number}", + "notifications_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/labels{/name}", + "releases_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/releases{/id}", + "deployments_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/deployments", + "created_at": "2013-11-28T14:46:38Z", + "updated_at": "2016-02-05T13:33:23Z", + "pushed_at": "2013-11-28T14:55:36Z", + "git_url": "git://github.com/farmdawgnation/github-api-test-1.git", + "ssh_url": "git@github.com:farmdawgnation/github-api-test-1.git", + "clone_url": "https://github.com/farmdawgnation/github-api-test-1.git", + "svn_url": "https://github.com/farmdawgnation/github-api-test-1", + "homepage": null, + "size": 89, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 60, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 60, + "open_issues": 0, + "watchers": 0, + "default_branch": "main" + }, + "network_count": 60, + "subscribers_count": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/__files/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/__files/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json new file mode 100644 index 0000000000..b2bf0b6737 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/__files/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json @@ -0,0 +1,18 @@ +{ + "name": "a-file-with-content", + "path": "ghcontent-ro/a-file-with-content", + "sha": "901fd87750a8e53fe39a219cad50d4f7c80ca272", + "size": 22, + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content?ref=main", + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest/blob/main/ghcontent-ro/a-file-with-content", + "git_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs/901fd87750a8e53fe39a219cad50d4f7c80ca272", + "download_url": "https://raw.githubusercontent.com/hub4j-test-org/GHContentIntegrationTest/main/ghcontent-ro/a-file-with-content", + "type": "file", + "content": "dGhhbmtzIGZvciByZWFkaW5nIG1lCg==\n", + "encoding": "base64", + "_links": { + "self": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content?ref=main", + "git": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs/901fd87750a8e53fe39a219cad50d4f7c80ca272", + "html": "https://github.com/hub4j-test-org/GHContentIntegrationTest/blob/main/ghcontent-ro/a-file-with-content" + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/__files/4-r_h_g_contents_ghcontent-ro_a-file-with-content-update.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/__files/4-r_h_g_contents_ghcontent-ro_a-file-with-content-update.json new file mode 100644 index 0000000000..cd35c418e7 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/__files/4-r_h_g_contents_ghcontent-ro_a-file-with-content-update.json @@ -0,0 +1,52 @@ +{ + "content": { + "name": "a-file-with-content", + "path": "ghcontent-ro/a-file-with-content", + "sha": "bbccddee11223344556677889900aabbccddeeff", + "size": 16, + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content?ref=main", + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest/blob/main/ghcontent-ro/a-file-with-content", + "git_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs/bbccddee11223344556677889900aabbccddeeff", + "download_url": "https://raw.githubusercontent.com/hub4j-test-org/GHContentIntegrationTest/main/ghcontent-ro/a-file-with-content", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content?ref=main", + "git": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs/bbccddee11223344556677889900aabbccddeeff", + "html": "https://github.com/hub4j-test-org/GHContentIntegrationTest/blob/main/ghcontent-ro/a-file-with-content" + } + }, + "commit": { + "sha": "2233445566778899001122334455667788990011", + "node_id": "MDY6Q29tbWl0NDA3NjM1Nzc6MjIzMzQ0NTU2Njc3ODg5OTAwMTEyMjMzNDQ1NTY2Nzc4ODk5MDAxMQ==", + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/commits/2233445566778899001122334455667788990011", + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest/commit/2233445566778899001122334455667788990011", + "author": { + "name": "John Doe", + "email": "john@example.com", + "date": "2024-01-01T00:00:00Z" + }, + "committer": { + "name": "Service Account", + "email": "service@example.com", + "date": "2024-01-01T00:00:00Z" + }, + "tree": { + "sha": "ccdd00112233445566778899aabb00112233eeff", + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/trees/ccdd00112233445566778899aabb00112233eeff" + }, + "message": "Updating with custom author and committer", + "parents": [ + { + "sha": "aaaa111122223333444455556666777788889999", + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/commits/aaaa111122223333444455556666777788889999", + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest/commit/aaaa111122223333444455556666777788889999" + } + ], + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null + } + } +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/mappings/1-user.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/mappings/1-user.json new file mode 100644 index 0000000000..867e80d5f8 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/mappings/1-user.json @@ -0,0 +1,48 @@ +{ + "id": "b9d02cd3-311a-4d49-8de8-aa4220400616", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "1-user.json", + "headers": { + "Date": "Sat, 21 Dec 2019 03:41:23 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4985", + "X-RateLimit-Reset": "1576903221", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding" + ], + "ETag": "W/\"be4370b3c906450f450e411f567ee839\"", + "Last-Modified": "Wed, 18 Dec 2019 01:26:55 GMT", + "X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "unknown, github.v3", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "C977:4ACE:948C05:B32660:5DFD9463" + } + }, + "uuid": "b9d02cd3-311a-4d49-8de8-aa4220400616", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/mappings/2-r_h_ghcontentintegrationtest.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/mappings/2-r_h_ghcontentintegrationtest.json new file mode 100644 index 0000000000..a96af5128d --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/mappings/2-r_h_ghcontentintegrationtest.json @@ -0,0 +1,48 @@ +{ + "id": "f60bd9fd-10cc-4488-b2ce-b618cac83ae2", + "name": "repos_hub4j-test-org_ghcontentintegrationtest", + "request": { + "url": "/repos/hub4j-test-org/GHContentIntegrationTest", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "2-r_h_ghcontentintegrationtest.json", + "headers": { + "Date": "Sat, 21 Dec 2019 03:41:25 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4981", + "X-RateLimit-Reset": "1576903221", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding" + ], + "ETag": "W/\"1e430d4199aa33f3d4673fee6fee2709\"", + "Last-Modified": "Tue, 26 Nov 2019 01:09:49 GMT", + "X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion", + "X-Accepted-OAuth-Scopes": "repo", + "X-GitHub-Media-Type": "unknown, github.v3", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "C977:4ACE:948C3A:B32669:5DFD9463" + } + }, + "uuid": "f60bd9fd-10cc-4488-b2ce-b618cac83ae2", + "persistent": true, + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/mappings/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/mappings/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json new file mode 100644 index 0000000000..6191da8eb7 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/mappings/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json @@ -0,0 +1,24 @@ +{ + "id": "a1b2c3d4-e5f6-7890-abcd-200000000001", + "name": "repos_hub4j-test-org_ghcontentintegrationtest_contents_ghcontent-ro_a-file-with-content", + "request": { + "url": "/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "3-r_h_g_contents_ghcontent-ro_a-file-with-content.json", + "headers": { + "Content-Type": "application/json; charset=utf-8", + "Status": "200 OK" + } + }, + "uuid": "a1b2c3d4-e5f6-7890-abcd-200000000001", + "persistent": true, + "insertionIndex": 3 +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/mappings/4-r_h_g_contents_ghcontent-ro_a-file-with-content-update.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/mappings/4-r_h_g_contents_ghcontent-ro_a-file-with-content-update.json new file mode 100644 index 0000000000..7a06d27795 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitter/mappings/4-r_h_g_contents_ghcontent-ro_a-file-with-content-update.json @@ -0,0 +1,31 @@ +{ + "id": "a1b2c3d4-e5f6-7890-abcd-200000000002", + "name": "repos_hub4j-test-org_ghcontentintegrationtest_contents_ghcontent-ro_a-file-with-content_update", + "request": { + "url": "/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content", + "method": "PUT", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{\"path\":\"ghcontent-ro/a-file-with-content\",\"sha\":\"901fd87750a8e53fe39a219cad50d4f7c80ca272\",\"message\":\"Updating with custom author and committer\",\"content\":\"dXBkYXRlZCBjb250ZW50Cg==\",\"branch\":\"main\",\"author\":{\"name\":\"John Doe\",\"email\":\"john@example.com\"},\"committer\":{\"name\":\"Service Account\",\"email\":\"service@example.com\"}}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 200, + "bodyFileName": "4-r_h_g_contents_ghcontent-ro_a-file-with-content-update.json", + "headers": { + "Content-Type": "application/json; charset=utf-8", + "Status": "200 OK" + } + }, + "uuid": "a1b2c3d4-e5f6-7890-abcd-200000000002", + "persistent": true, + "insertionIndex": 4 +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/__files/1-user.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/__files/1-user.json new file mode 100644 index 0000000000..98e8c76ba2 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/__files/1-user.json @@ -0,0 +1,45 @@ +{ + "login": "bitwiseman", + "id": 1958953, + "node_id": "MDQ6VXNlcjE5NTg5NTM=", + "avatar_url": "https://avatars3.githubusercontent.com/u/1958953?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bitwiseman", + "html_url": "https://github.com/bitwiseman", + "followers_url": "https://api.github.com/users/bitwiseman/followers", + "following_url": "https://api.github.com/users/bitwiseman/following{/other_user}", + "gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions", + "organizations_url": "https://api.github.com/users/bitwiseman/orgs", + "repos_url": "https://api.github.com/users/bitwiseman/repos", + "events_url": "https://api.github.com/users/bitwiseman/events{/privacy}", + "received_events_url": "https://api.github.com/users/bitwiseman/received_events", + "type": "User", + "site_admin": false, + "name": "Liam Newman", + "company": "Cloudbees, Inc.", + "blog": "", + "location": "Seattle, WA, USA", + "email": "bitwiseman@gmail.com", + "hireable": null, + "bio": "https://twitter.com/bitwiseman", + "public_repos": 178, + "public_gists": 7, + "followers": 144, + "following": 9, + "created_at": "2012-07-11T20:38:33Z", + "updated_at": "2019-12-18T01:26:55Z", + "private_gists": 7, + "total_private_repos": 10, + "owned_private_repos": 0, + "disk_usage": 33697, + "collaborators": 0, + "two_factor_authentication": true, + "plan": { + "name": "free", + "space": 976562499, + "collaborators": 0, + "private_repos": 10000 + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/__files/2-r_h_ghcontentintegrationtest.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/__files/2-r_h_ghcontentintegrationtest.json new file mode 100644 index 0000000000..68e3d4f428 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/__files/2-r_h_ghcontentintegrationtest.json @@ -0,0 +1,313 @@ +{ + "id": 40763577, + "node_id": "MDEwOlJlcG9zaXRvcnk0MDc2MzU3Nw==", + "name": "GHContentIntegrationTest", + "full_name": "hub4j-test-org/GHContentIntegrationTest", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest", + "description": "Repository used for integration test of github-api", + "fork": true, + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest", + "forks_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/deployments", + "created_at": "2015-08-15T14:14:57Z", + "updated_at": "2019-11-26T01:09:49Z", + "pushed_at": "2019-11-26T01:09:48Z", + "git_url": "git://github.com/hub4j-test-org/GHContentIntegrationTest.git", + "ssh_url": "git@github.com:hub4j-test-org/GHContentIntegrationTest.git", + "clone_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest.git", + "svn_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest", + "homepage": null, + "size": 52, + "stargazers_count": 1, + "watchers_count": 1, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 41, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 41, + "open_issues": 0, + "watchers": 1, + "default_branch": "main", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "organization": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "parent": { + "id": 19653852, + "node_id": "MDEwOlJlcG9zaXRvcnkxOTY1Mzg1Mg==", + "name": "GHContentIntegrationTest", + "full_name": "kohsuke2/GHContentIntegrationTest", + "private": false, + "owner": { + "login": "kohsuke2", + "id": 1329242, + "node_id": "MDQ6VXNlcjEzMjkyNDI=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1329242?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/kohsuke2", + "html_url": "https://github.com/kohsuke2", + "followers_url": "https://api.github.com/users/kohsuke2/followers", + "following_url": "https://api.github.com/users/kohsuke2/following{/other_user}", + "gists_url": "https://api.github.com/users/kohsuke2/gists{/gist_id}", + "starred_url": "https://api.github.com/users/kohsuke2/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/kohsuke2/subscriptions", + "organizations_url": "https://api.github.com/users/kohsuke2/orgs", + "repos_url": "https://api.github.com/users/kohsuke2/repos", + "events_url": "https://api.github.com/users/kohsuke2/events{/privacy}", + "received_events_url": "https://api.github.com/users/kohsuke2/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/kohsuke2/GHContentIntegrationTest", + "description": "Repository used for integration test of github-api", + "fork": true, + "url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest", + "forks_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/forks", + "keys_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/teams", + "hooks_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/hooks", + "issue_events_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/events", + "assignees_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/tags", + "blobs_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/languages", + "stargazers_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/stargazers", + "contributors_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/contributors", + "subscribers_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/subscribers", + "subscription_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/subscription", + "commits_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/merges", + "archive_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/downloads", + "issues_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/labels{/name}", + "releases_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/deployments", + "created_at": "2014-05-10T22:50:30Z", + "updated_at": "2018-11-07T15:36:19Z", + "pushed_at": "2018-11-07T15:36:18Z", + "git_url": "git://github.com/kohsuke2/GHContentIntegrationTest.git", + "ssh_url": "git@github.com:kohsuke2/GHContentIntegrationTest.git", + "clone_url": "https://github.com/kohsuke2/GHContentIntegrationTest.git", + "svn_url": "https://github.com/kohsuke2/GHContentIntegrationTest", + "homepage": null, + "size": 111, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 1, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 1, + "open_issues": 0, + "watchers": 0, + "default_branch": "main" + }, + "source": { + "id": 14779458, + "node_id": "MDEwOlJlcG9zaXRvcnkxNDc3OTQ1OA==", + "name": "github-api-test-1", + "full_name": "farmdawgnation/github-api-test-1", + "private": false, + "owner": { + "login": "farmdawgnation", + "id": 620189, + "node_id": "MDQ6VXNlcjYyMDE4OQ==", + "avatar_url": "https://avatars2.githubusercontent.com/u/620189?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/farmdawgnation", + "html_url": "https://github.com/farmdawgnation", + "followers_url": "https://api.github.com/users/farmdawgnation/followers", + "following_url": "https://api.github.com/users/farmdawgnation/following{/other_user}", + "gists_url": "https://api.github.com/users/farmdawgnation/gists{/gist_id}", + "starred_url": "https://api.github.com/users/farmdawgnation/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/farmdawgnation/subscriptions", + "organizations_url": "https://api.github.com/users/farmdawgnation/orgs", + "repos_url": "https://api.github.com/users/farmdawgnation/repos", + "events_url": "https://api.github.com/users/farmdawgnation/events{/privacy}", + "received_events_url": "https://api.github.com/users/farmdawgnation/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/farmdawgnation/github-api-test-1", + "description": "Repository used for integration test of github-api", + "fork": false, + "url": "https://api.github.com/repos/farmdawgnation/github-api-test-1", + "forks_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/forks", + "keys_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/teams", + "hooks_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/hooks", + "issue_events_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues/events{/number}", + "events_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/events", + "assignees_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/assignees{/user}", + "branches_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/branches{/branch}", + "tags_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/tags", + "blobs_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/statuses/{sha}", + "languages_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/languages", + "stargazers_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/stargazers", + "contributors_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/contributors", + "subscribers_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/subscribers", + "subscription_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/subscription", + "commits_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/contents/{+path}", + "compare_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/merges", + "archive_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/downloads", + "issues_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues{/number}", + "pulls_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/pulls{/number}", + "milestones_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/milestones{/number}", + "notifications_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/labels{/name}", + "releases_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/releases{/id}", + "deployments_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/deployments", + "created_at": "2013-11-28T14:46:38Z", + "updated_at": "2016-02-05T13:33:23Z", + "pushed_at": "2013-11-28T14:55:36Z", + "git_url": "git://github.com/farmdawgnation/github-api-test-1.git", + "ssh_url": "git@github.com:farmdawgnation/github-api-test-1.git", + "clone_url": "https://github.com/farmdawgnation/github-api-test-1.git", + "svn_url": "https://github.com/farmdawgnation/github-api-test-1", + "homepage": null, + "size": 89, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 60, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "forks": 60, + "open_issues": 0, + "watchers": 0, + "default_branch": "main" + }, + "network_count": 60, + "subscribers_count": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/__files/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/__files/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json new file mode 100644 index 0000000000..b2bf0b6737 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/__files/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json @@ -0,0 +1,18 @@ +{ + "name": "a-file-with-content", + "path": "ghcontent-ro/a-file-with-content", + "sha": "901fd87750a8e53fe39a219cad50d4f7c80ca272", + "size": 22, + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content?ref=main", + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest/blob/main/ghcontent-ro/a-file-with-content", + "git_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs/901fd87750a8e53fe39a219cad50d4f7c80ca272", + "download_url": "https://raw.githubusercontent.com/hub4j-test-org/GHContentIntegrationTest/main/ghcontent-ro/a-file-with-content", + "type": "file", + "content": "dGhhbmtzIGZvciByZWFkaW5nIG1lCg==\n", + "encoding": "base64", + "_links": { + "self": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content?ref=main", + "git": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs/901fd87750a8e53fe39a219cad50d4f7c80ca272", + "html": "https://github.com/hub4j-test-org/GHContentIntegrationTest/blob/main/ghcontent-ro/a-file-with-content" + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/__files/4-r_h_g_contents_ghcontent-ro_a-file-with-content-update.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/__files/4-r_h_g_contents_ghcontent-ro_a-file-with-content-update.json new file mode 100644 index 0000000000..edfaef1ade --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/__files/4-r_h_g_contents_ghcontent-ro_a-file-with-content-update.json @@ -0,0 +1,52 @@ +{ + "content": { + "name": "a-file-with-content", + "path": "ghcontent-ro/a-file-with-content", + "sha": "bbccddee11223344556677889900aabbccddeeff", + "size": 16, + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content?ref=main", + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest/blob/main/ghcontent-ro/a-file-with-content", + "git_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs/bbccddee11223344556677889900aabbccddeeff", + "download_url": "https://raw.githubusercontent.com/hub4j-test-org/GHContentIntegrationTest/main/ghcontent-ro/a-file-with-content", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content?ref=main", + "git": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs/bbccddee11223344556677889900aabbccddeeff", + "html": "https://github.com/hub4j-test-org/GHContentIntegrationTest/blob/main/ghcontent-ro/a-file-with-content" + } + }, + "commit": { + "sha": "2233445566778899001122334455667788990011", + "node_id": "MDY6Q29tbWl0NDA3NjM1Nzc6MjIzMzQ0NTU2Njc3ODg5OTAwMTEyMjMzNDQ1NTY2Nzc4ODk5MDAxMQ==", + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/commits/2233445566778899001122334455667788990011", + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest/commit/2233445566778899001122334455667788990011", + "author": { + "name": "John Doe", + "email": "john@example.com", + "date": "2009-02-13T23:31:30Z" + }, + "committer": { + "name": "Service Account", + "email": "service@example.com", + "date": "2009-02-13T23:31:30Z" + }, + "tree": { + "sha": "ccdd00112233445566778899aabb00112233eeff", + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/trees/ccdd00112233445566778899aabb00112233eeff" + }, + "message": "Updating with custom author and committer", + "parents": [ + { + "sha": "aaaa111122223333444455556666777788889999", + "url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/commits/aaaa111122223333444455556666777788889999", + "html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest/commit/aaaa111122223333444455556666777788889999" + } + ], + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null + } + } +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/mappings/1-user.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/mappings/1-user.json new file mode 100644 index 0000000000..867e80d5f8 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/mappings/1-user.json @@ -0,0 +1,48 @@ +{ + "id": "b9d02cd3-311a-4d49-8de8-aa4220400616", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "1-user.json", + "headers": { + "Date": "Sat, 21 Dec 2019 03:41:23 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4985", + "X-RateLimit-Reset": "1576903221", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding" + ], + "ETag": "W/\"be4370b3c906450f450e411f567ee839\"", + "Last-Modified": "Wed, 18 Dec 2019 01:26:55 GMT", + "X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "unknown, github.v3", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "C977:4ACE:948C05:B32660:5DFD9463" + } + }, + "uuid": "b9d02cd3-311a-4d49-8de8-aa4220400616", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/mappings/2-r_h_ghcontentintegrationtest.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/mappings/2-r_h_ghcontentintegrationtest.json new file mode 100644 index 0000000000..a96af5128d --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/mappings/2-r_h_ghcontentintegrationtest.json @@ -0,0 +1,48 @@ +{ + "id": "f60bd9fd-10cc-4488-b2ce-b618cac83ae2", + "name": "repos_hub4j-test-org_ghcontentintegrationtest", + "request": { + "url": "/repos/hub4j-test-org/GHContentIntegrationTest", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "2-r_h_ghcontentintegrationtest.json", + "headers": { + "Date": "Sat, 21 Dec 2019 03:41:25 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4981", + "X-RateLimit-Reset": "1576903221", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding" + ], + "ETag": "W/\"1e430d4199aa33f3d4673fee6fee2709\"", + "Last-Modified": "Tue, 26 Nov 2019 01:09:49 GMT", + "X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion", + "X-Accepted-OAuth-Scopes": "repo", + "X-GitHub-Media-Type": "unknown, github.v3", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "C977:4ACE:948C3A:B32669:5DFD9463" + } + }, + "uuid": "f60bd9fd-10cc-4488-b2ce-b618cac83ae2", + "persistent": true, + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/mappings/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/mappings/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json new file mode 100644 index 0000000000..6191da8eb7 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/mappings/3-r_h_g_contents_ghcontent-ro_a-file-with-content.json @@ -0,0 +1,24 @@ +{ + "id": "a1b2c3d4-e5f6-7890-abcd-200000000001", + "name": "repos_hub4j-test-org_ghcontentintegrationtest_contents_ghcontent-ro_a-file-with-content", + "request": { + "url": "/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "3-r_h_g_contents_ghcontent-ro_a-file-with-content.json", + "headers": { + "Content-Type": "application/json; charset=utf-8", + "Status": "200 OK" + } + }, + "uuid": "a1b2c3d4-e5f6-7890-abcd-200000000001", + "persistent": true, + "insertionIndex": 3 +} diff --git a/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/mappings/4-r_h_g_contents_ghcontent-ro_a-file-with-content-update.json b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/mappings/4-r_h_g_contents_ghcontent-ro_a-file-with-content-update.json new file mode 100644 index 0000000000..c5664af76e --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHContentIntegrationTest/wiremock/testUpdateWithAuthorCommitterAndDate/mappings/4-r_h_g_contents_ghcontent-ro_a-file-with-content-update.json @@ -0,0 +1,31 @@ +{ + "id": "a1b2c3d4-e5f6-7890-abcd-210000000002", + "name": "repos_hub4j-test-org_ghcontentintegrationtest_contents_ghcontent-ro_a-file-with-content_update_with_date", + "request": { + "url": "/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-file-with-content", + "method": "PUT", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{\"path\":\"ghcontent-ro/a-file-with-content\",\"sha\":\"901fd87750a8e53fe39a219cad50d4f7c80ca272\",\"message\":\"Updating with custom author and committer\",\"content\":\"dXBkYXRlZCBjb250ZW50Cg==\",\"branch\":\"main\",\"author\":{\"name\":\"John Doe\",\"email\":\"john@example.com\",\"date\":\"2009-02-13T23:31:30Z\"},\"committer\":{\"name\":\"Service Account\",\"email\":\"service@example.com\",\"date\":\"2009-02-13T23:31:30Z\"}}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 200, + "bodyFileName": "4-r_h_g_contents_ghcontent-ro_a-file-with-content-update.json", + "headers": { + "Content-Type": "application/json; charset=utf-8", + "Status": "200 OK" + } + }, + "uuid": "a1b2c3d4-e5f6-7890-abcd-210000000002", + "persistent": true, + "insertionIndex": 4 +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/issue_unlabeled_deleted_label.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/issue_unlabeled_deleted_label.json new file mode 100644 index 0000000000..d2ee928a6a --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/issue_unlabeled_deleted_label.json @@ -0,0 +1,167 @@ +{ + "action": "unlabeled", + "issue": { + "url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/issues/42", + "repository_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground", + "labels_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/issues/42/labels{/name}", + "comments_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/issues/42/comments", + "events_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/issues/42/events", + "html_url": "https://github.com/gsmet/quarkus-bot-java-playground/issues/42", + "id": 835908684, + "node_id": "MDU6SXNzdWU4MzU5MDg2ODQ=", + "number": 42, + "title": "Test GHEventPayload.Issue label/unlabel", + "user": { + "login": "gsmet", + "id": 1279749, + "node_id": "MDQ6VXNlcjEyNzk3NDk=", + "avatar_url": "https://avatars.githubusercontent.com/u/1279749?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gsmet", + "html_url": "https://github.com/gsmet", + "followers_url": "https://api.github.com/users/gsmet/followers", + "following_url": "https://api.github.com/users/gsmet/following{/other_user}", + "gists_url": "https://api.github.com/users/gsmet/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gsmet/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gsmet/subscriptions", + "organizations_url": "https://api.github.com/users/gsmet/orgs", + "repos_url": "https://api.github.com/users/gsmet/repos", + "events_url": "https://api.github.com/users/gsmet/events{/privacy}", + "received_events_url": "https://api.github.com/users/gsmet/received_events", + "type": "User", + "site_admin": false + }, + "labels": [], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2021-03-19T12:02:09Z", + "updated_at": "2021-03-19T12:02:43Z", + "closed_at": null, + "author_association": "OWNER", + "active_lock_reason": null, + "body": "", + "performed_via_github_app": null + }, + "repository": { + "id": 313384129, + "node_id": "MDEwOlJlcG9zaXRvcnkzMTMzODQxMjk=", + "name": "quarkus-bot-java-playground", + "full_name": "gsmet/quarkus-bot-java-playground", + "private": true, + "owner": { + "login": "gsmet", + "id": 1279749, + "node_id": "MDQ6VXNlcjEyNzk3NDk=", + "avatar_url": "https://avatars.githubusercontent.com/u/1279749?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gsmet", + "html_url": "https://github.com/gsmet", + "followers_url": "https://api.github.com/users/gsmet/followers", + "following_url": "https://api.github.com/users/gsmet/following{/other_user}", + "gists_url": "https://api.github.com/users/gsmet/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gsmet/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gsmet/subscriptions", + "organizations_url": "https://api.github.com/users/gsmet/orgs", + "repos_url": "https://api.github.com/users/gsmet/repos", + "events_url": "https://api.github.com/users/gsmet/events{/privacy}", + "received_events_url": "https://api.github.com/users/gsmet/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/gsmet/quarkus-bot-java-playground", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground", + "forks_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/forks", + "keys_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/teams", + "hooks_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/hooks", + "issue_events_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/issues/events{/number}", + "events_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/events", + "assignees_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/assignees{/user}", + "branches_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/branches{/branch}", + "tags_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/tags", + "blobs_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/statuses/{sha}", + "languages_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/languages", + "stargazers_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/stargazers", + "contributors_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/contributors", + "subscribers_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/subscribers", + "subscription_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/subscription", + "commits_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/contents/{+path}", + "compare_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/merges", + "archive_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/downloads", + "issues_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/issues{/number}", + "pulls_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/pulls{/number}", + "milestones_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/milestones{/number}", + "notifications_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/labels{/name}", + "releases_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/releases{/id}", + "deployments_url": "https://api.github.com/repos/gsmet/quarkus-bot-java-playground/deployments", + "created_at": "2020-11-16T17:55:53Z", + "updated_at": "2020-12-01T08:39:07Z", + "pushed_at": "2020-12-01T08:39:05Z", + "git_url": "git://github.com/gsmet/quarkus-bot-java-playground.git", + "ssh_url": "git@github.com:gsmet/quarkus-bot-java-playground.git", + "clone_url": "https://github.com/gsmet/quarkus-bot-java-playground.git", + "svn_url": "https://github.com/gsmet/quarkus-bot-java-playground", + "homepage": null, + "size": 13, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 1, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 14, + "license": null, + "forks": 1, + "open_issues": 14, + "watchers": 0, + "default_branch": "main" + }, + "sender": { + "login": "gsmet", + "id": 1279749, + "node_id": "MDQ6VXNlcjEyNzk3NDk=", + "avatar_url": "https://avatars.githubusercontent.com/u/1279749?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gsmet", + "html_url": "https://github.com/gsmet", + "followers_url": "https://api.github.com/users/gsmet/followers", + "following_url": "https://api.github.com/users/gsmet/following{/other_user}", + "gists_url": "https://api.github.com/users/gsmet/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gsmet/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gsmet/subscriptions", + "organizations_url": "https://api.github.com/users/gsmet/orgs", + "repos_url": "https://api.github.com/users/gsmet/repos", + "events_url": "https://api.github.com/users/gsmet/events{/privacy}", + "received_events_url": "https://api.github.com/users/gsmet/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": 13005535, + "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTMwMDU1MzU=" + } +} diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/__files/1-user.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/__files/1-user.json new file mode 100644 index 0000000000..5105950e36 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/__files/1-user.json @@ -0,0 +1,48 @@ +{ + "login": "leowebb", + "id": 6266758, + "node_id": "MDQ6VXNlcjYyNjY3NTg=", + "avatar_url": "https://avatars.githubusercontent.com/u/6266758?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/leowebb", + "html_url": "https://github.com/leowebb", + "followers_url": "https://api.github.com/users/leowebb/followers", + "following_url": "https://api.github.com/users/leowebb/following{/other_user}", + "gists_url": "https://api.github.com/users/leowebb/gists{/gist_id}", + "starred_url": "https://api.github.com/users/leowebb/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/leowebb/subscriptions", + "organizations_url": "https://api.github.com/users/leowebb/orgs", + "repos_url": "https://api.github.com/users/leowebb/repos", + "events_url": "https://api.github.com/users/leowebb/events{/privacy}", + "received_events_url": "https://api.github.com/users/leowebb/received_events", + "type": "User", + "user_view_type": "private", + "site_admin": false, + "name": null, + "company": null, + "blog": "", + "location": null, + "email": null, + "hireable": null, + "bio": null, + "twitter_username": null, + "notification_email": null, + "public_repos": 4, + "public_gists": 0, + "followers": 5, + "following": 8, + "created_at": "2013-12-26T20:32:21Z", + "updated_at": "2025-08-04T14:53:23Z", + "private_gists": 1, + "total_private_repos": 5, + "owned_private_repos": 5, + "disk_usage": 24, + "collaborators": 0, + "two_factor_authentication": true, + "plan": { + "name": "free", + "space": 976562499, + "collaborators": 0, + "private_repos": 10000 + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/__files/2-orgs_hub4j-test-org.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/__files/2-orgs_hub4j-test-org.json new file mode 100644 index 0000000000..0464ffc1be --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/__files/2-orgs_hub4j-test-org.json @@ -0,0 +1,76 @@ +{ + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "url": "https://api.github.com/orgs/hub4j-test-org", + "repos_url": "https://api.github.com/orgs/hub4j-test-org/repos", + "events_url": "https://api.github.com/orgs/hub4j-test-org/events", + "hooks_url": "https://api.github.com/orgs/hub4j-test-org/hooks", + "issues_url": "https://api.github.com/orgs/hub4j-test-org/issues", + "members_url": "https://api.github.com/orgs/hub4j-test-org/members{/member}", + "public_members_url": "https://api.github.com/orgs/hub4j-test-org/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "description": "Hub4j Test Org Description (this could be null or blank too)", + "name": "Hub4j Test Org Name (this could be null or blank too)", + "company": null, + "blog": "https://hub4j.url.io/could/be/null", + "location": "Hub4j Test Org Location (this could be null or blank too)", + "email": "hub4jtestorgemail@could.be.null.com", + "twitter_username": null, + "is_verified": false, + "has_organization_projects": true, + "has_repository_projects": true, + "public_repos": 27, + "public_gists": 0, + "followers": 2, + "following": 0, + "html_url": "https://github.com/hub4j-test-org", + "created_at": "2014-05-10T19:39:11Z", + "updated_at": "2025-08-05T00:53:03Z", + "archived_at": null, + "type": "Organization", + "total_private_repos": 8, + "owned_private_repos": 8, + "private_gists": 0, + "disk_usage": 12020, + "collaborators": 1, + "billing_email": "kk@kohsuke.org", + "default_repository_permission": "none", + "members_can_create_repositories": false, + "two_factor_requirement_enabled": false, + "members_allowed_repository_creation_type": "none", + "members_can_create_public_repositories": false, + "members_can_create_private_repositories": false, + "members_can_create_internal_repositories": false, + "members_can_create_pages": true, + "members_can_fork_private_repositories": false, + "web_commit_signoff_required": false, + "deploy_keys_enabled_for_repositories": false, + "members_can_delete_repositories": true, + "members_can_change_repo_visibility": true, + "members_can_invite_outside_collaborators": true, + "members_can_delete_issues": false, + "display_commenter_full_name_setting_enabled": false, + "readers_can_create_discussions": true, + "members_can_create_teams": true, + "members_can_view_dependency_insights": true, + "default_repository_branch": "main", + "members_can_create_public_pages": true, + "members_can_create_private_pages": true, + "plan": { + "name": "free", + "space": 976562499, + "private_repos": 10000, + "filled_seats": 52, + "seats": 3 + }, + "advanced_security_enabled_for_new_repositories": false, + "dependabot_alerts_enabled_for_new_repositories": false, + "dependabot_security_updates_enabled_for_new_repositories": false, + "dependency_graph_enabled_for_new_repositories": false, + "secret_scanning_enabled_for_new_repositories": false, + "secret_scanning_push_protection_enabled_for_new_repositories": false, + "secret_scanning_push_protection_custom_link_enabled": false, + "secret_scanning_push_protection_custom_link": null, + "secret_scanning_validity_checks_enabled": false +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/__files/3-r_h_github-api-template-test.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/__files/3-r_h_github-api-template-test.json new file mode 100644 index 0000000000..0a4f2ee087 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/__files/3-r_h_github-api-template-test.json @@ -0,0 +1,161 @@ +{ + "id": 776220577, + "node_id": "R_kgDOLkQvoQ", + "name": "github-api-template-test", + "full_name": "hub4j-test-org/github-api-template-test", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/github-api-template-test", + "description": "a test template repository used to test kohsuke's github-api", + "fork": false, + "url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test", + "forks_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/deployments", + "created_at": "2024-03-22T23:35:17Z", + "updated_at": "2024-03-22T23:35:17Z", + "pushed_at": "2025-08-05T00:38:59Z", + "git_url": "git://github.com/hub4j-test-org/github-api-template-test.git", + "ssh_url": "git@github.com:hub4j-test-org/github-api-template-test.git", + "clone_url": "https://github.com/hub4j-test-org/github-api-template-test.git", + "svn_url": "https://github.com/hub4j-test-org/github-api-template-test", + "homepage": "http://github-api.kohsuke.org/", + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "allow_forking": true, + "is_template": true, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "main", + "permissions": { + "admin": true, + "maintain": true, + "push": true, + "triage": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": false, + "delete_branch_on_merge": false, + "allow_update_branch": false, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE", + "custom_properties": {}, + "organization": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "security_and_analysis": { + "secret_scanning": { + "status": "disabled" + }, + "secret_scanning_push_protection": { + "status": "disabled" + }, + "dependabot_security_updates": { + "status": "disabled" + }, + "secret_scanning_non_provider_patterns": { + "status": "disabled" + }, + "secret_scanning_validity_checks": { + "status": "disabled" + } + }, + "network_count": 0, + "subscribers_count": 21 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/__files/4-r_h_g_generate.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/__files/4-r_h_g_generate.json new file mode 100644 index 0000000000..5129a8b25f --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/__files/4-r_h_g_generate.json @@ -0,0 +1,132 @@ +{ + "id": 1032196167, + "node_id": "R_kgDOPYYQRw", + "name": "github-api-test", + "full_name": "hub4j-test-org/github-api-test", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/github-api-test", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/hub4j-test-org/github-api-test", + "forks_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/deployments", + "created_at": "2025-08-05T00:55:47Z", + "updated_at": "2025-08-05T00:55:48Z", + "pushed_at": "2025-08-05T00:55:48Z", + "git_url": "git://github.com/hub4j-test-org/github-api-test.git", + "ssh_url": "git@github.com:hub4j-test-org/github-api-test.git", + "clone_url": "https://github.com/hub4j-test-org/github-api-test.git", + "svn_url": "https://github.com/hub4j-test-org/github-api-test", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "main", + "permissions": { + "admin": true, + "maintain": true, + "push": true, + "triage": true, + "pull": true + }, + "custom_properties": {}, + "organization": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "network_count": 0, + "subscribers_count": 0 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/__files/5-r_h_g_branches.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/__files/5-r_h_g_branches.json new file mode 100644 index 0000000000..4382c7c7c8 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/__files/5-r_h_g_branches.json @@ -0,0 +1,36 @@ +[ + { + "name": "branching-test", + "commit": { + "sha": "3d880c57c1ce955efc2b4d506b1e31ee133ee3a7", + "url": "https://api.github.com/repos/hub4j-test-org/github-api-test/commits/3d880c57c1ce955efc2b4d506b1e31ee133ee3a7" + }, + "protected": false, + "protection": { + "enabled": false, + "required_status_checks": { + "enforcement_level": "off", + "contexts": [], + "checks": [] + } + }, + "protection_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/branches/branching-test/protection" + }, + { + "name": "main", + "commit": { + "sha": "8686ca07ae2cacf0f2f6eed6211937ac7d594d30", + "url": "https://api.github.com/repos/hub4j-test-org/github-api-test/commits/8686ca07ae2cacf0f2f6eed6211937ac7d594d30" + }, + "protected": false, + "protection": { + "enabled": false, + "required_status_checks": { + "enforcement_level": "off", + "contexts": [], + "checks": [] + } + }, + "protection_url": "https://api.github.com/repos/hub4j-test-org/github-api-test/branches/main/protection" + } +] \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/__files/6-r_h_g_branches.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/__files/6-r_h_g_branches.json new file mode 100644 index 0000000000..f44ffde90c --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/__files/6-r_h_g_branches.json @@ -0,0 +1,36 @@ +[ + { + "name": "branching-test", + "commit": { + "sha": "ac090707866926bb0de3c00a144eba6e318d1c50", + "url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/commits/ac090707866926bb0de3c00a144eba6e318d1c50" + }, + "protected": false, + "protection": { + "enabled": false, + "required_status_checks": { + "enforcement_level": "off", + "contexts": [], + "checks": [] + } + }, + "protection_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/branches/branching-test/protection" + }, + { + "name": "main", + "commit": { + "sha": "ac090707866926bb0de3c00a144eba6e318d1c50", + "url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/commits/ac090707866926bb0de3c00a144eba6e318d1c50" + }, + "protected": false, + "protection": { + "enabled": false, + "required_status_checks": { + "enforcement_level": "off", + "contexts": [], + "checks": [] + } + }, + "protection_url": "https://api.github.com/repos/hub4j-test-org/github-api-template-test/branches/main/protection" + } +] \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/mappings/1-user.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/mappings/1-user.json new file mode 100644 index 0000000000..29e3c40134 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/mappings/1-user.json @@ -0,0 +1,48 @@ +{ + "id": "6499492a-8b31-4428-a249-2ca6c5393d6b", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "1-user.json", + "headers": { + "Date": "Tue, 05 Aug 2025 00:55:45 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"669f0448ff8e82094e6e29310e9ca58b4f0a0260025d1dae31da3371b4665be4\"", + "Last-Modified": "Mon, 04 Aug 2025 14:53:23 GMT", + "X-OAuth-Scopes": "admin:enterprise, admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, admin:ssh_signing_key, audit_log, codespace, copilot, delete:packages, gist, notifications, project, repo, user, workflow, write:discussion, write:network_configurations, write:packages", + "X-Accepted-OAuth-Scopes": "", + "github-authentication-token-expiration": "2025-09-04 00:52:39 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4985", + "X-RateLimit-Reset": "1754358782", + "X-RateLimit-Used": "15", + "X-RateLimit-Resource": "core", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "9540:2250F3:7A20E9:F8CD50:68915691", + "Server": "github.com" + } + }, + "uuid": "6499492a-8b31-4428-a249-2ca6c5393d6b", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/mappings/2-orgs_hub4j-test-org.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/mappings/2-orgs_hub4j-test-org.json new file mode 100644 index 0000000000..49ab4ecb02 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/mappings/2-orgs_hub4j-test-org.json @@ -0,0 +1,48 @@ +{ + "id": "e77910fb-b184-44cc-a9f4-4caab65e3c7b", + "name": "orgs_hub4j-test-org", + "request": { + "url": "/orgs/hub4j-test-org", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "2-orgs_hub4j-test-org.json", + "headers": { + "Date": "Tue, 05 Aug 2025 00:55:47 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"879ba0dff2068c70ca6713fc68d369478e0d292070140c8511781742684fc36f\"", + "Last-Modified": "Tue, 05 Aug 2025 00:53:03 GMT", + "X-OAuth-Scopes": "admin:enterprise, admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, admin:ssh_signing_key, audit_log, codespace, copilot, delete:packages, gist, notifications, project, repo, user, workflow, write:discussion, write:network_configurations, write:packages", + "X-Accepted-OAuth-Scopes": "admin:org, read:org, repo, user, write:org", + "github-authentication-token-expiration": "2025-09-04 00:52:39 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4979", + "X-RateLimit-Reset": "1754358782", + "X-RateLimit-Used": "21", + "X-RateLimit-Resource": "core", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "9552:219362:7E6EB8:1014ED0:68915692", + "Server": "github.com" + } + }, + "uuid": "e77910fb-b184-44cc-a9f4-4caab65e3c7b", + "persistent": true, + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/mappings/3-r_h_github-api-template-test.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/mappings/3-r_h_github-api-template-test.json new file mode 100644 index 0000000000..1fd2a28354 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/mappings/3-r_h_github-api-template-test.json @@ -0,0 +1,48 @@ +{ + "id": "3be380d5-6c6b-4bec-9c17-8c6950b6ffd3", + "name": "repos_hub4j-test-org_github-api-template-test", + "request": { + "url": "/repos/hub4j-test-org/github-api-template-test", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "3-r_h_github-api-template-test.json", + "headers": { + "Date": "Tue, 05 Aug 2025 00:55:47 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"92af1f8638a2230bc80dac77f029817ec674cc2815523fa77313aa7cf9ab61a3\"", + "Last-Modified": "Fri, 22 Mar 2024 23:35:17 GMT", + "X-OAuth-Scopes": "admin:enterprise, admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, admin:ssh_signing_key, audit_log, codespace, copilot, delete:packages, gist, notifications, project, repo, user, workflow, write:discussion, write:network_configurations, write:packages", + "X-Accepted-OAuth-Scopes": "repo", + "github-authentication-token-expiration": "2025-09-04 00:52:39 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4978", + "X-RateLimit-Reset": "1754358782", + "X-RateLimit-Used": "22", + "X-RateLimit-Resource": "core", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "9562:31FBF7:856AC1:10EB80C:68915693", + "Server": "github.com" + } + }, + "uuid": "3be380d5-6c6b-4bec-9c17-8c6950b6ffd3", + "persistent": true, + "insertionIndex": 3 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/mappings/4-r_h_g_generate.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/mappings/4-r_h_g_generate.json new file mode 100644 index 0000000000..2cf8d759c8 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/mappings/4-r_h_g_generate.json @@ -0,0 +1,55 @@ +{ + "id": "2407cebd-d3e5-4ac0-b724-7fd80d1db097", + "name": "repos_hub4j-test-org_github-api-template-test_generate", + "request": { + "url": "/repos/hub4j-test-org/github-api-template-test/generate", + "method": "POST", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{\"owner\":\"hub4j-test-org\",\"include_all_branches\":true,\"name\":\"github-api-test\"}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 201, + "bodyFileName": "4-r_h_g_generate.json", + "headers": { + "Date": "Tue, 05 Aug 2025 00:55:48 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "\"e29df3c395d2e6d1d91db004f4f3c0c0087ad36aa5d003fb0d6029bbf6a75f55\"", + "X-OAuth-Scopes": "admin:enterprise, admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, admin:ssh_signing_key, audit_log, codespace, copilot, delete:packages, gist, notifications, project, repo, user, workflow, write:discussion, write:network_configurations, write:packages", + "X-Accepted-OAuth-Scopes": "", + "github-authentication-token-expiration": "2025-09-04 00:52:39 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4977", + "X-RateLimit-Reset": "1754358782", + "X-RateLimit-Used": "23", + "X-RateLimit-Resource": "core", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "956A:335062:84A4C9:10D80D1:68915693", + "Server": "github.com", + "Location": "https://api.github.com/repos/hub4j-test-org/github-api-test" + } + }, + "uuid": "2407cebd-d3e5-4ac0-b724-7fd80d1db097", + "persistent": true, + "insertionIndex": 4 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/mappings/5-r_h_g_branches.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/mappings/5-r_h_g_branches.json new file mode 100644 index 0000000000..17f8789293 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/mappings/5-r_h_g_branches.json @@ -0,0 +1,47 @@ +{ + "id": "ac312ba2-dff5-4e0e-a35c-953dadee48a0", + "name": "repos_hub4j-test-org_github-api-test_branches", + "request": { + "url": "/repos/hub4j-test-org/github-api-test/branches", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "5-r_h_g_branches.json", + "headers": { + "Date": "Tue, 05 Aug 2025 00:55:50 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"36a6a88cd2f82a248d3c28db752145a9d6a4b94fc08acb28b38439d632e2ce39\"", + "X-OAuth-Scopes": "admin:enterprise, admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, admin:ssh_signing_key, audit_log, codespace, copilot, delete:packages, gist, notifications, project, repo, user, workflow, write:discussion, write:network_configurations, write:packages", + "X-Accepted-OAuth-Scopes": "", + "github-authentication-token-expiration": "2025-09-04 00:52:39 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4976", + "X-RateLimit-Reset": "1754358782", + "X-RateLimit-Used": "24", + "X-RateLimit-Resource": "core", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "9578:3BF659:84AA5D:10D4D8F:68915695", + "Server": "github.com" + } + }, + "uuid": "ac312ba2-dff5-4e0e-a35c-953dadee48a0", + "persistent": true, + "insertionIndex": 5 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/mappings/6-r_h_g_branches.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/mappings/6-r_h_g_branches.json new file mode 100644 index 0000000000..ababbca7e7 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testCreateRepositoryWithTemplateAndIncludeAllBranches/mappings/6-r_h_g_branches.json @@ -0,0 +1,47 @@ +{ + "id": "66707ce2-4617-443d-9d19-22d040f3f764", + "name": "repos_hub4j-test-org_github-api-template-test_branches", + "request": { + "url": "/repos/hub4j-test-org/github-api-template-test/branches", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "6-r_h_g_branches.json", + "headers": { + "Date": "Tue, 05 Aug 2025 00:55:50 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"fdb42d177a2b0fe69755920b8d8be9843687bf2b692b426cb6c4e0cfdcf1082d\"", + "X-OAuth-Scopes": "admin:enterprise, admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, admin:ssh_signing_key, audit_log, codespace, copilot, delete:packages, gist, notifications, project, repo, user, workflow, write:discussion, write:network_configurations, write:packages", + "X-Accepted-OAuth-Scopes": "", + "github-authentication-token-expiration": "2025-09-04 00:52:39 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4975", + "X-RateLimit-Reset": "1754358782", + "X-RateLimit-Used": "25", + "X-RateLimit-Resource": "core", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "9586:188203:826FEE:1090115:68915696", + "Server": "github.com" + } + }, + "uuid": "66707ce2-4617-443d-9d19-22d040f3f764", + "persistent": true, + "insertionIndex": 6 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/__files/1-user.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/__files/1-user.json new file mode 100644 index 0000000000..76578d3e42 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/__files/1-user.json @@ -0,0 +1,36 @@ +{ + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false, + "name": "KIMSIWOO", + "company": "Inha university", + "blog": "", + "location": null, + "email": "sa20207@naver.com", + "hireable": null, + "bio": null, + "twitter_username": null, + "notification_email": "sa20207@naver.com", + "public_repos": 34, + "public_gists": 0, + "followers": 2, + "following": 2, + "created_at": "2021-07-02T07:40:16Z", + "updated_at": "2025-03-03T13:26:53Z" +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/__files/2-r_s_for-test.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/__files/2-r_s_for-test.json new file mode 100644 index 0000000000..c2de0d78c0 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/__files/2-r_s_for-test.json @@ -0,0 +1,139 @@ +{ + "id": 933252913, + "node_id": "R_kgDON6BPMQ", + "name": "for-test", + "full_name": "seate/for-test", + "private": false, + "owner": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/seate/for-test", + "description": "깃허브 í…ŒėŠ¤íŠ¸ėšŠ 레íŦ", + "fork": false, + "url": "https://api.github.com/repos/seate/for-test", + "forks_url": "https://api.github.com/repos/seate/for-test/forks", + "keys_url": "https://api.github.com/repos/seate/for-test/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/seate/for-test/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/seate/for-test/teams", + "hooks_url": "https://api.github.com/repos/seate/for-test/hooks", + "issue_events_url": "https://api.github.com/repos/seate/for-test/issues/events{/number}", + "events_url": "https://api.github.com/repos/seate/for-test/events", + "assignees_url": "https://api.github.com/repos/seate/for-test/assignees{/user}", + "branches_url": "https://api.github.com/repos/seate/for-test/branches{/branch}", + "tags_url": "https://api.github.com/repos/seate/for-test/tags", + "blobs_url": "https://api.github.com/repos/seate/for-test/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/seate/for-test/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/seate/for-test/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/seate/for-test/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/seate/for-test/statuses/{sha}", + "languages_url": "https://api.github.com/repos/seate/for-test/languages", + "stargazers_url": "https://api.github.com/repos/seate/for-test/stargazers", + "contributors_url": "https://api.github.com/repos/seate/for-test/contributors", + "subscribers_url": "https://api.github.com/repos/seate/for-test/subscribers", + "subscription_url": "https://api.github.com/repos/seate/for-test/subscription", + "commits_url": "https://api.github.com/repos/seate/for-test/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/seate/for-test/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/seate/for-test/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/seate/for-test/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/seate/for-test/contents/{+path}", + "compare_url": "https://api.github.com/repos/seate/for-test/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/seate/for-test/merges", + "archive_url": "https://api.github.com/repos/seate/for-test/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/seate/for-test/downloads", + "issues_url": "https://api.github.com/repos/seate/for-test/issues{/number}", + "pulls_url": "https://api.github.com/repos/seate/for-test/pulls{/number}", + "milestones_url": "https://api.github.com/repos/seate/for-test/milestones{/number}", + "notifications_url": "https://api.github.com/repos/seate/for-test/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/seate/for-test/labels{/name}", + "releases_url": "https://api.github.com/repos/seate/for-test/releases{/id}", + "deployments_url": "https://api.github.com/repos/seate/for-test/deployments", + "created_at": "2025-02-15T14:21:31Z", + "updated_at": "2025-03-15T16:06:51Z", + "pushed_at": "2025-03-15T16:17:01Z", + "git_url": "git://github.com/seate/for-test.git", + "ssh_url": "git@github.com:seate/for-test.git", + "clone_url": "https://github.com/seate/for-test.git", + "svn_url": "https://github.com/seate/for-test", + "homepage": null, + "size": 62, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 3, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 3, + "watchers": 0, + "default_branch": "develop", + "permissions": { + "admin": true, + "maintain": true, + "push": true, + "triage": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": true, + "delete_branch_on_merge": false, + "allow_update_branch": false, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE", + "security_and_analysis": { + "secret_scanning": { + "status": "disabled" + }, + "secret_scanning_push_protection": { + "status": "disabled" + }, + "dependabot_security_updates": { + "status": "disabled" + }, + "secret_scanning_non_provider_patterns": { + "status": "disabled" + }, + "secret_scanning_validity_checks": { + "status": "disabled" + } + }, + "network_count": 0, + "subscribers_count": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/__files/3-r_s_f_pulls_9.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/__files/3-r_s_f_pulls_9.json new file mode 100644 index 0000000000..11347dec30 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/__files/3-r_s_f_pulls_9.json @@ -0,0 +1,372 @@ +{ + "url": "https://api.github.com/repos/seate/for-test/pulls/9", + "id": 2395455682, + "node_id": "PR_kwDON6BPMc6Ox8DC", + "html_url": "https://github.com/seate/for-test/pull/9", + "diff_url": "https://github.com/seate/for-test/pull/9.diff", + "patch_url": "https://github.com/seate/for-test/pull/9.patch", + "issue_url": "https://api.github.com/repos/seate/for-test/issues/9", + "number": 9, + "state": "open", + "locked": false, + "title": "github-api enable pull request auto merge test", + "user": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "body": "github-api enable pull request auto merge test", + "created_at": "2025-03-15T16:07:53Z", + "updated_at": "2025-03-15T16:18:20Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": "25a5888073ee3d2a975e012492950dddb8c346dc", + "assignee": null, + "assignees": [], + "requested_reviewers": [], + "requested_teams": [], + "labels": [], + "milestone": null, + "draft": false, + "commits_url": "https://api.github.com/repos/seate/for-test/pulls/9/commits", + "review_comments_url": "https://api.github.com/repos/seate/for-test/pulls/9/comments", + "review_comment_url": "https://api.github.com/repos/seate/for-test/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/seate/for-test/issues/9/comments", + "statuses_url": "https://api.github.com/repos/seate/for-test/statuses/4888b44d7204dd05680e90159af839c8b1194b6d", + "head": { + "label": "seate:test1", + "ref": "test1", + "sha": "4888b44d7204dd05680e90159af839c8b1194b6d", + "user": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 933252913, + "node_id": "R_kgDON6BPMQ", + "name": "for-test", + "full_name": "seate/for-test", + "private": false, + "owner": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/seate/for-test", + "description": "깃허브 í…ŒėŠ¤íŠ¸ėšŠ 레íŦ", + "fork": false, + "url": "https://api.github.com/repos/seate/for-test", + "forks_url": "https://api.github.com/repos/seate/for-test/forks", + "keys_url": "https://api.github.com/repos/seate/for-test/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/seate/for-test/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/seate/for-test/teams", + "hooks_url": "https://api.github.com/repos/seate/for-test/hooks", + "issue_events_url": "https://api.github.com/repos/seate/for-test/issues/events{/number}", + "events_url": "https://api.github.com/repos/seate/for-test/events", + "assignees_url": "https://api.github.com/repos/seate/for-test/assignees{/user}", + "branches_url": "https://api.github.com/repos/seate/for-test/branches{/branch}", + "tags_url": "https://api.github.com/repos/seate/for-test/tags", + "blobs_url": "https://api.github.com/repos/seate/for-test/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/seate/for-test/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/seate/for-test/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/seate/for-test/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/seate/for-test/statuses/{sha}", + "languages_url": "https://api.github.com/repos/seate/for-test/languages", + "stargazers_url": "https://api.github.com/repos/seate/for-test/stargazers", + "contributors_url": "https://api.github.com/repos/seate/for-test/contributors", + "subscribers_url": "https://api.github.com/repos/seate/for-test/subscribers", + "subscription_url": "https://api.github.com/repos/seate/for-test/subscription", + "commits_url": "https://api.github.com/repos/seate/for-test/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/seate/for-test/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/seate/for-test/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/seate/for-test/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/seate/for-test/contents/{+path}", + "compare_url": "https://api.github.com/repos/seate/for-test/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/seate/for-test/merges", + "archive_url": "https://api.github.com/repos/seate/for-test/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/seate/for-test/downloads", + "issues_url": "https://api.github.com/repos/seate/for-test/issues{/number}", + "pulls_url": "https://api.github.com/repos/seate/for-test/pulls{/number}", + "milestones_url": "https://api.github.com/repos/seate/for-test/milestones{/number}", + "notifications_url": "https://api.github.com/repos/seate/for-test/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/seate/for-test/labels{/name}", + "releases_url": "https://api.github.com/repos/seate/for-test/releases{/id}", + "deployments_url": "https://api.github.com/repos/seate/for-test/deployments", + "created_at": "2025-02-15T14:21:31Z", + "updated_at": "2025-03-15T16:06:51Z", + "pushed_at": "2025-03-15T16:17:01Z", + "git_url": "git://github.com/seate/for-test.git", + "ssh_url": "git@github.com:seate/for-test.git", + "clone_url": "https://github.com/seate/for-test.git", + "svn_url": "https://github.com/seate/for-test", + "homepage": null, + "size": 62, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 3, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 3, + "watchers": 0, + "default_branch": "develop" + } + }, + "base": { + "label": "seate:develop", + "ref": "develop", + "sha": "2bc9cde73b377e4d0ebda0d19f636644808388f5", + "user": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 933252913, + "node_id": "R_kgDON6BPMQ", + "name": "for-test", + "full_name": "seate/for-test", + "private": false, + "owner": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/seate/for-test", + "description": "깃허브 í…ŒėŠ¤íŠ¸ėšŠ 레íŦ", + "fork": false, + "url": "https://api.github.com/repos/seate/for-test", + "forks_url": "https://api.github.com/repos/seate/for-test/forks", + "keys_url": "https://api.github.com/repos/seate/for-test/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/seate/for-test/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/seate/for-test/teams", + "hooks_url": "https://api.github.com/repos/seate/for-test/hooks", + "issue_events_url": "https://api.github.com/repos/seate/for-test/issues/events{/number}", + "events_url": "https://api.github.com/repos/seate/for-test/events", + "assignees_url": "https://api.github.com/repos/seate/for-test/assignees{/user}", + "branches_url": "https://api.github.com/repos/seate/for-test/branches{/branch}", + "tags_url": "https://api.github.com/repos/seate/for-test/tags", + "blobs_url": "https://api.github.com/repos/seate/for-test/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/seate/for-test/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/seate/for-test/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/seate/for-test/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/seate/for-test/statuses/{sha}", + "languages_url": "https://api.github.com/repos/seate/for-test/languages", + "stargazers_url": "https://api.github.com/repos/seate/for-test/stargazers", + "contributors_url": "https://api.github.com/repos/seate/for-test/contributors", + "subscribers_url": "https://api.github.com/repos/seate/for-test/subscribers", + "subscription_url": "https://api.github.com/repos/seate/for-test/subscription", + "commits_url": "https://api.github.com/repos/seate/for-test/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/seate/for-test/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/seate/for-test/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/seate/for-test/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/seate/for-test/contents/{+path}", + "compare_url": "https://api.github.com/repos/seate/for-test/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/seate/for-test/merges", + "archive_url": "https://api.github.com/repos/seate/for-test/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/seate/for-test/downloads", + "issues_url": "https://api.github.com/repos/seate/for-test/issues{/number}", + "pulls_url": "https://api.github.com/repos/seate/for-test/pulls{/number}", + "milestones_url": "https://api.github.com/repos/seate/for-test/milestones{/number}", + "notifications_url": "https://api.github.com/repos/seate/for-test/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/seate/for-test/labels{/name}", + "releases_url": "https://api.github.com/repos/seate/for-test/releases{/id}", + "deployments_url": "https://api.github.com/repos/seate/for-test/deployments", + "created_at": "2025-02-15T14:21:31Z", + "updated_at": "2025-03-15T16:06:51Z", + "pushed_at": "2025-03-15T16:17:01Z", + "git_url": "git://github.com/seate/for-test.git", + "ssh_url": "git@github.com:seate/for-test.git", + "clone_url": "https://github.com/seate/for-test.git", + "svn_url": "https://github.com/seate/for-test", + "homepage": null, + "size": 62, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 3, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 3, + "watchers": 0, + "default_branch": "develop" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/seate/for-test/pulls/9" + }, + "html": { + "href": "https://github.com/seate/for-test/pull/9" + }, + "issue": { + "href": "https://api.github.com/repos/seate/for-test/issues/9" + }, + "comments": { + "href": "https://api.github.com/repos/seate/for-test/issues/9/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/seate/for-test/pulls/9/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/seate/for-test/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/seate/for-test/pulls/9/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/seate/for-test/statuses/4888b44d7204dd05680e90159af839c8b1194b6d" + } + }, + "author_association": "OWNER", + "auto_merge": { + "enabled_by": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "merge_method": "merge", + "commit_title": "This is commit title.", + "commit_message": "This is commit body." + }, + "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": 16, + "additions": 642, + "deletions": 0, + "changed_files": 19 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/__files/6-r_s_f_pulls_9.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/__files/6-r_s_f_pulls_9.json new file mode 100644 index 0000000000..11347dec30 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/__files/6-r_s_f_pulls_9.json @@ -0,0 +1,372 @@ +{ + "url": "https://api.github.com/repos/seate/for-test/pulls/9", + "id": 2395455682, + "node_id": "PR_kwDON6BPMc6Ox8DC", + "html_url": "https://github.com/seate/for-test/pull/9", + "diff_url": "https://github.com/seate/for-test/pull/9.diff", + "patch_url": "https://github.com/seate/for-test/pull/9.patch", + "issue_url": "https://api.github.com/repos/seate/for-test/issues/9", + "number": 9, + "state": "open", + "locked": false, + "title": "github-api enable pull request auto merge test", + "user": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "body": "github-api enable pull request auto merge test", + "created_at": "2025-03-15T16:07:53Z", + "updated_at": "2025-03-15T16:18:20Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": "25a5888073ee3d2a975e012492950dddb8c346dc", + "assignee": null, + "assignees": [], + "requested_reviewers": [], + "requested_teams": [], + "labels": [], + "milestone": null, + "draft": false, + "commits_url": "https://api.github.com/repos/seate/for-test/pulls/9/commits", + "review_comments_url": "https://api.github.com/repos/seate/for-test/pulls/9/comments", + "review_comment_url": "https://api.github.com/repos/seate/for-test/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/seate/for-test/issues/9/comments", + "statuses_url": "https://api.github.com/repos/seate/for-test/statuses/4888b44d7204dd05680e90159af839c8b1194b6d", + "head": { + "label": "seate:test1", + "ref": "test1", + "sha": "4888b44d7204dd05680e90159af839c8b1194b6d", + "user": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 933252913, + "node_id": "R_kgDON6BPMQ", + "name": "for-test", + "full_name": "seate/for-test", + "private": false, + "owner": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/seate/for-test", + "description": "깃허브 í…ŒėŠ¤íŠ¸ėšŠ 레íŦ", + "fork": false, + "url": "https://api.github.com/repos/seate/for-test", + "forks_url": "https://api.github.com/repos/seate/for-test/forks", + "keys_url": "https://api.github.com/repos/seate/for-test/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/seate/for-test/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/seate/for-test/teams", + "hooks_url": "https://api.github.com/repos/seate/for-test/hooks", + "issue_events_url": "https://api.github.com/repos/seate/for-test/issues/events{/number}", + "events_url": "https://api.github.com/repos/seate/for-test/events", + "assignees_url": "https://api.github.com/repos/seate/for-test/assignees{/user}", + "branches_url": "https://api.github.com/repos/seate/for-test/branches{/branch}", + "tags_url": "https://api.github.com/repos/seate/for-test/tags", + "blobs_url": "https://api.github.com/repos/seate/for-test/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/seate/for-test/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/seate/for-test/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/seate/for-test/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/seate/for-test/statuses/{sha}", + "languages_url": "https://api.github.com/repos/seate/for-test/languages", + "stargazers_url": "https://api.github.com/repos/seate/for-test/stargazers", + "contributors_url": "https://api.github.com/repos/seate/for-test/contributors", + "subscribers_url": "https://api.github.com/repos/seate/for-test/subscribers", + "subscription_url": "https://api.github.com/repos/seate/for-test/subscription", + "commits_url": "https://api.github.com/repos/seate/for-test/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/seate/for-test/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/seate/for-test/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/seate/for-test/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/seate/for-test/contents/{+path}", + "compare_url": "https://api.github.com/repos/seate/for-test/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/seate/for-test/merges", + "archive_url": "https://api.github.com/repos/seate/for-test/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/seate/for-test/downloads", + "issues_url": "https://api.github.com/repos/seate/for-test/issues{/number}", + "pulls_url": "https://api.github.com/repos/seate/for-test/pulls{/number}", + "milestones_url": "https://api.github.com/repos/seate/for-test/milestones{/number}", + "notifications_url": "https://api.github.com/repos/seate/for-test/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/seate/for-test/labels{/name}", + "releases_url": "https://api.github.com/repos/seate/for-test/releases{/id}", + "deployments_url": "https://api.github.com/repos/seate/for-test/deployments", + "created_at": "2025-02-15T14:21:31Z", + "updated_at": "2025-03-15T16:06:51Z", + "pushed_at": "2025-03-15T16:17:01Z", + "git_url": "git://github.com/seate/for-test.git", + "ssh_url": "git@github.com:seate/for-test.git", + "clone_url": "https://github.com/seate/for-test.git", + "svn_url": "https://github.com/seate/for-test", + "homepage": null, + "size": 62, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 3, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 3, + "watchers": 0, + "default_branch": "develop" + } + }, + "base": { + "label": "seate:develop", + "ref": "develop", + "sha": "2bc9cde73b377e4d0ebda0d19f636644808388f5", + "user": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 933252913, + "node_id": "R_kgDON6BPMQ", + "name": "for-test", + "full_name": "seate/for-test", + "private": false, + "owner": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/seate/for-test", + "description": "깃허브 í…ŒėŠ¤íŠ¸ėšŠ 레íŦ", + "fork": false, + "url": "https://api.github.com/repos/seate/for-test", + "forks_url": "https://api.github.com/repos/seate/for-test/forks", + "keys_url": "https://api.github.com/repos/seate/for-test/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/seate/for-test/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/seate/for-test/teams", + "hooks_url": "https://api.github.com/repos/seate/for-test/hooks", + "issue_events_url": "https://api.github.com/repos/seate/for-test/issues/events{/number}", + "events_url": "https://api.github.com/repos/seate/for-test/events", + "assignees_url": "https://api.github.com/repos/seate/for-test/assignees{/user}", + "branches_url": "https://api.github.com/repos/seate/for-test/branches{/branch}", + "tags_url": "https://api.github.com/repos/seate/for-test/tags", + "blobs_url": "https://api.github.com/repos/seate/for-test/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/seate/for-test/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/seate/for-test/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/seate/for-test/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/seate/for-test/statuses/{sha}", + "languages_url": "https://api.github.com/repos/seate/for-test/languages", + "stargazers_url": "https://api.github.com/repos/seate/for-test/stargazers", + "contributors_url": "https://api.github.com/repos/seate/for-test/contributors", + "subscribers_url": "https://api.github.com/repos/seate/for-test/subscribers", + "subscription_url": "https://api.github.com/repos/seate/for-test/subscription", + "commits_url": "https://api.github.com/repos/seate/for-test/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/seate/for-test/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/seate/for-test/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/seate/for-test/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/seate/for-test/contents/{+path}", + "compare_url": "https://api.github.com/repos/seate/for-test/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/seate/for-test/merges", + "archive_url": "https://api.github.com/repos/seate/for-test/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/seate/for-test/downloads", + "issues_url": "https://api.github.com/repos/seate/for-test/issues{/number}", + "pulls_url": "https://api.github.com/repos/seate/for-test/pulls{/number}", + "milestones_url": "https://api.github.com/repos/seate/for-test/milestones{/number}", + "notifications_url": "https://api.github.com/repos/seate/for-test/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/seate/for-test/labels{/name}", + "releases_url": "https://api.github.com/repos/seate/for-test/releases{/id}", + "deployments_url": "https://api.github.com/repos/seate/for-test/deployments", + "created_at": "2025-02-15T14:21:31Z", + "updated_at": "2025-03-15T16:06:51Z", + "pushed_at": "2025-03-15T16:17:01Z", + "git_url": "git://github.com/seate/for-test.git", + "ssh_url": "git@github.com:seate/for-test.git", + "clone_url": "https://github.com/seate/for-test.git", + "svn_url": "https://github.com/seate/for-test", + "homepage": null, + "size": 62, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 3, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 3, + "watchers": 0, + "default_branch": "develop" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/seate/for-test/pulls/9" + }, + "html": { + "href": "https://github.com/seate/for-test/pull/9" + }, + "issue": { + "href": "https://api.github.com/repos/seate/for-test/issues/9" + }, + "comments": { + "href": "https://api.github.com/repos/seate/for-test/issues/9/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/seate/for-test/pulls/9/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/seate/for-test/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/seate/for-test/pulls/9/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/seate/for-test/statuses/4888b44d7204dd05680e90159af839c8b1194b6d" + } + }, + "author_association": "OWNER", + "auto_merge": { + "enabled_by": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "merge_method": "merge", + "commit_title": "This is commit title.", + "commit_message": "This is commit body." + }, + "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": 16, + "additions": 642, + "deletions": 0, + "changed_files": 19 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/__files/7-users_seate.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/__files/7-users_seate.json new file mode 100644 index 0000000000..ae11f23591 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/__files/7-users_seate.json @@ -0,0 +1,35 @@ +{ + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false, + "name": "KIMSIWOO", + "company": "Inha university", + "blog": "", + "location": null, + "email": "sa20207@naver.com", + "hireable": null, + "bio": null, + "twitter_username": null, + "public_repos": 34, + "public_gists": 0, + "followers": 2, + "following": 2, + "created_at": "2021-07-02T07:40:16Z", + "updated_at": "2025-03-03T13:26:53Z" +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/1-user.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/1-user.json new file mode 100644 index 0000000000..4b252141e4 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/1-user.json @@ -0,0 +1,47 @@ +{ + "id": "08dbdf10-b416-4ff3-b2f8-3985f3f99bb9", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "1-user.json", + "headers": { + "Date": "Sat, 15 Mar 2025 18:13:24 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"91439c9cd22b1066c90ef899df4f995dcda9ed34b86d5e107b7c311aaaff2136\"", + "Last-Modified": "Mon, 03 Mar 2025 13:26:53 GMT", + "X-OAuth-Scopes": "admin:repo_hook, gist, notifications, read:discussion, read:org, read:project, repo, user:email", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4989", + "X-RateLimit-Reset": "1742065904", + "X-RateLimit-Used": "11", + "X-RateLimit-Resource": "core", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-GitHub-Request-Id": "F688:2B46D8:3A9875:59C85D:67D5C344" + } + }, + "uuid": "08dbdf10-b416-4ff3-b2f8-3985f3f99bb9", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/2-r_s_for-test.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/2-r_s_for-test.json new file mode 100644 index 0000000000..6fcd708edf --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/2-r_s_for-test.json @@ -0,0 +1,47 @@ +{ + "id": "efe9930f-f284-49cb-ac98-1870d22d0454", + "name": "repos_seate_for-test", + "request": { + "url": "/repos/seate/for-test", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "2-r_s_for-test.json", + "headers": { + "Date": "Sat, 15 Mar 2025 18:13:27 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"9181d2d37a58759c6739fb93cdf26cbc7b9cc04f34e87456932f65921cb5473d\"", + "Last-Modified": "Sat, 15 Mar 2025 16:06:51 GMT", + "X-OAuth-Scopes": "admin:repo_hook, gist, notifications, read:discussion, read:org, read:project, repo, user:email", + "X-Accepted-OAuth-Scopes": "repo", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4984", + "X-RateLimit-Reset": "1742065904", + "X-RateLimit-Used": "16", + "X-RateLimit-Resource": "core", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-GitHub-Request-Id": "F68A:165A85:3BCFDE:5B22D9:67D5C347" + } + }, + "uuid": "efe9930f-f284-49cb-ac98-1870d22d0454", + "persistent": true, + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/3-r_s_f_pulls_9.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/3-r_s_f_pulls_9.json new file mode 100644 index 0000000000..f3a8eab80c --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/3-r_s_f_pulls_9.json @@ -0,0 +1,50 @@ +{ + "id": "4b26d080-5f51-45ea-90b9-dfbe0751cdb5", + "name": "repos_seate_for-test_pulls_9", + "request": { + "url": "/repos/seate/for-test/pulls/9", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "3-r_s_f_pulls_9.json", + "headers": { + "Date": "Sat, 15 Mar 2025 18:13:27 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"4cb91abd4bd5effc3228763b88c5abec155f063e483efaa6ba284bb351e27687\"", + "Last-Modified": "Sat, 15 Mar 2025 16:18:20 GMT", + "X-OAuth-Scopes": "admin:repo_hook, gist, notifications, read:discussion, read:org, read:project, repo, user:email", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4983", + "X-RateLimit-Reset": "1742065904", + "X-RateLimit-Used": "17", + "X-RateLimit-Resource": "core", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-GitHub-Request-Id": "F68B:15724C:3AAF79:5A026E:67D5C347" + } + }, + "uuid": "4b26d080-5f51-45ea-90b9-dfbe0751cdb5", + "persistent": true, + "scenarioName": "scenario-1-repos-seate-for-test-pulls-9", + "requiredScenarioState": "Started", + "newScenarioState": "scenario-1-repos-seate-for-test-pulls-9-2", + "insertionIndex": 3 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/4-graphql.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/4-graphql.json new file mode 100644 index 0000000000..42db1e78ba --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/4-graphql.json @@ -0,0 +1,50 @@ +{ + "id": "89c6825e-6277-4ad0-a9f0-d0cb70e5a15b", + "name": "graphql", + "request": { + "url": "/graphql", + "method": "POST", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{\"query\":\"query GetPullRequestID { repository(name: \\\"for-test\\\", owner: \\\"seate\\\") { pullRequest(number: 9) { id } } }\"}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 200, + "body": "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDON6BPMc6Ox8DC\"}}}}", + "headers": { + "Date": "Sat, 15 Mar 2025 18:13:28 GMT", + "Content-Type": "application/json; charset=utf-8", + "X-OAuth-Scopes": "admin:repo_hook, gist, notifications, read:discussion, read:org, read:project, repo, user:email", + "X-Accepted-OAuth-Scopes": "repo", + "X-GitHub-Media-Type": "github.v4; format=json", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4981", + "X-RateLimit-Reset": "1742063501", + "X-RateLimit-Used": "19", + "X-RateLimit-Resource": "graphql", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Vary": "Accept-Encoding, Accept, X-Requested-With", + "Server": "github.com", + "X-GitHub-Request-Id": "F68C:2403B3:120286:1B2D2F:67D5C348" + } + }, + "uuid": "89c6825e-6277-4ad0-a9f0-d0cb70e5a15b", + "persistent": true, + "insertionIndex": 4 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/5-graphql.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/5-graphql.json new file mode 100644 index 0000000000..7aff47b1cf --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/5-graphql.json @@ -0,0 +1,50 @@ +{ + "id": "ff9bdb46-fb2a-44c2-a164-9a790e11c26c", + "name": "graphql", + "request": { + "url": "/graphql", + "method": "POST", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{\"query\":\"mutation EnableAutoMerge { enablePullRequestAutoMerge(input: { pullRequestId: \\\"PR_kwDON6BPMc6Ox8DC\\\" authorEmail: \\\"sa20207@naver.com\\\" clientMutationId: \\\"github-api\\\" commitBody: \\\"This is commit body.\\\" commitHeadline: \\\"This is commit title.\\\" expectedHeadOid: \\\"4888b44d7204dd05680e90159af839c8b1194b6d\\\" mergeMethod: MERGE}) { pullRequest { id } } }\"}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 200, + "body": "{\"data\":{\"enablePullRequestAutoMerge\":{\"pullRequest\":{\"id\":\"PR_kwDON6BPMc6Ox8DC\"}}}}", + "headers": { + "Date": "Sat, 15 Mar 2025 18:13:28 GMT", + "Content-Type": "application/json; charset=utf-8", + "X-OAuth-Scopes": "admin:repo_hook, gist, notifications, read:discussion, read:org, read:project, repo, user:email", + "X-Accepted-OAuth-Scopes": "repo", + "X-GitHub-Media-Type": "github.v4; format=json", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4980", + "X-RateLimit-Reset": "1742063501", + "X-RateLimit-Used": "20", + "X-RateLimit-Resource": "graphql", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Vary": "Accept-Encoding, Accept, X-Requested-With", + "Server": "github.com", + "X-GitHub-Request-Id": "F68D:4F3EE:3B15C8:5A6881:67D5C348" + } + }, + "uuid": "ff9bdb46-fb2a-44c2-a164-9a790e11c26c", + "persistent": true, + "insertionIndex": 5 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/6-r_s_f_pulls_9.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/6-r_s_f_pulls_9.json new file mode 100644 index 0000000000..f25e8a33f0 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/6-r_s_f_pulls_9.json @@ -0,0 +1,49 @@ +{ + "id": "0dd48f53-a8fb-4df8-ba9e-946146f68a33", + "name": "repos_seate_for-test_pulls_9", + "request": { + "url": "/repos/seate/for-test/pulls/9", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "6-r_s_f_pulls_9.json", + "headers": { + "Date": "Sat, 15 Mar 2025 18:13:29 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"4cb91abd4bd5effc3228763b88c5abec155f063e483efaa6ba284bb351e27687\"", + "Last-Modified": "Sat, 15 Mar 2025 16:18:20 GMT", + "X-OAuth-Scopes": "admin:repo_hook, gist, notifications, read:discussion, read:org, read:project, repo, user:email", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4982", + "X-RateLimit-Reset": "1742065904", + "X-RateLimit-Used": "18", + "X-RateLimit-Resource": "core", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-GitHub-Request-Id": "F68E:7ED0E:3A2A32:597D10:67D5C348" + } + }, + "uuid": "0dd48f53-a8fb-4df8-ba9e-946146f68a33", + "persistent": true, + "scenarioName": "scenario-1-repos-seate-for-test-pulls-9", + "requiredScenarioState": "scenario-1-repos-seate-for-test-pulls-9-2", + "insertionIndex": 6 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/7-users_seate.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/7-users_seate.json new file mode 100644 index 0000000000..51c6c8da18 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMerge/mappings/7-users_seate.json @@ -0,0 +1,47 @@ +{ + "id": "90286178-d879-4d06-ac33-48c714b16fc2", + "name": "users_seate", + "request": { + "url": "/users/seate", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "7-users_seate.json", + "headers": { + "Date": "Sat, 15 Mar 2025 18:13:29 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"0e08109bbc9b14a5d7838fffe7e57d5025e9ee8825089eb2c05f3681b890cbf4\"", + "Last-Modified": "Mon, 03 Mar 2025 13:26:53 GMT", + "X-OAuth-Scopes": "admin:repo_hook, gist, notifications, read:discussion, read:org, read:project, repo, user:email", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4981", + "X-RateLimit-Reset": "1742065904", + "X-RateLimit-Used": "19", + "X-RateLimit-Resource": "core", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-GitHub-Request-Id": "F68F:124DB4:3CBA1A:5C0D00:67D5C349" + } + }, + "uuid": "90286178-d879-4d06-ac33-48c714b16fc2", + "persistent": true, + "insertionIndex": 7 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/__files/1-user.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/__files/1-user.json new file mode 100644 index 0000000000..76578d3e42 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/__files/1-user.json @@ -0,0 +1,36 @@ +{ + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false, + "name": "KIMSIWOO", + "company": "Inha university", + "blog": "", + "location": null, + "email": "sa20207@naver.com", + "hireable": null, + "bio": null, + "twitter_username": null, + "notification_email": "sa20207@naver.com", + "public_repos": 34, + "public_gists": 0, + "followers": 2, + "following": 2, + "created_at": "2021-07-02T07:40:16Z", + "updated_at": "2025-03-03T13:26:53Z" +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/__files/2-r_s_for-test.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/__files/2-r_s_for-test.json new file mode 100644 index 0000000000..c2de0d78c0 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/__files/2-r_s_for-test.json @@ -0,0 +1,139 @@ +{ + "id": 933252913, + "node_id": "R_kgDON6BPMQ", + "name": "for-test", + "full_name": "seate/for-test", + "private": false, + "owner": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/seate/for-test", + "description": "깃허브 í…ŒėŠ¤íŠ¸ėšŠ 레íŦ", + "fork": false, + "url": "https://api.github.com/repos/seate/for-test", + "forks_url": "https://api.github.com/repos/seate/for-test/forks", + "keys_url": "https://api.github.com/repos/seate/for-test/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/seate/for-test/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/seate/for-test/teams", + "hooks_url": "https://api.github.com/repos/seate/for-test/hooks", + "issue_events_url": "https://api.github.com/repos/seate/for-test/issues/events{/number}", + "events_url": "https://api.github.com/repos/seate/for-test/events", + "assignees_url": "https://api.github.com/repos/seate/for-test/assignees{/user}", + "branches_url": "https://api.github.com/repos/seate/for-test/branches{/branch}", + "tags_url": "https://api.github.com/repos/seate/for-test/tags", + "blobs_url": "https://api.github.com/repos/seate/for-test/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/seate/for-test/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/seate/for-test/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/seate/for-test/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/seate/for-test/statuses/{sha}", + "languages_url": "https://api.github.com/repos/seate/for-test/languages", + "stargazers_url": "https://api.github.com/repos/seate/for-test/stargazers", + "contributors_url": "https://api.github.com/repos/seate/for-test/contributors", + "subscribers_url": "https://api.github.com/repos/seate/for-test/subscribers", + "subscription_url": "https://api.github.com/repos/seate/for-test/subscription", + "commits_url": "https://api.github.com/repos/seate/for-test/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/seate/for-test/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/seate/for-test/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/seate/for-test/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/seate/for-test/contents/{+path}", + "compare_url": "https://api.github.com/repos/seate/for-test/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/seate/for-test/merges", + "archive_url": "https://api.github.com/repos/seate/for-test/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/seate/for-test/downloads", + "issues_url": "https://api.github.com/repos/seate/for-test/issues{/number}", + "pulls_url": "https://api.github.com/repos/seate/for-test/pulls{/number}", + "milestones_url": "https://api.github.com/repos/seate/for-test/milestones{/number}", + "notifications_url": "https://api.github.com/repos/seate/for-test/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/seate/for-test/labels{/name}", + "releases_url": "https://api.github.com/repos/seate/for-test/releases{/id}", + "deployments_url": "https://api.github.com/repos/seate/for-test/deployments", + "created_at": "2025-02-15T14:21:31Z", + "updated_at": "2025-03-15T16:06:51Z", + "pushed_at": "2025-03-15T16:17:01Z", + "git_url": "git://github.com/seate/for-test.git", + "ssh_url": "git@github.com:seate/for-test.git", + "clone_url": "https://github.com/seate/for-test.git", + "svn_url": "https://github.com/seate/for-test", + "homepage": null, + "size": 62, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 3, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 3, + "watchers": 0, + "default_branch": "develop", + "permissions": { + "admin": true, + "maintain": true, + "push": true, + "triage": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": true, + "delete_branch_on_merge": false, + "allow_update_branch": false, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE", + "security_and_analysis": { + "secret_scanning": { + "status": "disabled" + }, + "secret_scanning_push_protection": { + "status": "disabled" + }, + "dependabot_security_updates": { + "status": "disabled" + }, + "secret_scanning_non_provider_patterns": { + "status": "disabled" + }, + "secret_scanning_validity_checks": { + "status": "disabled" + } + }, + "network_count": 0, + "subscribers_count": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/__files/3-r_s_f_pulls_9.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/__files/3-r_s_f_pulls_9.json new file mode 100644 index 0000000000..11347dec30 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/__files/3-r_s_f_pulls_9.json @@ -0,0 +1,372 @@ +{ + "url": "https://api.github.com/repos/seate/for-test/pulls/9", + "id": 2395455682, + "node_id": "PR_kwDON6BPMc6Ox8DC", + "html_url": "https://github.com/seate/for-test/pull/9", + "diff_url": "https://github.com/seate/for-test/pull/9.diff", + "patch_url": "https://github.com/seate/for-test/pull/9.patch", + "issue_url": "https://api.github.com/repos/seate/for-test/issues/9", + "number": 9, + "state": "open", + "locked": false, + "title": "github-api enable pull request auto merge test", + "user": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "body": "github-api enable pull request auto merge test", + "created_at": "2025-03-15T16:07:53Z", + "updated_at": "2025-03-15T16:18:20Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": "25a5888073ee3d2a975e012492950dddb8c346dc", + "assignee": null, + "assignees": [], + "requested_reviewers": [], + "requested_teams": [], + "labels": [], + "milestone": null, + "draft": false, + "commits_url": "https://api.github.com/repos/seate/for-test/pulls/9/commits", + "review_comments_url": "https://api.github.com/repos/seate/for-test/pulls/9/comments", + "review_comment_url": "https://api.github.com/repos/seate/for-test/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/seate/for-test/issues/9/comments", + "statuses_url": "https://api.github.com/repos/seate/for-test/statuses/4888b44d7204dd05680e90159af839c8b1194b6d", + "head": { + "label": "seate:test1", + "ref": "test1", + "sha": "4888b44d7204dd05680e90159af839c8b1194b6d", + "user": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 933252913, + "node_id": "R_kgDON6BPMQ", + "name": "for-test", + "full_name": "seate/for-test", + "private": false, + "owner": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/seate/for-test", + "description": "깃허브 í…ŒėŠ¤íŠ¸ėšŠ 레íŦ", + "fork": false, + "url": "https://api.github.com/repos/seate/for-test", + "forks_url": "https://api.github.com/repos/seate/for-test/forks", + "keys_url": "https://api.github.com/repos/seate/for-test/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/seate/for-test/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/seate/for-test/teams", + "hooks_url": "https://api.github.com/repos/seate/for-test/hooks", + "issue_events_url": "https://api.github.com/repos/seate/for-test/issues/events{/number}", + "events_url": "https://api.github.com/repos/seate/for-test/events", + "assignees_url": "https://api.github.com/repos/seate/for-test/assignees{/user}", + "branches_url": "https://api.github.com/repos/seate/for-test/branches{/branch}", + "tags_url": "https://api.github.com/repos/seate/for-test/tags", + "blobs_url": "https://api.github.com/repos/seate/for-test/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/seate/for-test/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/seate/for-test/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/seate/for-test/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/seate/for-test/statuses/{sha}", + "languages_url": "https://api.github.com/repos/seate/for-test/languages", + "stargazers_url": "https://api.github.com/repos/seate/for-test/stargazers", + "contributors_url": "https://api.github.com/repos/seate/for-test/contributors", + "subscribers_url": "https://api.github.com/repos/seate/for-test/subscribers", + "subscription_url": "https://api.github.com/repos/seate/for-test/subscription", + "commits_url": "https://api.github.com/repos/seate/for-test/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/seate/for-test/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/seate/for-test/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/seate/for-test/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/seate/for-test/contents/{+path}", + "compare_url": "https://api.github.com/repos/seate/for-test/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/seate/for-test/merges", + "archive_url": "https://api.github.com/repos/seate/for-test/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/seate/for-test/downloads", + "issues_url": "https://api.github.com/repos/seate/for-test/issues{/number}", + "pulls_url": "https://api.github.com/repos/seate/for-test/pulls{/number}", + "milestones_url": "https://api.github.com/repos/seate/for-test/milestones{/number}", + "notifications_url": "https://api.github.com/repos/seate/for-test/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/seate/for-test/labels{/name}", + "releases_url": "https://api.github.com/repos/seate/for-test/releases{/id}", + "deployments_url": "https://api.github.com/repos/seate/for-test/deployments", + "created_at": "2025-02-15T14:21:31Z", + "updated_at": "2025-03-15T16:06:51Z", + "pushed_at": "2025-03-15T16:17:01Z", + "git_url": "git://github.com/seate/for-test.git", + "ssh_url": "git@github.com:seate/for-test.git", + "clone_url": "https://github.com/seate/for-test.git", + "svn_url": "https://github.com/seate/for-test", + "homepage": null, + "size": 62, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 3, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 3, + "watchers": 0, + "default_branch": "develop" + } + }, + "base": { + "label": "seate:develop", + "ref": "develop", + "sha": "2bc9cde73b377e4d0ebda0d19f636644808388f5", + "user": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 933252913, + "node_id": "R_kgDON6BPMQ", + "name": "for-test", + "full_name": "seate/for-test", + "private": false, + "owner": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/seate/for-test", + "description": "깃허브 í…ŒėŠ¤íŠ¸ėšŠ 레íŦ", + "fork": false, + "url": "https://api.github.com/repos/seate/for-test", + "forks_url": "https://api.github.com/repos/seate/for-test/forks", + "keys_url": "https://api.github.com/repos/seate/for-test/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/seate/for-test/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/seate/for-test/teams", + "hooks_url": "https://api.github.com/repos/seate/for-test/hooks", + "issue_events_url": "https://api.github.com/repos/seate/for-test/issues/events{/number}", + "events_url": "https://api.github.com/repos/seate/for-test/events", + "assignees_url": "https://api.github.com/repos/seate/for-test/assignees{/user}", + "branches_url": "https://api.github.com/repos/seate/for-test/branches{/branch}", + "tags_url": "https://api.github.com/repos/seate/for-test/tags", + "blobs_url": "https://api.github.com/repos/seate/for-test/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/seate/for-test/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/seate/for-test/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/seate/for-test/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/seate/for-test/statuses/{sha}", + "languages_url": "https://api.github.com/repos/seate/for-test/languages", + "stargazers_url": "https://api.github.com/repos/seate/for-test/stargazers", + "contributors_url": "https://api.github.com/repos/seate/for-test/contributors", + "subscribers_url": "https://api.github.com/repos/seate/for-test/subscribers", + "subscription_url": "https://api.github.com/repos/seate/for-test/subscription", + "commits_url": "https://api.github.com/repos/seate/for-test/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/seate/for-test/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/seate/for-test/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/seate/for-test/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/seate/for-test/contents/{+path}", + "compare_url": "https://api.github.com/repos/seate/for-test/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/seate/for-test/merges", + "archive_url": "https://api.github.com/repos/seate/for-test/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/seate/for-test/downloads", + "issues_url": "https://api.github.com/repos/seate/for-test/issues{/number}", + "pulls_url": "https://api.github.com/repos/seate/for-test/pulls{/number}", + "milestones_url": "https://api.github.com/repos/seate/for-test/milestones{/number}", + "notifications_url": "https://api.github.com/repos/seate/for-test/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/seate/for-test/labels{/name}", + "releases_url": "https://api.github.com/repos/seate/for-test/releases{/id}", + "deployments_url": "https://api.github.com/repos/seate/for-test/deployments", + "created_at": "2025-02-15T14:21:31Z", + "updated_at": "2025-03-15T16:06:51Z", + "pushed_at": "2025-03-15T16:17:01Z", + "git_url": "git://github.com/seate/for-test.git", + "ssh_url": "git@github.com:seate/for-test.git", + "clone_url": "https://github.com/seate/for-test.git", + "svn_url": "https://github.com/seate/for-test", + "homepage": null, + "size": 62, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 3, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 3, + "watchers": 0, + "default_branch": "develop" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/seate/for-test/pulls/9" + }, + "html": { + "href": "https://github.com/seate/for-test/pull/9" + }, + "issue": { + "href": "https://api.github.com/repos/seate/for-test/issues/9" + }, + "comments": { + "href": "https://api.github.com/repos/seate/for-test/issues/9/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/seate/for-test/pulls/9/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/seate/for-test/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/seate/for-test/pulls/9/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/seate/for-test/statuses/4888b44d7204dd05680e90159af839c8b1194b6d" + } + }, + "author_association": "OWNER", + "auto_merge": { + "enabled_by": { + "login": "seate", + "id": 86824703, + "node_id": "MDQ6VXNlcjg2ODI0NzAz", + "avatar_url": "https://avatars.githubusercontent.com/u/86824703?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/seate", + "html_url": "https://github.com/seate", + "followers_url": "https://api.github.com/users/seate/followers", + "following_url": "https://api.github.com/users/seate/following{/other_user}", + "gists_url": "https://api.github.com/users/seate/gists{/gist_id}", + "starred_url": "https://api.github.com/users/seate/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/seate/subscriptions", + "organizations_url": "https://api.github.com/users/seate/orgs", + "repos_url": "https://api.github.com/users/seate/repos", + "events_url": "https://api.github.com/users/seate/events{/privacy}", + "received_events_url": "https://api.github.com/users/seate/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "merge_method": "merge", + "commit_title": "This is commit title.", + "commit_message": "This is commit body." + }, + "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": 16, + "additions": 642, + "deletions": 0, + "changed_files": 19 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/mappings/1-user.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/mappings/1-user.json new file mode 100644 index 0000000000..a6b92d442d --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/mappings/1-user.json @@ -0,0 +1,47 @@ +{ + "id": "931de630-5c54-4bb3-877f-16430f46887f", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "1-user.json", + "headers": { + "Date": "Mon, 17 Mar 2025 07:04:43 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"91439c9cd22b1066c90ef899df4f995dcda9ed34b86d5e107b7c311aaaff2136\"", + "Last-Modified": "Mon, 03 Mar 2025 13:26:53 GMT", + "X-OAuth-Scopes": "admin:repo_hook, gist, notifications, read:discussion, read:org, read:project, repo, user:email", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4990", + "X-RateLimit-Reset": "1742197445", + "X-RateLimit-Used": "10", + "X-RateLimit-Resource": "core", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-GitHub-Request-Id": "E025:4F3EE:A59617:F8E5A6:67D7C98B" + } + }, + "uuid": "931de630-5c54-4bb3-877f-16430f46887f", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/mappings/2-r_s_for-test.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/mappings/2-r_s_for-test.json new file mode 100644 index 0000000000..268fdf44ea --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/mappings/2-r_s_for-test.json @@ -0,0 +1,47 @@ +{ + "id": "cea6580e-f17f-43e1-b5c9-e27077b6ff17", + "name": "repos_seate_for-test", + "request": { + "url": "/repos/seate/for-test", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "2-r_s_for-test.json", + "headers": { + "Date": "Mon, 17 Mar 2025 07:04:45 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"9181d2d37a58759c6739fb93cdf26cbc7b9cc04f34e87456932f65921cb5473d\"", + "Last-Modified": "Sat, 15 Mar 2025 16:06:51 GMT", + "X-OAuth-Scopes": "admin:repo_hook, gist, notifications, read:discussion, read:org, read:project, repo, user:email", + "X-Accepted-OAuth-Scopes": "repo", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4985", + "X-RateLimit-Reset": "1742197445", + "X-RateLimit-Used": "15", + "X-RateLimit-Resource": "core", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-GitHub-Request-Id": "E027:1EF85A:16C064:1D9E9C:67D7C98D" + } + }, + "uuid": "cea6580e-f17f-43e1-b5c9-e27077b6ff17", + "persistent": true, + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/mappings/3-r_s_f_pulls_9.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/mappings/3-r_s_f_pulls_9.json new file mode 100644 index 0000000000..06d04c10ad --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/mappings/3-r_s_f_pulls_9.json @@ -0,0 +1,47 @@ +{ + "id": "88725e34-4c36-4681-bc6a-f82ff05b80ef", + "name": "repos_seate_for-test_pulls_9", + "request": { + "url": "/repos/seate/for-test/pulls/9", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "3-r_s_f_pulls_9.json", + "headers": { + "Date": "Mon, 17 Mar 2025 07:04:46 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"4cb91abd4bd5effc3228763b88c5abec155f063e483efaa6ba284bb351e27687\"", + "Last-Modified": "Sat, 15 Mar 2025 16:18:20 GMT", + "X-OAuth-Scopes": "admin:repo_hook, gist, notifications, read:discussion, read:org, read:project, repo, user:email", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4984", + "X-RateLimit-Reset": "1742197445", + "X-RateLimit-Used": "16", + "X-RateLimit-Resource": "core", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-GitHub-Request-Id": "E028:1F17C6:160FCD:1CEEA4:67D7C98D" + } + }, + "uuid": "88725e34-4c36-4681-bc6a-f82ff05b80ef", + "persistent": true, + "insertionIndex": 3 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/mappings/4-graphql.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/mappings/4-graphql.json new file mode 100644 index 0000000000..f1e592ffb3 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/mappings/4-graphql.json @@ -0,0 +1,50 @@ +{ + "id": "ab9b1fcc-2e83-46f8-82a7-a5a6b19b9958", + "name": "graphql", + "request": { + "url": "/graphql", + "method": "POST", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{\"query\":\"query GetPullRequestID { repository(name: \\\"for-test\\\", owner: \\\"seate\\\") { pullRequest(number: 9) { id } } }\"}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 200, + "body": "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDON6BPMc6Ox8DC\"}}}}", + "headers": { + "Date": "Mon, 17 Mar 2025 07:04:46 GMT", + "Content-Type": "application/json; charset=utf-8", + "X-OAuth-Scopes": "admin:repo_hook, gist, notifications, read:discussion, read:org, read:project, repo, user:email", + "X-Accepted-OAuth-Scopes": "repo", + "X-GitHub-Media-Type": "github.v4; format=json", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4923", + "X-RateLimit-Reset": "1742196376", + "X-RateLimit-Used": "77", + "X-RateLimit-Resource": "graphql", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Vary": "Accept-Encoding, Accept, X-Requested-With", + "Server": "github.com", + "X-GitHub-Request-Id": "E029:3882B9:169857:1D842A:67D7C98E" + } + }, + "uuid": "ab9b1fcc-2e83-46f8-82a7-a5a6b19b9958", + "persistent": true, + "insertionIndex": 4 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/mappings/5-graphql.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/mappings/5-graphql.json new file mode 100644 index 0000000000..1f2dc6f418 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/enablePullRequestAutoMergeFailure/mappings/5-graphql.json @@ -0,0 +1,50 @@ +{ + "id": "d219868c-dc53-4642-863d-64a268d3c115", + "name": "graphql", + "request": { + "url": "/graphql", + "method": "POST", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{\"query\":\"mutation EnableAutoMerge { enablePullRequestAutoMerge(input: { pullRequestId: \\\"PR_kwDON6BPMc6Ox8DC\\\" authorEmail: \\\"failureEmail@gmail.com\\\" clientMutationId: \\\"github-api\\\" commitBody: \\\"This is commit body.\\\" commitHeadline: \\\"This is commit title.\\\" expectedHeadOid: \\\"4888b44d7204dd05680e90159af839c8b1194b6d\\\"}) { pullRequest { id } } }\"}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 200, + "body": "{\"data\":{\"enablePullRequestAutoMerge\":null},\"errors\":[{\"type\":\"UNPROCESSABLE\",\"path\":[\"enablePullRequestAutoMerge\"],\"locations\":[{\"line\":1,\"column\":28}],\"message\":\"seate does not have a verified email, which is required to enable auto-merging.\"}]}", + "headers": { + "Date": "Mon, 17 Mar 2025 07:04:47 GMT", + "Content-Type": "application/json; charset=utf-8", + "X-OAuth-Scopes": "admin:repo_hook, gist, notifications, read:discussion, read:org, read:project, repo, user:email", + "X-Accepted-OAuth-Scopes": "repo", + "X-GitHub-Media-Type": "github.v4; format=json", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4922", + "X-RateLimit-Reset": "1742196376", + "X-RateLimit-Used": "78", + "X-RateLimit-Resource": "graphql", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Vary": "Accept-Encoding, Accept, X-Requested-With", + "Server": "github.com", + "X-GitHub-Request-Id": "E02A:FA314:AB056F:FE5536:67D7C98E" + } + }, + "uuid": "d219868c-dc53-4642-863d-64a268d3c115", + "persistent": true, + "insertionIndex": 5 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/__files/1-user.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/__files/1-user.json new file mode 100644 index 0000000000..fbc5eae788 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/__files/1-user.json @@ -0,0 +1,36 @@ +{ + "login": "Anonycoders", + "id": 40047636, + "node_id": "MDQ6VXNlcjQwMDQ3NjM2", + "avatar_url": "https://avatars.githubusercontent.com/u/40047636?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Anonycoders", + "html_url": "https://github.com/Anonycoders", + "followers_url": "https://api.github.com/users/Anonycoders/followers", + "following_url": "https://api.github.com/users/Anonycoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Anonycoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Anonycoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Anonycoders/subscriptions", + "organizations_url": "https://api.github.com/users/Anonycoders/orgs", + "repos_url": "https://api.github.com/users/Anonycoders/repos", + "events_url": "https://api.github.com/users/Anonycoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Anonycoders/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false, + "name": "Sorena Sarabadani", + "company": "@Adevinta", + "blog": "", + "location": "Berlin, Germany", + "email": "sorena.sarabadani@gmail.com", + "hireable": null, + "bio": "Ex-Shopifyer - Adevinta/Kleinanzeigen", + "twitter_username": "sorena_s", + "notification_email": "sorena.sarabadani@gmail.com", + "public_repos": 12, + "public_gists": 0, + "followers": 38, + "following": 4, + "created_at": "2018-06-08T02:07:15Z", + "updated_at": "2026-01-24T22:07:12Z" +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/__files/2-orgs_hub4j-test-org.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/__files/2-orgs_hub4j-test-org.json new file mode 100644 index 0000000000..8b36ed4dd1 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/__files/2-orgs_hub4j-test-org.json @@ -0,0 +1,76 @@ +{ + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "url": "https://api.github.com/orgs/hub4j-test-org", + "repos_url": "https://api.github.com/orgs/hub4j-test-org/repos", + "events_url": "https://api.github.com/orgs/hub4j-test-org/events", + "hooks_url": "https://api.github.com/orgs/hub4j-test-org/hooks", + "issues_url": "https://api.github.com/orgs/hub4j-test-org/issues", + "members_url": "https://api.github.com/orgs/hub4j-test-org/members{/member}", + "public_members_url": "https://api.github.com/orgs/hub4j-test-org/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "description": "Hub4j Test Org Description (this could be null or blank too)", + "name": "Hub4j Test Org Name (this could be null or blank too)", + "company": null, + "blog": "https://hub4j.url.io/could/be/null", + "location": "Hub4j Test Org Location (this could be null or blank too)", + "email": "hub4jtestorgemail@could.be.null.com", + "twitter_username": null, + "is_verified": false, + "has_organization_projects": true, + "has_repository_projects": true, + "public_repos": 27, + "public_gists": 0, + "followers": 2, + "following": 0, + "html_url": "https://github.com/hub4j-test-org", + "created_at": "2014-05-10T19:39:11Z", + "updated_at": "2025-08-05T00:53:03Z", + "archived_at": null, + "type": "Organization", + "total_private_repos": 8, + "owned_private_repos": 8, + "private_gists": 0, + "disk_usage": 12076, + "collaborators": 1, + "billing_email": "kk@kohsuke.org", + "default_repository_permission": "none", + "members_can_create_repositories": false, + "two_factor_requirement_enabled": false, + "members_allowed_repository_creation_type": "none", + "members_can_create_public_repositories": false, + "members_can_create_private_repositories": false, + "members_can_create_internal_repositories": false, + "members_can_create_pages": true, + "members_can_fork_private_repositories": false, + "web_commit_signoff_required": false, + "deploy_keys_enabled_for_repositories": false, + "members_can_delete_repositories": true, + "members_can_change_repo_visibility": true, + "members_can_invite_outside_collaborators": true, + "members_can_delete_issues": false, + "display_commenter_full_name_setting_enabled": false, + "readers_can_create_discussions": true, + "members_can_create_teams": true, + "members_can_view_dependency_insights": true, + "default_repository_branch": "main", + "members_can_create_public_pages": true, + "members_can_create_private_pages": true, + "plan": { + "name": "free", + "space": 976562499, + "private_repos": 10000, + "filled_seats": 53, + "seats": 3 + }, + "advanced_security_enabled_for_new_repositories": false, + "dependabot_alerts_enabled_for_new_repositories": false, + "dependabot_security_updates_enabled_for_new_repositories": false, + "dependency_graph_enabled_for_new_repositories": false, + "secret_scanning_enabled_for_new_repositories": false, + "secret_scanning_push_protection_enabled_for_new_repositories": false, + "secret_scanning_push_protection_custom_link_enabled": false, + "secret_scanning_push_protection_custom_link": null, + "secret_scanning_validity_checks_enabled": false +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/__files/3-r_h_github-api.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/__files/3-r_h_github-api.json new file mode 100644 index 0000000000..313ddcfbb5 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/__files/3-r_h_github-api.json @@ -0,0 +1,397 @@ +{ + "id": 206888201, + "node_id": "MDEwOlJlcG9zaXRvcnkyMDY4ODgyMDE=", + "name": "github-api", + "full_name": "hub4j-test-org/github-api", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/github-api", + "description": "Tricky", + "fork": true, + "url": "https://api.github.com/repos/hub4j-test-org/github-api", + "forks_url": "https://api.github.com/repos/hub4j-test-org/github-api/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/github-api/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/github-api/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/github-api/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/github-api/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/github-api/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/github-api/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/github-api/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/github-api/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/github-api/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/github-api/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/github-api/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/github-api/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/github-api/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/github-api/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/github-api/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/github-api/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/github-api/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/github-api/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/github-api/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/github-api/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/github-api/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/github-api/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/github-api/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/github-api/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/github-api/deployments", + "created_at": "2019-09-06T23:26:04Z", + "updated_at": "2024-03-22T23:30:32Z", + "pushed_at": "2024-06-16T10:20:03Z", + "git_url": "git://github.com/hub4j-test-org/github-api.git", + "ssh_url": "git@github.com:hub4j-test-org/github-api.git", + "clone_url": "https://github.com/hub4j-test-org/github-api.git", + "svn_url": "https://github.com/hub4j-test-org/github-api", + "homepage": "http://github-api.kohsuke.org/", + "size": 18977, + "stargazers_count": 1, + "watchers_count": 1, + "language": "Java", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 7, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 7, + "watchers": 1, + "default_branch": "main", + "permissions": { + "admin": true, + "maintain": true, + "push": true, + "triage": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": false, + "delete_branch_on_merge": false, + "allow_update_branch": false, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE", + "custom_properties": {}, + "organization": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "parent": { + "id": 617210, + "node_id": "MDEwOlJlcG9zaXRvcnk2MTcyMTA=", + "name": "github-api", + "full_name": "hub4j/github-api", + "private": false, + "owner": { + "login": "hub4j", + "id": 54909825, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU0OTA5ODI1", + "avatar_url": "https://avatars.githubusercontent.com/u/54909825?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j", + "html_url": "https://github.com/hub4j", + "followers_url": "https://api.github.com/users/hub4j/followers", + "following_url": "https://api.github.com/users/hub4j/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j/orgs", + "repos_url": "https://api.github.com/users/hub4j/repos", + "events_url": "https://api.github.com/users/hub4j/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/hub4j/github-api", + "description": "Java API for GitHub", + "fork": false, + "url": "https://api.github.com/repos/hub4j/github-api", + "forks_url": "https://api.github.com/repos/hub4j/github-api/forks", + "keys_url": "https://api.github.com/repos/hub4j/github-api/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j/github-api/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j/github-api/teams", + "hooks_url": "https://api.github.com/repos/hub4j/github-api/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j/github-api/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j/github-api/events", + "assignees_url": "https://api.github.com/repos/hub4j/github-api/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j/github-api/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j/github-api/tags", + "blobs_url": "https://api.github.com/repos/hub4j/github-api/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j/github-api/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j/github-api/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j/github-api/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j/github-api/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j/github-api/languages", + "stargazers_url": "https://api.github.com/repos/hub4j/github-api/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j/github-api/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j/github-api/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j/github-api/subscription", + "commits_url": "https://api.github.com/repos/hub4j/github-api/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j/github-api/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j/github-api/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j/github-api/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j/github-api/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j/github-api/merges", + "archive_url": "https://api.github.com/repos/hub4j/github-api/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j/github-api/downloads", + "issues_url": "https://api.github.com/repos/hub4j/github-api/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j/github-api/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j/github-api/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j/github-api/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j/github-api/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j/github-api/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j/github-api/deployments", + "created_at": "2010-04-19T04:13:03Z", + "updated_at": "2026-01-24T22:05:11Z", + "pushed_at": "2026-01-24T22:05:06Z", + "git_url": "git://github.com/hub4j/github-api.git", + "ssh_url": "git@github.com:hub4j/github-api.git", + "clone_url": "https://github.com/hub4j/github-api.git", + "svn_url": "https://github.com/hub4j/github-api", + "homepage": "https://hub4j.github.io/github-api/", + "size": 66454, + "stargazers_count": 1230, + "watchers_count": 1230, + "language": "Java", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "has_discussions": true, + "forks_count": 769, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 178, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "api", + "client-library", + "github", + "github-api", + "github-api-v3", + "java", + "java-api" + ], + "visibility": "public", + "forks": 769, + "open_issues": 178, + "watchers": 1230, + "default_branch": "main" + }, + "source": { + "id": 617210, + "node_id": "MDEwOlJlcG9zaXRvcnk2MTcyMTA=", + "name": "github-api", + "full_name": "hub4j/github-api", + "private": false, + "owner": { + "login": "hub4j", + "id": 54909825, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU0OTA5ODI1", + "avatar_url": "https://avatars.githubusercontent.com/u/54909825?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j", + "html_url": "https://github.com/hub4j", + "followers_url": "https://api.github.com/users/hub4j/followers", + "following_url": "https://api.github.com/users/hub4j/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j/orgs", + "repos_url": "https://api.github.com/users/hub4j/repos", + "events_url": "https://api.github.com/users/hub4j/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/hub4j/github-api", + "description": "Java API for GitHub", + "fork": false, + "url": "https://api.github.com/repos/hub4j/github-api", + "forks_url": "https://api.github.com/repos/hub4j/github-api/forks", + "keys_url": "https://api.github.com/repos/hub4j/github-api/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j/github-api/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j/github-api/teams", + "hooks_url": "https://api.github.com/repos/hub4j/github-api/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j/github-api/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j/github-api/events", + "assignees_url": "https://api.github.com/repos/hub4j/github-api/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j/github-api/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j/github-api/tags", + "blobs_url": "https://api.github.com/repos/hub4j/github-api/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j/github-api/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j/github-api/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j/github-api/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j/github-api/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j/github-api/languages", + "stargazers_url": "https://api.github.com/repos/hub4j/github-api/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j/github-api/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j/github-api/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j/github-api/subscription", + "commits_url": "https://api.github.com/repos/hub4j/github-api/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j/github-api/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j/github-api/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j/github-api/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j/github-api/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j/github-api/merges", + "archive_url": "https://api.github.com/repos/hub4j/github-api/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j/github-api/downloads", + "issues_url": "https://api.github.com/repos/hub4j/github-api/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j/github-api/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j/github-api/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j/github-api/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j/github-api/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j/github-api/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j/github-api/deployments", + "created_at": "2010-04-19T04:13:03Z", + "updated_at": "2026-01-24T22:05:11Z", + "pushed_at": "2026-01-24T22:05:06Z", + "git_url": "git://github.com/hub4j/github-api.git", + "ssh_url": "git@github.com:hub4j/github-api.git", + "clone_url": "https://github.com/hub4j/github-api.git", + "svn_url": "https://github.com/hub4j/github-api", + "homepage": "https://hub4j.github.io/github-api/", + "size": 66454, + "stargazers_count": 1230, + "watchers_count": 1230, + "language": "Java", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "has_discussions": true, + "forks_count": 769, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 178, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "api", + "client-library", + "github", + "github-api", + "github-api-v3", + "java", + "java-api" + ], + "visibility": "public", + "forks": 769, + "open_issues": 178, + "watchers": 1230, + "default_branch": "main" + }, + "security_and_analysis": { + "secret_scanning": { + "status": "disabled" + }, + "secret_scanning_push_protection": { + "status": "disabled" + }, + "dependabot_security_updates": { + "status": "disabled" + }, + "secret_scanning_non_provider_patterns": { + "status": "disabled" + }, + "secret_scanning_validity_checks": { + "status": "disabled" + } + }, + "network_count": 769, + "subscribers_count": 0 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/__files/4-r_h_g_pulls.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/__files/4-r_h_g_pulls.json new file mode 100644 index 0000000000..1068844514 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/__files/4-r_h_g_pulls.json @@ -0,0 +1,359 @@ +{ + "url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/486", + "id": 3207704845, + "node_id": "PR_kwDODFTdCc6_MbEN", + "html_url": "https://github.com/hub4j-test-org/github-api/pull/486", + "diff_url": "https://github.com/hub4j-test-org/github-api/pull/486.diff", + "patch_url": "https://github.com/hub4j-test-org/github-api/pull/486.patch", + "issue_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/486", + "number": 486, + "state": "open", + "locked": false, + "title": "markReadyForReview", + "user": { + "login": "Anonycoders", + "id": 40047636, + "node_id": "MDQ6VXNlcjQwMDQ3NjM2", + "avatar_url": "https://avatars.githubusercontent.com/u/40047636?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Anonycoders", + "html_url": "https://github.com/Anonycoders", + "followers_url": "https://api.github.com/users/Anonycoders/followers", + "following_url": "https://api.github.com/users/Anonycoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Anonycoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Anonycoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Anonycoders/subscriptions", + "organizations_url": "https://api.github.com/users/Anonycoders/orgs", + "repos_url": "https://api.github.com/users/Anonycoders/repos", + "events_url": "https://api.github.com/users/Anonycoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Anonycoders/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "body": "## test", + "created_at": "2026-01-25T03:18:07Z", + "updated_at": "2026-01-25T03:18:07Z", + "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/hub4j-test-org/github-api/pulls/486/commits", + "review_comments_url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/486/comments", + "review_comment_url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/486/comments", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/github-api/statuses/07374fe73aff1c2024a8d4114b32406c7a8e89b7", + "head": { + "label": "hub4j-test-org:test/stable", + "ref": "test/stable", + "sha": "07374fe73aff1c2024a8d4114b32406c7a8e89b7", + "user": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 206888201, + "node_id": "MDEwOlJlcG9zaXRvcnkyMDY4ODgyMDE=", + "name": "github-api", + "full_name": "hub4j-test-org/github-api", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/github-api", + "description": "Tricky", + "fork": true, + "url": "https://api.github.com/repos/hub4j-test-org/github-api", + "forks_url": "https://api.github.com/repos/hub4j-test-org/github-api/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/github-api/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/github-api/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/github-api/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/github-api/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/github-api/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/github-api/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/github-api/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/github-api/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/github-api/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/github-api/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/github-api/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/github-api/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/github-api/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/github-api/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/github-api/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/github-api/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/github-api/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/github-api/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/github-api/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/github-api/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/github-api/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/github-api/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/github-api/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/github-api/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/github-api/deployments", + "created_at": "2019-09-06T23:26:04Z", + "updated_at": "2024-03-22T23:30:32Z", + "pushed_at": "2024-06-16T10:20:03Z", + "git_url": "git://github.com/hub4j-test-org/github-api.git", + "ssh_url": "git@github.com:hub4j-test-org/github-api.git", + "clone_url": "https://github.com/hub4j-test-org/github-api.git", + "svn_url": "https://github.com/hub4j-test-org/github-api", + "homepage": "http://github-api.kohsuke.org/", + "size": 18977, + "stargazers_count": 1, + "watchers_count": 1, + "language": "Java", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 8, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 8, + "watchers": 1, + "default_branch": "main" + } + }, + "base": { + "label": "hub4j-test-org:main", + "ref": "main", + "sha": "c4b41922197a1d595bff30e89bb8540013ee4fd3", + "user": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 206888201, + "node_id": "MDEwOlJlcG9zaXRvcnkyMDY4ODgyMDE=", + "name": "github-api", + "full_name": "hub4j-test-org/github-api", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/github-api", + "description": "Tricky", + "fork": true, + "url": "https://api.github.com/repos/hub4j-test-org/github-api", + "forks_url": "https://api.github.com/repos/hub4j-test-org/github-api/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/github-api/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/github-api/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/github-api/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/github-api/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/github-api/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/github-api/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/github-api/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/github-api/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/github-api/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/github-api/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/github-api/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/github-api/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/github-api/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/github-api/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/github-api/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/github-api/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/github-api/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/github-api/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/github-api/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/github-api/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/github-api/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/github-api/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/github-api/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/github-api/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/github-api/deployments", + "created_at": "2019-09-06T23:26:04Z", + "updated_at": "2024-03-22T23:30:32Z", + "pushed_at": "2024-06-16T10:20:03Z", + "git_url": "git://github.com/hub4j-test-org/github-api.git", + "ssh_url": "git@github.com:hub4j-test-org/github-api.git", + "clone_url": "https://github.com/hub4j-test-org/github-api.git", + "svn_url": "https://github.com/hub4j-test-org/github-api", + "homepage": "http://github-api.kohsuke.org/", + "size": 18977, + "stargazers_count": 1, + "watchers_count": 1, + "language": "Java", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 8, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 8, + "watchers": 1, + "default_branch": "main" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/486" + }, + "html": { + "href": "https://github.com/hub4j-test-org/github-api/pull/486" + }, + "issue": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/issues/486" + }, + "comments": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/issues/486/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/486/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/486/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/statuses/07374fe73aff1c2024a8d4114b32406c7a8e89b7" + } + }, + "author_association": "MEMBER", + "auto_merge": null, + "active_lock_reason": null, + "merged": false, + "mergeable": null, + "rebaseable": null, + "mergeable_state": "unknown", + "merged_by": null, + "comments": 0, + "review_comments": 0, + "maintainer_can_modify": false, + "commits": 3, + "additions": 3, + "deletions": 2, + "changed_files": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/__files/6-r_h_g_pulls_486.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/__files/6-r_h_g_pulls_486.json new file mode 100644 index 0000000000..43c0ba2ba0 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/__files/6-r_h_g_pulls_486.json @@ -0,0 +1,359 @@ +{ + "url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/486", + "id": 3207704845, + "node_id": "PR_kwDODFTdCc6_MbEN", + "html_url": "https://github.com/hub4j-test-org/github-api/pull/486", + "diff_url": "https://github.com/hub4j-test-org/github-api/pull/486.diff", + "patch_url": "https://github.com/hub4j-test-org/github-api/pull/486.patch", + "issue_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/486", + "number": 486, + "state": "open", + "locked": false, + "title": "markReadyForReview", + "user": { + "login": "Anonycoders", + "id": 40047636, + "node_id": "MDQ6VXNlcjQwMDQ3NjM2", + "avatar_url": "https://avatars.githubusercontent.com/u/40047636?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Anonycoders", + "html_url": "https://github.com/Anonycoders", + "followers_url": "https://api.github.com/users/Anonycoders/followers", + "following_url": "https://api.github.com/users/Anonycoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Anonycoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Anonycoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Anonycoders/subscriptions", + "organizations_url": "https://api.github.com/users/Anonycoders/orgs", + "repos_url": "https://api.github.com/users/Anonycoders/repos", + "events_url": "https://api.github.com/users/Anonycoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Anonycoders/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "body": "## test", + "created_at": "2026-01-25T03:18:07Z", + "updated_at": "2026-01-25T03:18:08Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": "32c1c29c6a9d8dc4e43c1993a2424dafd98334d3", + "assignee": null, + "assignees": [], + "requested_reviewers": [], + "requested_teams": [], + "labels": [], + "milestone": null, + "draft": false, + "commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/486/commits", + "review_comments_url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/486/comments", + "review_comment_url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/486/comments", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/github-api/statuses/07374fe73aff1c2024a8d4114b32406c7a8e89b7", + "head": { + "label": "hub4j-test-org:test/stable", + "ref": "test/stable", + "sha": "07374fe73aff1c2024a8d4114b32406c7a8e89b7", + "user": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 206888201, + "node_id": "MDEwOlJlcG9zaXRvcnkyMDY4ODgyMDE=", + "name": "github-api", + "full_name": "hub4j-test-org/github-api", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/github-api", + "description": "Tricky", + "fork": true, + "url": "https://api.github.com/repos/hub4j-test-org/github-api", + "forks_url": "https://api.github.com/repos/hub4j-test-org/github-api/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/github-api/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/github-api/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/github-api/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/github-api/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/github-api/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/github-api/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/github-api/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/github-api/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/github-api/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/github-api/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/github-api/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/github-api/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/github-api/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/github-api/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/github-api/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/github-api/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/github-api/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/github-api/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/github-api/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/github-api/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/github-api/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/github-api/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/github-api/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/github-api/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/github-api/deployments", + "created_at": "2019-09-06T23:26:04Z", + "updated_at": "2024-03-22T23:30:32Z", + "pushed_at": "2024-06-16T10:20:03Z", + "git_url": "git://github.com/hub4j-test-org/github-api.git", + "ssh_url": "git@github.com:hub4j-test-org/github-api.git", + "clone_url": "https://github.com/hub4j-test-org/github-api.git", + "svn_url": "https://github.com/hub4j-test-org/github-api", + "homepage": "http://github-api.kohsuke.org/", + "size": 18977, + "stargazers_count": 1, + "watchers_count": 1, + "language": "Java", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 8, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 8, + "watchers": 1, + "default_branch": "main" + } + }, + "base": { + "label": "hub4j-test-org:main", + "ref": "main", + "sha": "c4b41922197a1d595bff30e89bb8540013ee4fd3", + "user": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 206888201, + "node_id": "MDEwOlJlcG9zaXRvcnkyMDY4ODgyMDE=", + "name": "github-api", + "full_name": "hub4j-test-org/github-api", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/github-api", + "description": "Tricky", + "fork": true, + "url": "https://api.github.com/repos/hub4j-test-org/github-api", + "forks_url": "https://api.github.com/repos/hub4j-test-org/github-api/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/github-api/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/github-api/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/github-api/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/github-api/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/github-api/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/github-api/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/github-api/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/github-api/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/github-api/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/github-api/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/github-api/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/github-api/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/github-api/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/github-api/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/github-api/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/github-api/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/github-api/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/github-api/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/github-api/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/github-api/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/github-api/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/github-api/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/github-api/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/github-api/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/github-api/deployments", + "created_at": "2019-09-06T23:26:04Z", + "updated_at": "2024-03-22T23:30:32Z", + "pushed_at": "2024-06-16T10:20:03Z", + "git_url": "git://github.com/hub4j-test-org/github-api.git", + "ssh_url": "git@github.com:hub4j-test-org/github-api.git", + "clone_url": "https://github.com/hub4j-test-org/github-api.git", + "svn_url": "https://github.com/hub4j-test-org/github-api", + "homepage": "http://github-api.kohsuke.org/", + "size": 18977, + "stargazers_count": 1, + "watchers_count": 1, + "language": "Java", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 8, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 8, + "watchers": 1, + "default_branch": "main" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/486" + }, + "html": { + "href": "https://github.com/hub4j-test-org/github-api/pull/486" + }, + "issue": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/issues/486" + }, + "comments": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/issues/486/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/486/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/486/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/statuses/07374fe73aff1c2024a8d4114b32406c7a8e89b7" + } + }, + "author_association": "MEMBER", + "auto_merge": null, + "active_lock_reason": null, + "merged": false, + "mergeable": true, + "rebaseable": true, + "mergeable_state": "unstable", + "merged_by": null, + "comments": 0, + "review_comments": 0, + "maintainer_can_modify": false, + "commits": 3, + "additions": 3, + "deletions": 2, + "changed_files": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/mappings/1-user.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/mappings/1-user.json new file mode 100644 index 0000000000..94af5dd933 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/mappings/1-user.json @@ -0,0 +1,48 @@ +{ + "id": "9862ae99-df05-4a92-94c7-c7db54d0ab24", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "1-user.json", + "headers": { + "Date": "Sun, 25 Jan 2026 03:18:04 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"15d7e1ad92a3639b979fc55254902e63ee0bfa5c8f6766990bf989044d491ce1\"", + "Last-Modified": "Sat, 24 Jan 2026 22:07:12 GMT", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4966", + "X-RateLimit-Reset": "1769314022", + "X-RateLimit-Used": "34", + "X-RateLimit-Resource": "core", + "X-GitHub-Request-Id": "D2A7:EDA44:264DBE3:21565BC:69758B6C" + } + }, + "uuid": "9862ae99-df05-4a92-94c7-c7db54d0ab24", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/mappings/2-orgs_hub4j-test-org.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/mappings/2-orgs_hub4j-test-org.json new file mode 100644 index 0000000000..90335c900c --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/mappings/2-orgs_hub4j-test-org.json @@ -0,0 +1,48 @@ +{ + "id": "58a9c36a-43fa-4632-974c-4cef049ec189", + "name": "orgs_hub4j-test-org", + "request": { + "url": "/orgs/hub4j-test-org", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "2-orgs_hub4j-test-org.json", + "headers": { + "Date": "Sun, 25 Jan 2026 03:18:06 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"00956a4407b90370d23784c971560ee6c5161de0af00f778ba8b07204571380b\"", + "Last-Modified": "Tue, 05 Aug 2025 00:53:03 GMT", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "admin:org, read:org, repo, user, write:org", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4961", + "X-RateLimit-Reset": "1769314022", + "X-RateLimit-Used": "39", + "X-RateLimit-Resource": "core", + "X-GitHub-Request-Id": "D2A9:11A506:261AEB2:21144D4:69758B6E" + } + }, + "uuid": "58a9c36a-43fa-4632-974c-4cef049ec189", + "persistent": true, + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/mappings/3-r_h_github-api.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/mappings/3-r_h_github-api.json new file mode 100644 index 0000000000..13addd0adf --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/mappings/3-r_h_github-api.json @@ -0,0 +1,48 @@ +{ + "id": "30135f26-dd4f-4e9c-9422-3f492a536e75", + "name": "repos_hub4j-test-org_github-api", + "request": { + "url": "/repos/hub4j-test-org/github-api", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "3-r_h_github-api.json", + "headers": { + "Date": "Sun, 25 Jan 2026 03:18:07 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"53eb536ba63deda9da7c94df7ec4300b0c98dcf83c228c2a027ce1cfe93ef94a\"", + "Last-Modified": "Fri, 22 Mar 2024 23:30:32 GMT", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "repo", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4960", + "X-RateLimit-Reset": "1769314022", + "X-RateLimit-Used": "40", + "X-RateLimit-Resource": "core", + "X-GitHub-Request-Id": "D2AA:373827:105D464F:E72B683:69758B6E" + } + }, + "uuid": "30135f26-dd4f-4e9c-9422-3f492a536e75", + "persistent": true, + "insertionIndex": 3 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/mappings/4-r_h_g_pulls.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/mappings/4-r_h_g_pulls.json new file mode 100644 index 0000000000..e4602c1c4f --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/mappings/4-r_h_g_pulls.json @@ -0,0 +1,55 @@ +{ + "id": "e6801eb3-b5ea-47c5-b0e3-8c4aa2813c08", + "name": "repos_hub4j-test-org_github-api_pulls", + "request": { + "url": "/repos/hub4j-test-org/github-api/pulls", + "method": "POST", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{\"head\":\"test/stable\",\"draft\":true,\"maintainer_can_modify\":false,\"title\":\"markReadyForReview\",\"body\":\"## test\",\"base\":\"main\"}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 201, + "bodyFileName": "4-r_h_g_pulls.json", + "headers": { + "Date": "Sun, 25 Jan 2026 03:18:07 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "\"200ed505a980a7bb09b1f4cac99a709616044a8908721566c47ec0a0ac66187c\"", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4959", + "X-RateLimit-Reset": "1769314022", + "X-RateLimit-Used": "41", + "X-RateLimit-Resource": "core", + "X-GitHub-Request-Id": "D2AB:D9A12:2948536:245CBB3:69758B6F", + "Location": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/486" + } + }, + "uuid": "e6801eb3-b5ea-47c5-b0e3-8c4aa2813c08", + "persistent": true, + "insertionIndex": 4 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/mappings/5-graphql.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/mappings/5-graphql.json new file mode 100644 index 0000000000..c0693504ef --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/mappings/5-graphql.json @@ -0,0 +1,51 @@ +{ + "id": "d40081bf-57ba-4bf1-a1bd-c6726f518fed", + "name": "graphql", + "request": { + "url": "/graphql", + "method": "POST", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{\"query\":\"mutation MarkReadyForReview { markPullRequestReadyForReview(input: { pullRequestId: \\\"PR_kwDODFTdCc6_MbEN\\\"}) { pullRequest { id } } }\"}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 200, + "body": "{\"data\":{\"markPullRequestReadyForReview\":{\"pullRequest\":{\"id\":\"PR_kwDODFTdCc6_MbEN\"}}}}", + "headers": { + "Date": "Sun, 25 Jan 2026 03:18:08 GMT", + "Content-Type": "application/json; charset=utf-8", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "repo", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v4; format=json", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4927", + "X-RateLimit-Reset": "1769312274", + "X-RateLimit-Used": "73", + "X-RateLimit-Resource": "graphql", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Vary": "Accept-Encoding, Accept, X-Requested-With", + "Server": "github.com", + "X-GitHub-Request-Id": "D2AC:275471:2600920:210014D:69758B70" + } + }, + "uuid": "d40081bf-57ba-4bf1-a1bd-c6726f518fed", + "persistent": true, + "insertionIndex": 5 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/mappings/6-r_h_g_pulls_486.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/mappings/6-r_h_g_pulls_486.json new file mode 100644 index 0000000000..9612ac0860 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReview/mappings/6-r_h_g_pulls_486.json @@ -0,0 +1,47 @@ +{ + "id": "d00fbce7-5819-4b69-88b4-a9201bbdbeaf", + "name": "repos_hub4j-test-org_github-api_pulls_486", + "request": { + "url": "/repos/hub4j-test-org/github-api/pulls/486", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "6-r_h_g_pulls_486.json", + "headers": { + "Access-Control-Allow-Origin": "*", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Content-Security-Policy": "default-src 'none'", + "Content-Type": "application/json; charset=utf-8", + "Date": "Sun, 25 Jan 2026 03:18:09 GMT", + "ETag": "W/\"0c63ffd8648417e7fbf86ee41f53146e573a08f88b3472f2ffd3974db9ae24cc\"", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "Last-Modified": "Sun, 25 Jan 2026 03:18:08 GMT", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "X-Accepted-OAuth-Scopes": "repo", + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": "deny", + "x-github-api-version-selected": "2022-11-28", + "X-GitHub-Media-Type": "github.v3; format=json", + "X-OAuth-Scopes": "repo", + "X-XSS-Protection": "0", + "Server": "github.com", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4957", + "X-RateLimit-Reset": "1769314022", + "X-RateLimit-Used": "43", + "X-RateLimit-Resource": "core", + "X-GitHub-Request-Id": "D2AE:19034C:25CB2D9:20EB663:69758B71" + } + }, + "uuid": "d00fbce7-5819-4b69-88b4-a9201bbdbeaf", + "persistent": true, + "insertionIndex": 6 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/__files/1-user.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/__files/1-user.json new file mode 100644 index 0000000000..fbc5eae788 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/__files/1-user.json @@ -0,0 +1,36 @@ +{ + "login": "Anonycoders", + "id": 40047636, + "node_id": "MDQ6VXNlcjQwMDQ3NjM2", + "avatar_url": "https://avatars.githubusercontent.com/u/40047636?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Anonycoders", + "html_url": "https://github.com/Anonycoders", + "followers_url": "https://api.github.com/users/Anonycoders/followers", + "following_url": "https://api.github.com/users/Anonycoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Anonycoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Anonycoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Anonycoders/subscriptions", + "organizations_url": "https://api.github.com/users/Anonycoders/orgs", + "repos_url": "https://api.github.com/users/Anonycoders/repos", + "events_url": "https://api.github.com/users/Anonycoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Anonycoders/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false, + "name": "Sorena Sarabadani", + "company": "@Adevinta", + "blog": "", + "location": "Berlin, Germany", + "email": "sorena.sarabadani@gmail.com", + "hireable": null, + "bio": "Ex-Shopifyer - Adevinta/Kleinanzeigen", + "twitter_username": "sorena_s", + "notification_email": "sorena.sarabadani@gmail.com", + "public_repos": 12, + "public_gists": 0, + "followers": 38, + "following": 4, + "created_at": "2018-06-08T02:07:15Z", + "updated_at": "2026-01-24T22:07:12Z" +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/__files/2-orgs_hub4j-test-org.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/__files/2-orgs_hub4j-test-org.json new file mode 100644 index 0000000000..8b36ed4dd1 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/__files/2-orgs_hub4j-test-org.json @@ -0,0 +1,76 @@ +{ + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "url": "https://api.github.com/orgs/hub4j-test-org", + "repos_url": "https://api.github.com/orgs/hub4j-test-org/repos", + "events_url": "https://api.github.com/orgs/hub4j-test-org/events", + "hooks_url": "https://api.github.com/orgs/hub4j-test-org/hooks", + "issues_url": "https://api.github.com/orgs/hub4j-test-org/issues", + "members_url": "https://api.github.com/orgs/hub4j-test-org/members{/member}", + "public_members_url": "https://api.github.com/orgs/hub4j-test-org/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "description": "Hub4j Test Org Description (this could be null or blank too)", + "name": "Hub4j Test Org Name (this could be null or blank too)", + "company": null, + "blog": "https://hub4j.url.io/could/be/null", + "location": "Hub4j Test Org Location (this could be null or blank too)", + "email": "hub4jtestorgemail@could.be.null.com", + "twitter_username": null, + "is_verified": false, + "has_organization_projects": true, + "has_repository_projects": true, + "public_repos": 27, + "public_gists": 0, + "followers": 2, + "following": 0, + "html_url": "https://github.com/hub4j-test-org", + "created_at": "2014-05-10T19:39:11Z", + "updated_at": "2025-08-05T00:53:03Z", + "archived_at": null, + "type": "Organization", + "total_private_repos": 8, + "owned_private_repos": 8, + "private_gists": 0, + "disk_usage": 12076, + "collaborators": 1, + "billing_email": "kk@kohsuke.org", + "default_repository_permission": "none", + "members_can_create_repositories": false, + "two_factor_requirement_enabled": false, + "members_allowed_repository_creation_type": "none", + "members_can_create_public_repositories": false, + "members_can_create_private_repositories": false, + "members_can_create_internal_repositories": false, + "members_can_create_pages": true, + "members_can_fork_private_repositories": false, + "web_commit_signoff_required": false, + "deploy_keys_enabled_for_repositories": false, + "members_can_delete_repositories": true, + "members_can_change_repo_visibility": true, + "members_can_invite_outside_collaborators": true, + "members_can_delete_issues": false, + "display_commenter_full_name_setting_enabled": false, + "readers_can_create_discussions": true, + "members_can_create_teams": true, + "members_can_view_dependency_insights": true, + "default_repository_branch": "main", + "members_can_create_public_pages": true, + "members_can_create_private_pages": true, + "plan": { + "name": "free", + "space": 976562499, + "private_repos": 10000, + "filled_seats": 53, + "seats": 3 + }, + "advanced_security_enabled_for_new_repositories": false, + "dependabot_alerts_enabled_for_new_repositories": false, + "dependabot_security_updates_enabled_for_new_repositories": false, + "dependency_graph_enabled_for_new_repositories": false, + "secret_scanning_enabled_for_new_repositories": false, + "secret_scanning_push_protection_enabled_for_new_repositories": false, + "secret_scanning_push_protection_custom_link_enabled": false, + "secret_scanning_push_protection_custom_link": null, + "secret_scanning_validity_checks_enabled": false +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/__files/3-r_h_github-api.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/__files/3-r_h_github-api.json new file mode 100644 index 0000000000..7e107d980a --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/__files/3-r_h_github-api.json @@ -0,0 +1,397 @@ +{ + "id": 206888201, + "node_id": "MDEwOlJlcG9zaXRvcnkyMDY4ODgyMDE=", + "name": "github-api", + "full_name": "hub4j-test-org/github-api", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/github-api", + "description": "Tricky", + "fork": true, + "url": "https://api.github.com/repos/hub4j-test-org/github-api", + "forks_url": "https://api.github.com/repos/hub4j-test-org/github-api/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/github-api/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/github-api/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/github-api/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/github-api/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/github-api/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/github-api/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/github-api/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/github-api/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/github-api/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/github-api/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/github-api/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/github-api/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/github-api/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/github-api/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/github-api/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/github-api/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/github-api/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/github-api/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/github-api/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/github-api/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/github-api/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/github-api/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/github-api/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/github-api/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/github-api/deployments", + "created_at": "2019-09-06T23:26:04Z", + "updated_at": "2024-03-22T23:30:32Z", + "pushed_at": "2024-06-16T10:20:03Z", + "git_url": "git://github.com/hub4j-test-org/github-api.git", + "ssh_url": "git@github.com:hub4j-test-org/github-api.git", + "clone_url": "https://github.com/hub4j-test-org/github-api.git", + "svn_url": "https://github.com/hub4j-test-org/github-api", + "homepage": "http://github-api.kohsuke.org/", + "size": 18977, + "stargazers_count": 1, + "watchers_count": 1, + "language": "Java", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 7, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 7, + "watchers": 1, + "default_branch": "main", + "permissions": { + "admin": true, + "maintain": true, + "push": true, + "triage": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": false, + "delete_branch_on_merge": false, + "allow_update_branch": false, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE", + "custom_properties": {}, + "organization": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "parent": { + "id": 617210, + "node_id": "MDEwOlJlcG9zaXRvcnk2MTcyMTA=", + "name": "github-api", + "full_name": "hub4j/github-api", + "private": false, + "owner": { + "login": "hub4j", + "id": 54909825, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU0OTA5ODI1", + "avatar_url": "https://avatars.githubusercontent.com/u/54909825?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j", + "html_url": "https://github.com/hub4j", + "followers_url": "https://api.github.com/users/hub4j/followers", + "following_url": "https://api.github.com/users/hub4j/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j/orgs", + "repos_url": "https://api.github.com/users/hub4j/repos", + "events_url": "https://api.github.com/users/hub4j/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/hub4j/github-api", + "description": "Java API for GitHub", + "fork": false, + "url": "https://api.github.com/repos/hub4j/github-api", + "forks_url": "https://api.github.com/repos/hub4j/github-api/forks", + "keys_url": "https://api.github.com/repos/hub4j/github-api/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j/github-api/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j/github-api/teams", + "hooks_url": "https://api.github.com/repos/hub4j/github-api/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j/github-api/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j/github-api/events", + "assignees_url": "https://api.github.com/repos/hub4j/github-api/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j/github-api/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j/github-api/tags", + "blobs_url": "https://api.github.com/repos/hub4j/github-api/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j/github-api/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j/github-api/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j/github-api/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j/github-api/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j/github-api/languages", + "stargazers_url": "https://api.github.com/repos/hub4j/github-api/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j/github-api/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j/github-api/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j/github-api/subscription", + "commits_url": "https://api.github.com/repos/hub4j/github-api/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j/github-api/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j/github-api/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j/github-api/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j/github-api/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j/github-api/merges", + "archive_url": "https://api.github.com/repos/hub4j/github-api/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j/github-api/downloads", + "issues_url": "https://api.github.com/repos/hub4j/github-api/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j/github-api/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j/github-api/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j/github-api/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j/github-api/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j/github-api/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j/github-api/deployments", + "created_at": "2010-04-19T04:13:03Z", + "updated_at": "2026-01-25T03:20:40Z", + "pushed_at": "2026-01-25T03:20:35Z", + "git_url": "git://github.com/hub4j/github-api.git", + "ssh_url": "git@github.com:hub4j/github-api.git", + "clone_url": "https://github.com/hub4j/github-api.git", + "svn_url": "https://github.com/hub4j/github-api", + "homepage": "https://hub4j.github.io/github-api/", + "size": 66454, + "stargazers_count": 1230, + "watchers_count": 1230, + "language": "Java", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "has_discussions": true, + "forks_count": 769, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 179, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "api", + "client-library", + "github", + "github-api", + "github-api-v3", + "java", + "java-api" + ], + "visibility": "public", + "forks": 769, + "open_issues": 179, + "watchers": 1230, + "default_branch": "main" + }, + "source": { + "id": 617210, + "node_id": "MDEwOlJlcG9zaXRvcnk2MTcyMTA=", + "name": "github-api", + "full_name": "hub4j/github-api", + "private": false, + "owner": { + "login": "hub4j", + "id": 54909825, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU0OTA5ODI1", + "avatar_url": "https://avatars.githubusercontent.com/u/54909825?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j", + "html_url": "https://github.com/hub4j", + "followers_url": "https://api.github.com/users/hub4j/followers", + "following_url": "https://api.github.com/users/hub4j/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j/orgs", + "repos_url": "https://api.github.com/users/hub4j/repos", + "events_url": "https://api.github.com/users/hub4j/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/hub4j/github-api", + "description": "Java API for GitHub", + "fork": false, + "url": "https://api.github.com/repos/hub4j/github-api", + "forks_url": "https://api.github.com/repos/hub4j/github-api/forks", + "keys_url": "https://api.github.com/repos/hub4j/github-api/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j/github-api/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j/github-api/teams", + "hooks_url": "https://api.github.com/repos/hub4j/github-api/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j/github-api/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j/github-api/events", + "assignees_url": "https://api.github.com/repos/hub4j/github-api/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j/github-api/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j/github-api/tags", + "blobs_url": "https://api.github.com/repos/hub4j/github-api/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j/github-api/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j/github-api/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j/github-api/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j/github-api/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j/github-api/languages", + "stargazers_url": "https://api.github.com/repos/hub4j/github-api/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j/github-api/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j/github-api/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j/github-api/subscription", + "commits_url": "https://api.github.com/repos/hub4j/github-api/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j/github-api/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j/github-api/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j/github-api/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j/github-api/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j/github-api/merges", + "archive_url": "https://api.github.com/repos/hub4j/github-api/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j/github-api/downloads", + "issues_url": "https://api.github.com/repos/hub4j/github-api/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j/github-api/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j/github-api/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j/github-api/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j/github-api/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j/github-api/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j/github-api/deployments", + "created_at": "2010-04-19T04:13:03Z", + "updated_at": "2026-01-25T03:20:40Z", + "pushed_at": "2026-01-25T03:20:35Z", + "git_url": "git://github.com/hub4j/github-api.git", + "ssh_url": "git@github.com:hub4j/github-api.git", + "clone_url": "https://github.com/hub4j/github-api.git", + "svn_url": "https://github.com/hub4j/github-api", + "homepage": "https://hub4j.github.io/github-api/", + "size": 66454, + "stargazers_count": 1230, + "watchers_count": 1230, + "language": "Java", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "has_discussions": true, + "forks_count": 769, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 179, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "api", + "client-library", + "github", + "github-api", + "github-api-v3", + "java", + "java-api" + ], + "visibility": "public", + "forks": 769, + "open_issues": 179, + "watchers": 1230, + "default_branch": "main" + }, + "security_and_analysis": { + "secret_scanning": { + "status": "disabled" + }, + "secret_scanning_push_protection": { + "status": "disabled" + }, + "dependabot_security_updates": { + "status": "disabled" + }, + "secret_scanning_non_provider_patterns": { + "status": "disabled" + }, + "secret_scanning_validity_checks": { + "status": "disabled" + } + }, + "network_count": 769, + "subscribers_count": 0 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/__files/4-r_h_g_pulls.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/__files/4-r_h_g_pulls.json new file mode 100644 index 0000000000..aac84c4629 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/__files/4-r_h_g_pulls.json @@ -0,0 +1,359 @@ +{ + "url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/487", + "id": 3207753962, + "node_id": "PR_kwDODFTdCc6_MnDq", + "html_url": "https://github.com/hub4j-test-org/github-api/pull/487", + "diff_url": "https://github.com/hub4j-test-org/github-api/pull/487.diff", + "patch_url": "https://github.com/hub4j-test-org/github-api/pull/487.patch", + "issue_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/487", + "number": 487, + "state": "open", + "locked": false, + "title": "markReadyForReviewNonDraft", + "user": { + "login": "Anonycoders", + "id": 40047636, + "node_id": "MDQ6VXNlcjQwMDQ3NjM2", + "avatar_url": "https://avatars.githubusercontent.com/u/40047636?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Anonycoders", + "html_url": "https://github.com/Anonycoders", + "followers_url": "https://api.github.com/users/Anonycoders/followers", + "following_url": "https://api.github.com/users/Anonycoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Anonycoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Anonycoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Anonycoders/subscriptions", + "organizations_url": "https://api.github.com/users/Anonycoders/orgs", + "repos_url": "https://api.github.com/users/Anonycoders/repos", + "events_url": "https://api.github.com/users/Anonycoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Anonycoders/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "body": "## test", + "created_at": "2026-01-25T04:11:59Z", + "updated_at": "2026-01-25T04:11:59Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": null, + "assignee": null, + "assignees": [], + "requested_reviewers": [], + "requested_teams": [], + "labels": [], + "milestone": null, + "draft": false, + "commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/487/commits", + "review_comments_url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/487/comments", + "review_comment_url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/487/comments", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/github-api/statuses/07374fe73aff1c2024a8d4114b32406c7a8e89b7", + "head": { + "label": "hub4j-test-org:test/stable", + "ref": "test/stable", + "sha": "07374fe73aff1c2024a8d4114b32406c7a8e89b7", + "user": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 206888201, + "node_id": "MDEwOlJlcG9zaXRvcnkyMDY4ODgyMDE=", + "name": "github-api", + "full_name": "hub4j-test-org/github-api", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/github-api", + "description": "Tricky", + "fork": true, + "url": "https://api.github.com/repos/hub4j-test-org/github-api", + "forks_url": "https://api.github.com/repos/hub4j-test-org/github-api/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/github-api/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/github-api/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/github-api/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/github-api/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/github-api/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/github-api/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/github-api/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/github-api/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/github-api/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/github-api/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/github-api/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/github-api/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/github-api/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/github-api/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/github-api/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/github-api/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/github-api/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/github-api/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/github-api/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/github-api/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/github-api/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/github-api/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/github-api/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/github-api/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/github-api/deployments", + "created_at": "2019-09-06T23:26:04Z", + "updated_at": "2024-03-22T23:30:32Z", + "pushed_at": "2024-06-16T10:20:03Z", + "git_url": "git://github.com/hub4j-test-org/github-api.git", + "ssh_url": "git@github.com:hub4j-test-org/github-api.git", + "clone_url": "https://github.com/hub4j-test-org/github-api.git", + "svn_url": "https://github.com/hub4j-test-org/github-api", + "homepage": "http://github-api.kohsuke.org/", + "size": 18977, + "stargazers_count": 1, + "watchers_count": 1, + "language": "Java", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 8, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 8, + "watchers": 1, + "default_branch": "main" + } + }, + "base": { + "label": "hub4j-test-org:main", + "ref": "main", + "sha": "c4b41922197a1d595bff30e89bb8540013ee4fd3", + "user": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 206888201, + "node_id": "MDEwOlJlcG9zaXRvcnkyMDY4ODgyMDE=", + "name": "github-api", + "full_name": "hub4j-test-org/github-api", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/github-api", + "description": "Tricky", + "fork": true, + "url": "https://api.github.com/repos/hub4j-test-org/github-api", + "forks_url": "https://api.github.com/repos/hub4j-test-org/github-api/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/github-api/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/github-api/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/github-api/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/github-api/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/github-api/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/github-api/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/github-api/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/github-api/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/github-api/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/github-api/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/github-api/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/github-api/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/github-api/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/github-api/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/github-api/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/github-api/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/github-api/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/github-api/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/github-api/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/github-api/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/github-api/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/github-api/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/github-api/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/github-api/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/github-api/deployments", + "created_at": "2019-09-06T23:26:04Z", + "updated_at": "2024-03-22T23:30:32Z", + "pushed_at": "2024-06-16T10:20:03Z", + "git_url": "git://github.com/hub4j-test-org/github-api.git", + "ssh_url": "git@github.com:hub4j-test-org/github-api.git", + "clone_url": "https://github.com/hub4j-test-org/github-api.git", + "svn_url": "https://github.com/hub4j-test-org/github-api", + "homepage": "http://github-api.kohsuke.org/", + "size": 18977, + "stargazers_count": 1, + "watchers_count": 1, + "language": "Java", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 8, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 8, + "watchers": 1, + "default_branch": "main" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/487" + }, + "html": { + "href": "https://github.com/hub4j-test-org/github-api/pull/487" + }, + "issue": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/issues/487" + }, + "comments": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/issues/487/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/487/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/487/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/statuses/07374fe73aff1c2024a8d4114b32406c7a8e89b7" + } + }, + "author_association": "MEMBER", + "auto_merge": null, + "active_lock_reason": null, + "merged": false, + "mergeable": null, + "rebaseable": null, + "mergeable_state": "unknown", + "merged_by": null, + "comments": 0, + "review_comments": 0, + "maintainer_can_modify": false, + "commits": 3, + "additions": 3, + "deletions": 2, + "changed_files": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/mappings/1-user.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/mappings/1-user.json new file mode 100644 index 0000000000..8f4402d010 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/mappings/1-user.json @@ -0,0 +1,48 @@ +{ + "id": "b44e0863-c7bd-4ee5-ab4b-5892017c5730", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "1-user.json", + "headers": { + "Date": "Sun, 25 Jan 2026 04:11:57 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"15d7e1ad92a3639b979fc55254902e63ee0bfa5c8f6766990bf989044d491ce1\"", + "Last-Modified": "Sat, 24 Jan 2026 22:07:12 GMT", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4989", + "X-RateLimit-Reset": "1769317626", + "X-RateLimit-Used": "11", + "X-RateLimit-Resource": "core", + "X-GitHub-Request-Id": "F21E:38EB6:28BF995:23864B6:6975980D" + } + }, + "uuid": "b44e0863-c7bd-4ee5-ab4b-5892017c5730", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/mappings/2-orgs_hub4j-test-org.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/mappings/2-orgs_hub4j-test-org.json new file mode 100644 index 0000000000..c70c16c208 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/mappings/2-orgs_hub4j-test-org.json @@ -0,0 +1,48 @@ +{ + "id": "fcc340cd-78fc-48c1-873c-5332440cc41d", + "name": "orgs_hub4j-test-org", + "request": { + "url": "/orgs/hub4j-test-org", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "2-orgs_hub4j-test-org.json", + "headers": { + "Date": "Sun, 25 Jan 2026 04:11:58 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"00956a4407b90370d23784c971560ee6c5161de0af00f778ba8b07204571380b\"", + "Last-Modified": "Tue, 05 Aug 2025 00:53:03 GMT", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "admin:org, read:org, repo, user, write:org", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4984", + "X-RateLimit-Reset": "1769317626", + "X-RateLimit-Used": "16", + "X-RateLimit-Resource": "core", + "X-GitHub-Request-Id": "F220:3CF23:2873A9E:2340C40:6975980E" + } + }, + "uuid": "fcc340cd-78fc-48c1-873c-5332440cc41d", + "persistent": true, + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/mappings/3-r_h_github-api.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/mappings/3-r_h_github-api.json new file mode 100644 index 0000000000..442ff3502e --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/mappings/3-r_h_github-api.json @@ -0,0 +1,48 @@ +{ + "id": "e38c90e5-3d50-42c3-bb81-630e2ed8dd6c", + "name": "repos_hub4j-test-org_github-api", + "request": { + "url": "/repos/hub4j-test-org/github-api", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "3-r_h_github-api.json", + "headers": { + "Date": "Sun, 25 Jan 2026 04:11:59 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"069a761d0325a44f38742c806c4563d69400543c5cd2a0373d68ebf870711c51\"", + "Last-Modified": "Fri, 22 Mar 2024 23:30:32 GMT", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "repo", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4983", + "X-RateLimit-Reset": "1769317626", + "X-RateLimit-Used": "17", + "X-RateLimit-Resource": "core", + "X-GitHub-Request-Id": "F221:11A506:289CC2D:2368264:6975980E" + } + }, + "uuid": "e38c90e5-3d50-42c3-bb81-630e2ed8dd6c", + "persistent": true, + "insertionIndex": 3 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/mappings/4-r_h_g_pulls.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/mappings/4-r_h_g_pulls.json new file mode 100644 index 0000000000..6c568d066a --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/markReadyForReviewNonDraft/mappings/4-r_h_g_pulls.json @@ -0,0 +1,55 @@ +{ + "id": "863bc8f2-017d-4d15-8589-5187109dbe38", + "name": "repos_hub4j-test-org_github-api_pulls", + "request": { + "url": "/repos/hub4j-test-org/github-api/pulls", + "method": "POST", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{\"head\":\"test/stable\",\"draft\":false,\"maintainer_can_modify\":true,\"title\":\"markReadyForReviewNonDraft\",\"body\":\"## test\",\"base\":\"main\"}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 201, + "bodyFileName": "4-r_h_g_pulls.json", + "headers": { + "Date": "Sun, 25 Jan 2026 04:12:00 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "\"bddcb2f98f65303801f4add26b2bf8d27ac917e20ed838934b47af12b3b26a3b\"", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4982", + "X-RateLimit-Reset": "1769317626", + "X-RateLimit-Used": "18", + "X-RateLimit-Resource": "core", + "X-GitHub-Request-Id": "F222:38EB6:28C0057:2386B06:6975980F", + "Location": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/487" + } + }, + "uuid": "863bc8f2-017d-4d15-8589-5187109dbe38", + "persistent": true, + "insertionIndex": 4 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviewComments/mappings/7-r_h_g_pulls_484_comments.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviewComments/mappings/7-r_h_g_pulls_484_comments.json index c62816d287..94ea535e21 100644 --- a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviewComments/mappings/7-r_h_g_pulls_484_comments.json +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviewComments/mappings/7-r_h_g_pulls_484_comments.json @@ -11,7 +11,7 @@ }, "bodyPatterns": [ { - "equalToJson": "{\"path\":\"README.md\",\"line\":2,\"body\":\"A single line review comment\",\"commit_id\":\"07374fe73aff1c2024a8d4114b32406c7a8e89b7\"}", + "equalToJson": "{\"path\":\"README.md\",\"side\":\"right\",\"line\":2,\"body\":\"A single line review comment\",\"commit_id\":\"07374fe73aff1c2024a8d4114b32406c7a8e89b7\"}", "ignoreArrayOrder": true, "ignoreExtraElements": false } @@ -55,4 +55,4 @@ "uuid": "43f15589-cf59-4ed5-a2b8-8f6b7ebae791", "persistent": true, "insertionIndex": 7 -} \ No newline at end of file +} diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviewComments/mappings/8-r_h_g_pulls_484_comments.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviewComments/mappings/8-r_h_g_pulls_484_comments.json index 2b80fe4f36..f335232943 100644 --- a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviewComments/mappings/8-r_h_g_pulls_484_comments.json +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviewComments/mappings/8-r_h_g_pulls_484_comments.json @@ -11,7 +11,7 @@ }, "bodyPatterns": [ { - "equalToJson": "{\"path\":\"README.md\",\"line\":3,\"start_line\":2,\"body\":\"A multiline review comment\",\"commit_id\":\"07374fe73aff1c2024a8d4114b32406c7a8e89b7\"}", + "equalToJson": "{\"path\":\"README.md\",\"side\":\"right\",\"start_side\":\"right\",\"line\":3,\"start_line\":2,\"body\":\"A multiline review comment\",\"commit_id\":\"07374fe73aff1c2024a8d4114b32406c7a8e89b7\"}", "ignoreArrayOrder": true, "ignoreExtraElements": false } @@ -55,4 +55,4 @@ "uuid": "efe94b1b-7f9c-4bba-9af2-0a7a59a498ab", "persistent": true, "insertionIndex": 8 -} \ No newline at end of file +} diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviews/__files/12-user.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviews/__files/12-user.json new file mode 100644 index 0000000000..fbc5eae788 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviews/__files/12-user.json @@ -0,0 +1,36 @@ +{ + "login": "Anonycoders", + "id": 40047636, + "node_id": "MDQ6VXNlcjQwMDQ3NjM2", + "avatar_url": "https://avatars.githubusercontent.com/u/40047636?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Anonycoders", + "html_url": "https://github.com/Anonycoders", + "followers_url": "https://api.github.com/users/Anonycoders/followers", + "following_url": "https://api.github.com/users/Anonycoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Anonycoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Anonycoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Anonycoders/subscriptions", + "organizations_url": "https://api.github.com/users/Anonycoders/orgs", + "repos_url": "https://api.github.com/users/Anonycoders/repos", + "events_url": "https://api.github.com/users/Anonycoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Anonycoders/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false, + "name": "Sorena Sarabadani", + "company": "@Adevinta", + "blog": "", + "location": "Berlin, Germany", + "email": "sorena.sarabadani@gmail.com", + "hireable": null, + "bio": "Ex-Shopifyer - Adevinta/Kleinanzeigen", + "twitter_username": "sorena_s", + "notification_email": "sorena.sarabadani@gmail.com", + "public_repos": 12, + "public_gists": 0, + "followers": 38, + "following": 4, + "created_at": "2018-06-08T02:07:15Z", + "updated_at": "2026-01-24T22:07:12Z" +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviews/__files/13-r_h_g_pulls_comments_1641771497.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviews/__files/13-r_h_g_pulls_comments_1641771497.json new file mode 100644 index 0000000000..a51974394d --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviews/__files/13-r_h_g_pulls_comments_1641771497.json @@ -0,0 +1,69 @@ +{ + "url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/comments/1641771497", + "pull_request_review_id": 2121304234, + "id": 1641771497, + "node_id": "PRRC_kwDODFTdCc5h23Hp", + "diff_hunk": "@@ -1,3 +1,4 @@\n-Java API for GitHub", + "path": "README.md", + "commit_id": "07374fe73aff1c2024a8d4114b32406c7a8e89b7", + "original_commit_id": "07374fe73aff1c2024a8d4114b32406c7a8e89b7", + "user": { + "login": "maximevw", + "id": 48218208, + "node_id": "MDQ6VXNlcjQ4MjE4MjA4", + "avatar_url": "https://avatars.githubusercontent.com/u/48218208?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/maximevw", + "html_url": "https://github.com/maximevw", + "followers_url": "https://api.github.com/users/maximevw/followers", + "following_url": "https://api.github.com/users/maximevw/following{/other_user}", + "gists_url": "https://api.github.com/users/maximevw/gists{/gist_id}", + "starred_url": "https://api.github.com/users/maximevw/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/maximevw/subscriptions", + "organizations_url": "https://api.github.com/users/maximevw/orgs", + "repos_url": "https://api.github.com/users/maximevw/repos", + "events_url": "https://api.github.com/users/maximevw/events{/privacy}", + "received_events_url": "https://api.github.com/users/maximevw/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "body": "Some niggle", + "created_at": "2024-06-16T09:55:53Z", + "updated_at": "2024-06-16T09:55:55Z", + "html_url": "https://github.com/hub4j-test-org/github-api/pull/482#discussion_r1641771497", + "pull_request_url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/482", + "_links": { + "self": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/comments/1641771497" + }, + "html": { + "href": "https://github.com/hub4j-test-org/github-api/pull/482#discussion_r1641771497" + }, + "pull_request": { + "href": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/482" + } + }, + "reactions": { + "url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls/comments/1641771497/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "start_line": null, + "original_start_line": null, + "start_side": null, + "line": 1, + "original_line": 1, + "side": "LEFT", + "author_association": "MEMBER", + "original_position": 1, + "position": 1, + "subject_type": "line" +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviews/mappings/12-user.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviews/mappings/12-user.json new file mode 100644 index 0000000000..5853e14e7a --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviews/mappings/12-user.json @@ -0,0 +1,48 @@ +{ + "id": "5531941e-d98d-4c37-be0e-cfe288bcc93d", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "12-user.json", + "headers": { + "Date": "Tue, 10 Feb 2026 21:33:45 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"15d7e1ad92a3639b979fc55254902e63ee0bfa5c8f6766990bf989044d491ce1\"", + "Last-Modified": "Sat, 24 Jan 2026 22:07:12 GMT", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4997", + "X-RateLimit-Reset": "1770762770", + "X-RateLimit-Used": "3", + "X-RateLimit-Resource": "core", + "X-GitHub-Request-Id": "C400:31B50A:A9FC456:9DD9471:698BA438" + } + }, + "uuid": "5531941e-d98d-4c37-be0e-cfe288bcc93d", + "persistent": true, + "insertionIndex": 12 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviews/mappings/13-r_h_g_pulls_comments_1641771497.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviews/mappings/13-r_h_g_pulls_comments_1641771497.json new file mode 100644 index 0000000000..6b14f69e19 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/pullRequestReviews/mappings/13-r_h_g_pulls_comments_1641771497.json @@ -0,0 +1,48 @@ +{ + "id": "729a05bb-28a0-4e91-99a3-347eb78bce62", + "name": "repos_hub4j-test-org_github-api_pulls_comments_1641771497", + "request": { + "url": "/repos/hub4j-test-org/github-api/pulls/comments/1641771497", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "13-r_h_g_pulls_comments_1641771497.json", + "headers": { + "Date": "Tue, 10 Feb 2026 21:33:47 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"089067a7341750dff9af7d18f68314ec595952d4062b8605e17e39a85285a435\"", + "Last-Modified": "Tue, 10 Feb 2026 15:58:02 GMT", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4992", + "X-RateLimit-Reset": "1770762770", + "X-RateLimit-Used": "8", + "X-RateLimit-Resource": "core", + "X-GitHub-Request-Id": "C402:3085FD:A9A140B:9D63CD0:698BA43A" + } + }, + "uuid": "729a05bb-28a0-4e91-99a3-347eb78bce62", + "persistent": true, + "insertionIndex": 13 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/queryPullRequestsQualifiedHead/mappings/6-r_h_g_pulls.json b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/queryPullRequestsQualifiedHead/mappings/6-r_h_g_pulls.json index 8eaeaeb3df..c7ceb0d2df 100644 --- a/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/queryPullRequestsQualifiedHead/mappings/6-r_h_g_pulls.json +++ b/src/test/resources/org/kohsuke/github/GHPullRequestTest/wiremock/queryPullRequestsQualifiedHead/mappings/6-r_h_g_pulls.json @@ -2,7 +2,7 @@ "id": "ab74613d-0613-47a8-a6fc-34add77d9967", "name": "repos_hub4j-test-org_github-api_pulls", "request": { - "url": "/repos/hub4j-test-org/github-api/pulls?state=open&head=hub4j-test-org%3Atest%2Fstable&base=main", + "url": "/repos/hub4j-test-org/github-api/pulls?state=open&head=hub4j-test-org%3Atest%2Fstable&base=main&per_page=5", "method": "GET", "headers": { "Accept": { diff --git a/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/__files/1-user.json b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/__files/1-user.json new file mode 100644 index 0000000000..e4fc4d8657 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/__files/1-user.json @@ -0,0 +1,45 @@ +{ + "login": "bitwiseman", + "id": 1958953, + "node_id": "MDQ6VXNlcjE5NTg5NTM=", + "avatar_url": "https://avatars3.githubusercontent.com/u/1958953?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bitwiseman", + "html_url": "https://github.com/bitwiseman", + "followers_url": "https://api.github.com/users/bitwiseman/followers", + "following_url": "https://api.github.com/users/bitwiseman/following{/other_user}", + "gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions", + "organizations_url": "https://api.github.com/users/bitwiseman/orgs", + "repos_url": "https://api.github.com/users/bitwiseman/repos", + "events_url": "https://api.github.com/users/bitwiseman/events{/privacy}", + "received_events_url": "https://api.github.com/users/bitwiseman/received_events", + "type": "User", + "site_admin": false, + "name": "Liam Newman", + "company": "Cloudbees, Inc.", + "blog": "", + "location": "Seattle, WA, USA", + "email": "bitwiseman@gmail.com", + "hireable": null, + "bio": "https://twitter.com/bitwiseman", + "public_repos": 183, + "public_gists": 7, + "followers": 159, + "following": 9, + "created_at": "2012-07-11T20:38:33Z", + "updated_at": "2020-05-20T16:02:42Z", + "private_gists": 19, + "total_private_repos": 12, + "owned_private_repos": 0, + "disk_usage": 33697, + "collaborators": 0, + "two_factor_authentication": true, + "plan": { + "name": "free", + "space": 976562499, + "collaborators": 0, + "private_repos": 10000 + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/__files/2-orgs_hub4j-test-org.json b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/__files/2-orgs_hub4j-test-org.json new file mode 100644 index 0000000000..e079133f20 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/__files/2-orgs_hub4j-test-org.json @@ -0,0 +1,41 @@ +{ + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "url": "https://api.github.com/orgs/hub4j-test-org", + "repos_url": "https://api.github.com/orgs/hub4j-test-org/repos", + "events_url": "https://api.github.com/orgs/hub4j-test-org/events", + "hooks_url": "https://api.github.com/orgs/hub4j-test-org/hooks", + "issues_url": "https://api.github.com/orgs/hub4j-test-org/issues", + "members_url": "https://api.github.com/orgs/hub4j-test-org/members{/member}", + "public_members_url": "https://api.github.com/orgs/hub4j-test-org/public_members{/member}", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "description": null, + "is_verified": false, + "has_organization_projects": true, + "has_repository_projects": true, + "public_repos": 15, + "public_gists": 0, + "followers": 0, + "following": 0, + "html_url": "https://github.com/hub4j-test-org", + "created_at": "2014-05-10T19:39:11Z", + "updated_at": "2020-05-15T15:14:14Z", + "type": "Organization", + "total_private_repos": 0, + "owned_private_repos": 0, + "private_gists": 0, + "disk_usage": 148, + "collaborators": 0, + "billing_email": "kk@kohsuke.org", + "default_repository_permission": "none", + "members_can_create_repositories": false, + "two_factor_requirement_enabled": false, + "plan": { + "name": "free", + "space": 976562499, + "private_repos": 10000, + "filled_seats": 17, + "seats": 3 + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/__files/3-r_h_github-api.json b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/__files/3-r_h_github-api.json new file mode 100644 index 0000000000..aadbbf12ef --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/__files/3-r_h_github-api.json @@ -0,0 +1,332 @@ +{ + "id": 206888201, + "node_id": "MDEwOlJlcG9zaXRvcnkyMDY4ODgyMDE=", + "name": "github-api", + "full_name": "hub4j-test-org/github-api", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/github-api", + "description": "Tricky", + "fork": true, + "url": "https://api.github.com/repos/hub4j-test-org/github-api", + "forks_url": "https://api.github.com/repos/hub4j-test-org/github-api/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/github-api/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/github-api/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/github-api/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/github-api/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/github-api/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/github-api/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/github-api/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/github-api/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/github-api/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/github-api/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/github-api/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/github-api/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/github-api/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/github-api/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/github-api/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/github-api/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/github-api/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/github-api/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/github-api/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/github-api/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/github-api/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/github-api/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/github-api/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/github-api/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/github-api/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/github-api/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/github-api/deployments", + "created_at": "2019-09-06T23:26:04Z", + "updated_at": "2020-01-16T21:22:56Z", + "pushed_at": "2020-05-20T16:22:43Z", + "git_url": "git://github.com/hub4j-test-org/github-api.git", + "ssh_url": "git@github.com:hub4j-test-org/github-api.git", + "clone_url": "https://github.com/hub4j-test-org/github-api.git", + "svn_url": "https://github.com/hub4j-test-org/github-api", + "homepage": "http://github-api.kohsuke.org/", + "size": 19035, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Java", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 5, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "forks": 0, + "open_issues": 5, + "watchers": 0, + "default_branch": "main", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "delete_branch_on_merge": false, + "organization": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "parent": { + "id": 617210, + "node_id": "MDEwOlJlcG9zaXRvcnk2MTcyMTA=", + "name": "github-api", + "full_name": "hub4j/github-api", + "private": false, + "owner": { + "login": "hub4j", + "id": 54909825, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU0OTA5ODI1", + "avatar_url": "https://avatars3.githubusercontent.com/u/54909825?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j", + "html_url": "https://github.com/hub4j", + "followers_url": "https://api.github.com/users/hub4j/followers", + "following_url": "https://api.github.com/users/hub4j/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j/orgs", + "repos_url": "https://api.github.com/users/hub4j/repos", + "events_url": "https://api.github.com/users/hub4j/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/hub4j/github-api", + "description": "Java API for GitHub", + "fork": false, + "url": "https://api.github.com/repos/hub4j/github-api", + "forks_url": "https://api.github.com/repos/hub4j/github-api/forks", + "keys_url": "https://api.github.com/repos/hub4j/github-api/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j/github-api/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j/github-api/teams", + "hooks_url": "https://api.github.com/repos/hub4j/github-api/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j/github-api/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j/github-api/events", + "assignees_url": "https://api.github.com/repos/hub4j/github-api/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j/github-api/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j/github-api/tags", + "blobs_url": "https://api.github.com/repos/hub4j/github-api/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j/github-api/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j/github-api/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j/github-api/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j/github-api/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j/github-api/languages", + "stargazers_url": "https://api.github.com/repos/hub4j/github-api/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j/github-api/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j/github-api/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j/github-api/subscription", + "commits_url": "https://api.github.com/repos/hub4j/github-api/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j/github-api/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j/github-api/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j/github-api/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j/github-api/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j/github-api/merges", + "archive_url": "https://api.github.com/repos/hub4j/github-api/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j/github-api/downloads", + "issues_url": "https://api.github.com/repos/hub4j/github-api/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j/github-api/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j/github-api/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j/github-api/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j/github-api/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j/github-api/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j/github-api/deployments", + "created_at": "2010-04-19T04:13:03Z", + "updated_at": "2020-05-20T22:54:46Z", + "pushed_at": "2020-05-20T20:24:04Z", + "git_url": "git://github.com/hub4j/github-api.git", + "ssh_url": "git@github.com:hub4j/github-api.git", + "clone_url": "https://github.com/hub4j/github-api.git", + "svn_url": "https://github.com/hub4j/github-api", + "homepage": "https://github-api.kohsuke.org/", + "size": 23100, + "stargazers_count": 656, + "watchers_count": 656, + "language": "Java", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 478, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 67, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "forks": 478, + "open_issues": 67, + "watchers": 656, + "default_branch": "main" + }, + "source": { + "id": 617210, + "node_id": "MDEwOlJlcG9zaXRvcnk2MTcyMTA=", + "name": "github-api", + "full_name": "hub4j/github-api", + "private": false, + "owner": { + "login": "hub4j", + "id": 54909825, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU0OTA5ODI1", + "avatar_url": "https://avatars3.githubusercontent.com/u/54909825?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j", + "html_url": "https://github.com/hub4j", + "followers_url": "https://api.github.com/users/hub4j/followers", + "following_url": "https://api.github.com/users/hub4j/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j/orgs", + "repos_url": "https://api.github.com/users/hub4j/repos", + "events_url": "https://api.github.com/users/hub4j/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/hub4j/github-api", + "description": "Java API for GitHub", + "fork": false, + "url": "https://api.github.com/repos/hub4j/github-api", + "forks_url": "https://api.github.com/repos/hub4j/github-api/forks", + "keys_url": "https://api.github.com/repos/hub4j/github-api/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j/github-api/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j/github-api/teams", + "hooks_url": "https://api.github.com/repos/hub4j/github-api/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j/github-api/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j/github-api/events", + "assignees_url": "https://api.github.com/repos/hub4j/github-api/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j/github-api/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j/github-api/tags", + "blobs_url": "https://api.github.com/repos/hub4j/github-api/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j/github-api/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j/github-api/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j/github-api/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j/github-api/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j/github-api/languages", + "stargazers_url": "https://api.github.com/repos/hub4j/github-api/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j/github-api/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j/github-api/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j/github-api/subscription", + "commits_url": "https://api.github.com/repos/hub4j/github-api/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j/github-api/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j/github-api/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j/github-api/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j/github-api/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j/github-api/merges", + "archive_url": "https://api.github.com/repos/hub4j/github-api/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j/github-api/downloads", + "issues_url": "https://api.github.com/repos/hub4j/github-api/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j/github-api/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j/github-api/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j/github-api/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j/github-api/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j/github-api/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j/github-api/deployments", + "created_at": "2010-04-19T04:13:03Z", + "updated_at": "2020-05-20T22:54:46Z", + "pushed_at": "2020-05-20T20:24:04Z", + "git_url": "git://github.com/hub4j/github-api.git", + "ssh_url": "git@github.com:hub4j/github-api.git", + "clone_url": "https://github.com/hub4j/github-api.git", + "svn_url": "https://github.com/hub4j/github-api", + "homepage": "https://github-api.kohsuke.org/", + "size": 23100, + "stargazers_count": 656, + "watchers_count": 656, + "language": "Java", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 478, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 67, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "forks": 478, + "open_issues": 67, + "watchers": 656, + "default_branch": "main" + }, + "network_count": 478, + "subscribers_count": 0 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/__files/4-r_h_g_git_refs_heads.json b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/__files/4-r_h_g_git_refs_heads.json new file mode 100644 index 0000000000..87cfc4734a --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/__files/4-r_h_g_git_refs_heads.json @@ -0,0 +1,102 @@ +[ + { + "ref": "refs/heads/changes", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvY2hhbmdlcw==", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/changes", + "object": { + "sha": "1393706f1364742defbc28ba459082630ca979af", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/1393706f1364742defbc28ba459082630ca979af" + } + }, + { + "ref": "refs/heads/gh-pages", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvZ2gtcGFnZXM=", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/gh-pages", + "object": { + "sha": "4e64a0f9c3d561ab8587d2f7b03074b8745b5943", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/4e64a0f9c3d561ab8587d2f7b03074b8745b5943" + } + }, + { + "ref": "refs/heads/main", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvbWFzdGVy", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/main", + "object": { + "sha": "8051615eff597f4e49f4f47625e6fc2b49f26bfc", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/8051615eff597f4e49f4f47625e6fc2b49f26bfc" + } + }, + { + "ref": "refs/heads/test/#UrlEncode", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvdGVzdC8jVXJsRW5jb2Rl", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/test/%23UrlEncode", + "object": { + "sha": "14fa3698221f91613b9e1d809434326e5ed546af", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/14fa3698221f91613b9e1d809434326e5ed546af" + } + }, + { + "ref": "refs/heads/test/mergeable_branch", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvdGVzdC9tZXJnZWFibGVfYnJhbmNo", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/test/mergeable_branch", + "object": { + "sha": "b036909fcf45565c82c888ee326ebd0e382f6173", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/b036909fcf45565c82c888ee326ebd0e382f6173" + } + }, + { + "ref": "refs/heads/test/rc", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvdGVzdC9yYw==", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/test/rc", + "object": { + "sha": "14fa3698221f91613b9e1d809434326e5ed546af", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/14fa3698221f91613b9e1d809434326e5ed546af" + } + }, + { + "ref": "refs/heads/test/squashMerge", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvdGVzdC9zcXVhc2hNZXJnZQ==", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/test/squashMerge", + "object": { + "sha": "80902706c65747f9d8a7dbf82c451658efe2516d", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/80902706c65747f9d8a7dbf82c451658efe2516d" + } + }, + { + "ref": "refs/heads/test/stable", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvdGVzdC9zdGFibGU=", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/test/stable", + "object": { + "sha": "1f40fdf4acf1adb246c109c21a22f617e4bd1ca8", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/1f40fdf4acf1adb246c109c21a22f617e4bd1ca8" + } + }, + { + "ref": "refs/heads/test/unmergeable", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvdGVzdC91bm1lcmdlYWJsZQ==", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/test/unmergeable", + "object": { + "sha": "d4080cf9e2fa0959966d201f3dd60105fdf5bd97", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/d4080cf9e2fa0959966d201f3dd60105fdf5bd97" + } + }, + { + "ref": "refs/heads/test/updateContentSquashMerge", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvdGVzdC91cGRhdGVDb250ZW50U3F1YXNoTWVyZ2U=", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/test/updateContentSquashMerge", + "object": { + "sha": "36526be0a94e2d315c30379c7a561b1f7125a35f", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/36526be0a94e2d315c30379c7a561b1f7125a35f" + } + } +] \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/__files/5-r_h_g_git_refs_heads.json b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/__files/5-r_h_g_git_refs_heads.json new file mode 100644 index 0000000000..87cfc4734a --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/__files/5-r_h_g_git_refs_heads.json @@ -0,0 +1,102 @@ +[ + { + "ref": "refs/heads/changes", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvY2hhbmdlcw==", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/changes", + "object": { + "sha": "1393706f1364742defbc28ba459082630ca979af", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/1393706f1364742defbc28ba459082630ca979af" + } + }, + { + "ref": "refs/heads/gh-pages", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvZ2gtcGFnZXM=", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/gh-pages", + "object": { + "sha": "4e64a0f9c3d561ab8587d2f7b03074b8745b5943", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/4e64a0f9c3d561ab8587d2f7b03074b8745b5943" + } + }, + { + "ref": "refs/heads/main", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvbWFzdGVy", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/main", + "object": { + "sha": "8051615eff597f4e49f4f47625e6fc2b49f26bfc", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/8051615eff597f4e49f4f47625e6fc2b49f26bfc" + } + }, + { + "ref": "refs/heads/test/#UrlEncode", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvdGVzdC8jVXJsRW5jb2Rl", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/test/%23UrlEncode", + "object": { + "sha": "14fa3698221f91613b9e1d809434326e5ed546af", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/14fa3698221f91613b9e1d809434326e5ed546af" + } + }, + { + "ref": "refs/heads/test/mergeable_branch", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvdGVzdC9tZXJnZWFibGVfYnJhbmNo", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/test/mergeable_branch", + "object": { + "sha": "b036909fcf45565c82c888ee326ebd0e382f6173", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/b036909fcf45565c82c888ee326ebd0e382f6173" + } + }, + { + "ref": "refs/heads/test/rc", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvdGVzdC9yYw==", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/test/rc", + "object": { + "sha": "14fa3698221f91613b9e1d809434326e5ed546af", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/14fa3698221f91613b9e1d809434326e5ed546af" + } + }, + { + "ref": "refs/heads/test/squashMerge", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvdGVzdC9zcXVhc2hNZXJnZQ==", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/test/squashMerge", + "object": { + "sha": "80902706c65747f9d8a7dbf82c451658efe2516d", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/80902706c65747f9d8a7dbf82c451658efe2516d" + } + }, + { + "ref": "refs/heads/test/stable", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvdGVzdC9zdGFibGU=", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/test/stable", + "object": { + "sha": "1f40fdf4acf1adb246c109c21a22f617e4bd1ca8", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/1f40fdf4acf1adb246c109c21a22f617e4bd1ca8" + } + }, + { + "ref": "refs/heads/test/unmergeable", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvdGVzdC91bm1lcmdlYWJsZQ==", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/test/unmergeable", + "object": { + "sha": "d4080cf9e2fa0959966d201f3dd60105fdf5bd97", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/d4080cf9e2fa0959966d201f3dd60105fdf5bd97" + } + }, + { + "ref": "refs/heads/test/updateContentSquashMerge", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvdGVzdC91cGRhdGVDb250ZW50U3F1YXNoTWVyZ2U=", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/test/updateContentSquashMerge", + "object": { + "sha": "36526be0a94e2d315c30379c7a561b1f7125a35f", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/36526be0a94e2d315c30379c7a561b1f7125a35f" + } + } +] \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/__files/7-r_h_g_git_refs_heads_gh.json b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/__files/7-r_h_g_git_refs_heads_gh.json new file mode 100644 index 0000000000..a63324fb61 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/__files/7-r_h_g_git_refs_heads_gh.json @@ -0,0 +1,12 @@ +[ + { + "ref": "refs/heads/gh-pages", + "node_id": "MDM6UmVmMjA2ODg4MjAxOnJlZnMvaGVhZHMvZ2gtcGFnZXM=", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/refs/heads/gh-pages", + "object": { + "sha": "4e64a0f9c3d561ab8587d2f7b03074b8745b5943", + "type": "commit", + "url": "https://api.github.com/repos/hub4j-test-org/github-api/git/commits/4e64a0f9c3d561ab8587d2f7b03074b8745b5943" + } + } +] \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/mappings/1-user.json b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/mappings/1-user.json new file mode 100644 index 0000000000..0b94fe45e9 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/mappings/1-user.json @@ -0,0 +1,13 @@ +{ + "id": "1a78e64a-129b-473f-bdd1-f7f5471cc2a8", + "name": "user", + "request": { + "url": "/user", + "method": "GET" + }, + "response": { + "status": 200, + "bodyFileName": "1-user.json" + }, + "persistent": true +} diff --git a/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/mappings/2-orgs_hub4j-test-org.json b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/mappings/2-orgs_hub4j-test-org.json new file mode 100644 index 0000000000..87109f09c4 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/mappings/2-orgs_hub4j-test-org.json @@ -0,0 +1,42 @@ +{ + "id": "e7d412e7-52ee-4820-9a7b-f03438e032fb", + "name": "orgs_hub4j-test-org", + "request": { + "url": "/orgs/hub4j-test-org", + "method": "GET" + }, + "response": { + "status": 200, + "bodyFileName": "2-orgs_hub4j-test-org.json", + "headers": { + "Date": "Thu, 21 May 2020 00:01:19 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4927", + "X-RateLimit-Reset": "1590020149", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding, Accept, X-Requested-With", + "Accept-Encoding" + ], + "ETag": "W/\"de5da0827fbd925dd0dde9d2d6a85f45\"", + "Last-Modified": "Fri, 15 May 2020 15:14:14 GMT", + "X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion", + "X-Accepted-OAuth-Scopes": "admin:org, read:org, repo, user, write:org", + "X-GitHub-Media-Type": "unknown, github.v3", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "F020:533D:1B062:214B3:5EC5C4CE" + } + }, + "uuid": "e7d412e7-52ee-4820-9a7b-f03438e032fb", + "persistent": true, + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/mappings/3-r_h_github-api.json b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/mappings/3-r_h_github-api.json new file mode 100644 index 0000000000..ee46968581 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/mappings/3-r_h_github-api.json @@ -0,0 +1,47 @@ +{ + "id": "2566ff9b-e3b9-4819-9103-74a9c8d67bc0", + "name": "repos_hub4j-test-org_github-api", + "request": { + "url": "/repos/hub4j-test-org/github-api", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "3-r_h_github-api.json", + "headers": { + "Date": "Thu, 21 May 2020 00:01:19 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4926", + "X-RateLimit-Reset": "1590020148", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding, Accept, X-Requested-With", + "Accept-Encoding" + ], + "ETag": "W/\"312e4c361c7730b8cbae6f074a82248e\"", + "Last-Modified": "Thu, 16 Jan 2020 21:22:56 GMT", + "X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion", + "X-Accepted-OAuth-Scopes": "repo", + "X-GitHub-Media-Type": "unknown, github.v3", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "F020:533D:1B065:214B6:5EC5C4CF" + } + }, + "uuid": "2566ff9b-e3b9-4819-9103-74a9c8d67bc0", + "persistent": true, + "insertionIndex": 3 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/mappings/4-r_h_g_git_matching-refs_heads.json b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/mappings/4-r_h_g_git_matching-refs_heads.json new file mode 100644 index 0000000000..945c594089 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/mappings/4-r_h_g_git_matching-refs_heads.json @@ -0,0 +1,13 @@ +{ + "id": "4a78e64a-129b-473f-bdd1-f7f5471cc2a8", + "name": "repos_hub4j-test-org_github-api_git_matching-refs_heads", + "request": { + "url": "/repos/hub4j-test-org/github-api/git/matching-refs/heads", + "method": "GET" + }, + "response": { + "status": 200, + "bodyFileName": "4-r_h_g_git_refs_heads.json" + }, + "persistent": true +} diff --git a/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/mappings/5-r_h_g_git_matching-refs_refs_heads.json b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/mappings/5-r_h_g_git_matching-refs_refs_heads.json new file mode 100644 index 0000000000..8a284e2db0 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/mappings/5-r_h_g_git_matching-refs_refs_heads.json @@ -0,0 +1,13 @@ +{ + "id": "5a78e64a-129b-473f-bdd1-f7f5471cc2a8", + "name": "repos_hub4j-test-org_github-api_git_matching-refs_refs_heads", + "request": { + "url": "/repos/hub4j-test-org/github-api/git/matching-refs/refs/heads", + "method": "GET" + }, + "response": { + "status": 200, + "bodyFileName": "5-r_h_g_git_refs_heads.json" + }, + "persistent": true +} diff --git a/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/mappings/6-r_h_g_git_matching-refs_heads_gh.json b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/mappings/6-r_h_g_git_matching-refs_heads_gh.json new file mode 100644 index 0000000000..135dc7f6c5 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/listMatchingRefs/mappings/6-r_h_g_git_matching-refs_heads_gh.json @@ -0,0 +1,13 @@ +{ + "id": "6a78e64a-129b-473f-bdd1-f7f5471cc2a8", + "name": "repos_hub4j-test-org_github-api_git_matching-refs_heads_gh", + "request": { + "url": "/repos/hub4j-test-org/github-api/git/matching-refs/heads/gh", + "method": "GET" + }, + "response": { + "status": 200, + "bodyFileName": "7-r_h_g_git_refs_heads_gh.json" + }, + "persistent": true +} diff --git a/src/test/resources/org/kohsuke/github/GHSBOMTest/wiremock/getSBOM/__files/1-user.json b/src/test/resources/org/kohsuke/github/GHSBOMTest/wiremock/getSBOM/__files/1-user.json new file mode 100644 index 0000000000..fbc5eae788 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHSBOMTest/wiremock/getSBOM/__files/1-user.json @@ -0,0 +1,36 @@ +{ + "login": "Anonycoders", + "id": 40047636, + "node_id": "MDQ6VXNlcjQwMDQ3NjM2", + "avatar_url": "https://avatars.githubusercontent.com/u/40047636?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Anonycoders", + "html_url": "https://github.com/Anonycoders", + "followers_url": "https://api.github.com/users/Anonycoders/followers", + "following_url": "https://api.github.com/users/Anonycoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Anonycoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Anonycoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Anonycoders/subscriptions", + "organizations_url": "https://api.github.com/users/Anonycoders/orgs", + "repos_url": "https://api.github.com/users/Anonycoders/repos", + "events_url": "https://api.github.com/users/Anonycoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Anonycoders/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false, + "name": "Sorena Sarabadani", + "company": "@Adevinta", + "blog": "", + "location": "Berlin, Germany", + "email": "sorena.sarabadani@gmail.com", + "hireable": null, + "bio": "Ex-Shopifyer - Adevinta/Kleinanzeigen", + "twitter_username": "sorena_s", + "notification_email": "sorena.sarabadani@gmail.com", + "public_repos": 12, + "public_gists": 0, + "followers": 38, + "following": 4, + "created_at": "2018-06-08T02:07:15Z", + "updated_at": "2026-01-24T22:07:12Z" +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHSBOMTest/wiremock/getSBOM/__files/2-r_h_github-api.json b/src/test/resources/org/kohsuke/github/GHSBOMTest/wiremock/getSBOM/__files/2-r_h_github-api.json new file mode 100644 index 0000000000..c58b26f123 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHSBOMTest/wiremock/getSBOM/__files/2-r_h_github-api.json @@ -0,0 +1,147 @@ +{ + "id": 617210, + "node_id": "MDEwOlJlcG9zaXRvcnk2MTcyMTA=", + "name": "github-api", + "full_name": "hub4j/github-api", + "private": false, + "owner": { + "login": "hub4j", + "id": 54909825, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU0OTA5ODI1", + "avatar_url": "https://avatars.githubusercontent.com/u/54909825?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j", + "html_url": "https://github.com/hub4j", + "followers_url": "https://api.github.com/users/hub4j/followers", + "following_url": "https://api.github.com/users/hub4j/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j/orgs", + "repos_url": "https://api.github.com/users/hub4j/repos", + "events_url": "https://api.github.com/users/hub4j/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/hub4j/github-api", + "description": "Java API for GitHub", + "fork": false, + "url": "https://api.github.com/repos/hub4j/github-api", + "forks_url": "https://api.github.com/repos/hub4j/github-api/forks", + "keys_url": "https://api.github.com/repos/hub4j/github-api/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j/github-api/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j/github-api/teams", + "hooks_url": "https://api.github.com/repos/hub4j/github-api/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j/github-api/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j/github-api/events", + "assignees_url": "https://api.github.com/repos/hub4j/github-api/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j/github-api/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j/github-api/tags", + "blobs_url": "https://api.github.com/repos/hub4j/github-api/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j/github-api/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j/github-api/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j/github-api/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j/github-api/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j/github-api/languages", + "stargazers_url": "https://api.github.com/repos/hub4j/github-api/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j/github-api/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j/github-api/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j/github-api/subscription", + "commits_url": "https://api.github.com/repos/hub4j/github-api/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j/github-api/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j/github-api/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j/github-api/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j/github-api/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j/github-api/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j/github-api/merges", + "archive_url": "https://api.github.com/repos/hub4j/github-api/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j/github-api/downloads", + "issues_url": "https://api.github.com/repos/hub4j/github-api/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j/github-api/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j/github-api/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j/github-api/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j/github-api/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j/github-api/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j/github-api/deployments", + "created_at": "2010-04-19T04:13:03Z", + "updated_at": "2026-01-25T03:20:40Z", + "pushed_at": "2026-01-25T03:20:35Z", + "git_url": "git://github.com/hub4j/github-api.git", + "ssh_url": "git@github.com:hub4j/github-api.git", + "clone_url": "https://github.com/hub4j/github-api.git", + "svn_url": "https://github.com/hub4j/github-api", + "homepage": "https://hub4j.github.io/github-api/", + "size": 66459, + "stargazers_count": 1230, + "watchers_count": 1230, + "language": "Java", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "has_discussions": true, + "forks_count": 769, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 181, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "api", + "client-library", + "github", + "github-api", + "github-api-v3", + "java", + "java-api" + ], + "visibility": "public", + "forks": 769, + "open_issues": 181, + "watchers": 1230, + "default_branch": "main", + "permissions": { + "admin": false, + "maintain": false, + "push": false, + "triage": false, + "pull": true + }, + "temp_clone_token": "", + "custom_properties": {}, + "organization": { + "login": "hub4j", + "id": 54909825, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU0OTA5ODI1", + "avatar_url": "https://avatars.githubusercontent.com/u/54909825?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j", + "html_url": "https://github.com/hub4j", + "followers_url": "https://api.github.com/users/hub4j/followers", + "following_url": "https://api.github.com/users/hub4j/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j/orgs", + "repos_url": "https://api.github.com/users/hub4j/repos", + "events_url": "https://api.github.com/users/hub4j/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "network_count": 769, + "subscribers_count": 40 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHSBOMTest/wiremock/getSBOM/__files/3-r_h_g_dependency-graph_sbom.json b/src/test/resources/org/kohsuke/github/GHSBOMTest/wiremock/getSBOM/__files/3-r_h_g_dependency-graph_sbom.json new file mode 100644 index 0000000000..f6ab5b0560 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHSBOMTest/wiremock/getSBOM/__files/3-r_h_g_dependency-graph_sbom.json @@ -0,0 +1,1473 @@ +{ + "sbom": { + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "com.github.hub4j/github-api", + "documentNamespace": "https://spdx.org/spdxdocs/protobom/44697dda-5a9f-4bd4-a619-786180fb7843", + "comment": "Exact versions could not be resolved for some packages. For more information: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-the-dependency-graph#dependencies-included.", + "creationInfo": { + "creators": [ + "Tool: protobom-v0.0.0-20260121122932-f5d50261f216+dirty", + "Tool: GitHub.com-Dependency-Graph" + ], + "created": "2026-01-25T20:41:51Z" + }, + "packages": [ + { + "name": "repo-sync/pull-request", + "SPDXID": "SPDXRef-githubactions-repo-sync-pull-request-2..-75c946", + "versionInfo": "2.*.*", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:githubactions/repo-sync/pull-request@2.%2A.%2A" + } + ] + }, + { + "name": "stefanzweifel/git-auto-commit-action", + "SPDXID": "SPDXRef-githubactions-stefanzweifel-git-auto-commit-action-7..-75c946", + "versionInfo": "7.*.*", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:githubactions/stefanzweifel/git-auto-commit-action@7.%2A.%2A" + } + ] + }, + { + "name": "actions/download-artifact", + "SPDXID": "SPDXRef-githubactions-actions-download-artifact-7..-75c946", + "versionInfo": "7.*.*", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:githubactions/actions/download-artifact@7.%2A.%2A" + } + ] + }, + { + "name": "actions/upload-artifact", + "SPDXID": "SPDXRef-githubactions-actions-upload-artifact-6..-75c946", + "versionInfo": "6.*.*", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:githubactions/actions/upload-artifact@6.%2A.%2A" + } + ] + }, + { + "name": "codecov/codecov-action", + "SPDXID": "SPDXRef-githubactions-codecov-codecov-action-5.5.2-75c946", + "versionInfo": "5.5.2", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:githubactions/codecov/codecov-action@5.5.2" + } + ] + }, + { + "name": "release-drafter/release-drafter", + "SPDXID": "SPDXRef-githubactions-release-drafter-release-drafter-6..-75c946", + "versionInfo": "6.*.*", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:githubactions/release-drafter/release-drafter@6.%2A.%2A" + } + ] + }, + { + "name": "org.hamcrest:hamcrest-library", + "SPDXID": "SPDXRef-maven-org.hamcrest-hamcrest-library-75c946", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.hamcrest/hamcrest-library" + } + ] + }, + { + "name": "org.apache.maven.plugins:maven-release-plugin", + "SPDXID": "SPDXRef-maven-org.apache.maven.plugins-maven-release-plugin-3.1.1-75c946", + "versionInfo": "3.1.1", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "copyrightText": "Copyright 2002-2024 The Apache Software Foundation", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.maven.plugins/maven-release-plugin@3.1.1" + } + ] + }, + { + "name": "org.junit:junit-bom", + "SPDXID": "SPDXRef-maven-org.junit-junit-bom-5.13.4-75c946", + "versionInfo": "5.13.4", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.junit/junit-bom@5.13.4" + } + ] + }, + { + "name": "com.diffplug.spotless:spotless-maven-plugin", + "SPDXID": "SPDXRef-maven-com.diffplug.spotless-spotless-maven-plugin-2.46.1-75c946", + "versionInfo": "2.46.1", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.diffplug.spotless/spotless-maven-plugin@2.46.1" + } + ] + }, + { + "name": "org.apache.maven.plugins:maven-jar-plugin", + "SPDXID": "SPDXRef-maven-org.apache.maven.plugins-maven-jar-plugin-3.4.2-75c946", + "versionInfo": "3.4.2", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "copyrightText": "Copyright 2002-2024 The Apache Software Foundation", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.maven.plugins/maven-jar-plugin@3.4.2" + } + ] + }, + { + "name": "com.github.spotbugs:spotbugs-annotations", + "SPDXID": "SPDXRef-maven-com.github.spotbugs-spotbugs-annotations-4.8.6-75c946", + "versionInfo": "4.8.6", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "LGPL-2.1", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.github.spotbugs/spotbugs-annotations@4.8.6" + } + ] + }, + { + "name": "org.apache.maven.plugins:maven-project-info-reports-plugin", + "SPDXID": "SPDXRef-maven-org.apache.maven.plugins-maven-project-info-reports-plugin-3.9.0-75c946", + "versionInfo": "3.9.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0 AND MIT", + "copyrightText": "Copyright 2005-2025 The Apache Software Foundation", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.maven.plugins/maven-project-info-reports-plugin@3.9.0" + } + ] + }, + { + "name": "org.hamcrest:hamcrest-core", + "SPDXID": "SPDXRef-maven-org.hamcrest-hamcrest-core-75c946", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.hamcrest/hamcrest-core" + } + ] + }, + { + "name": "org.awaitility:awaitility", + "SPDXID": "SPDXRef-maven-org.awaitility-awaitility-4.3.0-75c946", + "versionInfo": "4.3.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "copyrightText": "Copyright 2015 the original author or authors", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.awaitility/awaitility@4.3.0" + } + ] + }, + { + "name": "org.slf4j:slf4j-bom", + "SPDXID": "SPDXRef-maven-org.slf4j-slf4j-bom-2.0.17-75c946", + "versionInfo": "2.0.17", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.slf4j/slf4j-bom@2.0.17" + } + ] + }, + { + "name": "com.tngtech.archunit:archunit", + "SPDXID": "SPDXRef-maven-com.tngtech.archunit-archunit-1.4.1-75c946", + "versionInfo": "1.4.1", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "copyrightText": "Copyright (c) 2000-2011 INRIA, France Telecom, Copyright 2025 TNG Technology Consulting GmbH", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.tngtech.archunit/archunit@1.4.1" + } + ] + }, + { + "name": "com.squareup.okio:okio", + "SPDXID": "SPDXRef-maven-com.squareup.okio-okio-3.16.0-75c946", + "versionInfo": "3.16.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.squareup.okio/okio@3.16.0" + } + ] + }, + { + "name": "io.jsonwebtoken:maven-surefire-plugin", + "SPDXID": "SPDXRef-maven-io.jsonwebtoken-maven-surefire-plugin-0.11.5-75c946", + "versionInfo": "0.11.5", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/io.jsonwebtoken/maven-surefire-plugin@0.11.5" + } + ] + }, + { + "name": "com.github.tomakehurst:wiremock-jre8-standalone", + "SPDXID": "SPDXRef-maven-com.github.tomakehurst-wiremock-jre8-standalone-2.35.2-75c946", + "versionInfo": "2.35.2", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0 AND EPL-1.0 AND EPL-2.0", + "copyrightText": "(c) 2021 Denis Pushkarev, (c) OpenJS Foundation and other contributors, Copyright (c) 1997-2010 Oracle and/or its affiliates, Copyright (c) 1997-2013 Oracle and/or its affiliates, Copyright (c) 2008-2010 Oracle and/or its affiliates, Copyright (c) 2009-2010 Oracle and/or its affiliates, Copyright 1995-2018 Mort Bay Consulting Pty Ltd., Copyright 1996 Aki Yoshida, Copyright 2004 The Apache Software Foundation, Copyright Mort Bay Consulting Pty Ltd", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.github.tomakehurst/wiremock-jre8-standalone@2.35.2" + } + ] + }, + { + "name": "org.hamcrest:hamcrest", + "SPDXID": "SPDXRef-maven-org.hamcrest-hamcrest-3.0-75c946", + "versionInfo": "3.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "BSD-3-Clause", + "copyrightText": "Copyright (c) 2000-2024, www.hamcrest.org", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.hamcrest/hamcrest@3.0" + } + ] + }, + { + "name": "org.jacoco:jacoco-maven-plugin", + "SPDXID": "SPDXRef-maven-org.jacoco-jacoco-maven-plugin-75c946", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.jacoco/jacoco-maven-plugin" + } + ] + }, + { + "name": "org.jenkins-ci:maven-compiler-plugin", + "SPDXID": "SPDXRef-maven-org.jenkins-ci-maven-compiler-plugin-3.14.0-75c946", + "versionInfo": "3.14.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.jenkins-ci/maven-compiler-plugin@3.14.0" + } + ] + }, + { + "name": "org.sonatype.plugins:nexus-staging-maven-plugin", + "SPDXID": "SPDXRef-maven-org.sonatype.plugins-nexus-staging-maven-plugin-1.7.0-75c946", + "versionInfo": "1.7.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "copyrightText": "Copyright (c) 2007-2015 Sonatype, Inc., Copyright (c) 2008-present Sonatype, Inc.", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.sonatype.plugins/nexus-staging-maven-plugin@1.7.0" + } + ] + }, + { + "name": "com.google.guava:guava", + "SPDXID": "SPDXRef-maven-com.google.guava-guava-33.4.6-jre-75c946", + "versionInfo": "33.4.6-jre", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.google.guava/guava@33.4.6-jre" + } + ] + }, + { + "name": "org.mockito:mockito-core", + "SPDXID": "SPDXRef-maven-org.mockito-mockito-core-5.21.0-75c946", + "versionInfo": "5.21.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "MIT", + "copyrightText": "Copyright (c) 2007 Mockito contributors", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.mockito/mockito-core@5.21.0" + } + ] + }, + { + "name": "com.infradna.tool:bridge-method-injector", + "SPDXID": "SPDXRef-maven-com.infradna.tool-bridge-method-injector-75c946", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.infradna.tool/bridge-method-injector" + } + ] + }, + { + "name": "org.kohsuke:wordnet-random-name", + "SPDXID": "SPDXRef-maven-org.kohsuke-wordnet-random-name-1.6-75c946", + "versionInfo": "1.6", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "MIT", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.kohsuke/wordnet-random-name@1.6" + } + ] + }, + { + "name": "io.jsonwebtoken:jjwt-jackson", + "SPDXID": "SPDXRef-maven-io.jsonwebtoken-jjwt-jackson-0.13.0-75c946", + "versionInfo": "0.13.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "copyrightText": "Copyright 2018 JWTK", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/io.jsonwebtoken/jjwt-jackson@0.13.0" + } + ] + }, + { + "name": "org.hamcrest:hamcrest", + "SPDXID": "SPDXRef-maven-org.hamcrest-hamcrest-75c946", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.hamcrest/hamcrest" + } + ] + }, + { + "name": "com.github.ekryd.sortpom:sortpom-maven-plugin", + "SPDXID": "SPDXRef-maven-com.github.ekryd.sortpom-sortpom-maven-plugin-4.0.0-75c946", + "versionInfo": "4.0.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.github.ekryd.sortpom/sortpom-maven-plugin@4.0.0" + } + ] + }, + { + "name": "com.fasterxml.jackson:jackson-bom", + "SPDXID": "SPDXRef-maven-com.fasterxml.jackson-jackson-bom-2.21.0-75c946", + "versionInfo": "2.21.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.fasterxml.jackson/jackson-bom@2.21.0" + } + ] + }, + { + "name": "io.jsonwebtoken:jjwt-api", + "SPDXID": "SPDXRef-maven-io.jsonwebtoken-jjwt-api-0.13.0-75c946", + "versionInfo": "0.13.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "copyrightText": "Copyright 2018 JWTK", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/io.jsonwebtoken/jjwt-api@0.13.0" + } + ] + }, + { + "name": "com.google.code.gson:gson", + "SPDXID": "SPDXRef-maven-com.google.code.gson-gson-2.13.2-75c946", + "versionInfo": "2.13.2", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "copyrightText": "Copyright 2008 Google LLC", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.google.code.gson/gson@2.13.2" + } + ] + }, + { + "name": "com.infradna.tool:bridge-method-injector", + "SPDXID": "SPDXRef-maven-com.infradna.tool-bridge-method-injector-1.31-75c946", + "versionInfo": "1.31", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.infradna.tool/bridge-method-injector@1.31" + } + ] + }, + { + "name": "com.infradna.tool:bridge-method-annotation", + "SPDXID": "SPDXRef-maven-com.infradna.tool-bridge-method-annotation-1.31-75c946", + "versionInfo": "1.31", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.infradna.tool/bridge-method-annotation@1.31" + } + ] + }, + { + "name": "io.jsonwebtoken:jjwt-impl", + "SPDXID": "SPDXRef-maven-io.jsonwebtoken-jjwt-impl-0.13.0-75c946", + "versionInfo": "0.13.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "copyrightText": "Copyright 2018 JWTK", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/io.jsonwebtoken/jjwt-impl@0.13.0" + } + ] + }, + { + "name": "com.fasterxml.jackson.core:jackson-databind", + "SPDXID": "SPDXRef-maven-com.fasterxml.jackson.core-jackson-databind-75c946", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.fasterxml.jackson.core/jackson-databind" + } + ] + }, + { + "name": "com.diffplug.spotless:spotless-maven-plugin", + "SPDXID": "SPDXRef-maven-com.diffplug.spotless-spotless-maven-plugin-75c946", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.diffplug.spotless/spotless-maven-plugin" + } + ] + }, + { + "name": "org.apache.maven.plugins:maven-project-info-reports-plugin", + "SPDXID": "SPDXRef-maven-org.apache.maven.plugins-maven-project-info-reports-plugin-75c946", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.maven.plugins/maven-project-info-reports-plugin" + } + ] + }, + { + "name": "com.github.spotbugs:spotbugs", + "SPDXID": "SPDXRef-maven-com.github.spotbugs-spotbugs-4.8.6-75c946", + "versionInfo": "4.8.6", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "LGPL-2.1", + "copyrightText": "(c) . Calling, Copyright (c) 1991, 1999 Free Software Foundation, Inc., Copyright (c) 2003-2008 University of Maryland and others, Copyright (c) 2004,2005 University of Maryland, Copyright (c) 2005, 2006 Etienne Giraudy, InStranet Inc, Copyright (c) 2005, 2007 Etienne Giraudy, Copyright (c) 2005, Chris Nappin, Copyright (c) 2015, 2017, Brahim Djoudi, Copyright (c) 2019 Bjorn Kautler, copyrighted by the Free Software Foundation", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.github.spotbugs/spotbugs@4.8.6" + } + ] + }, + { + "name": "org.apache.maven.plugins:maven-site-plugin", + "SPDXID": "SPDXRef-maven-org.apache.maven.plugins-maven-site-plugin-3.21.0-75c946", + "versionInfo": "3.21.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause AND EPL-1.0 AND EPL-2.0 AND LicenseRef-scancode-public-domain AND MIT", + "copyrightText": "Copyright 2002-2024 The Apache Software Foundation", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.maven.plugins/maven-site-plugin@3.21.0" + } + ] + }, + { + "name": "org.sonatype.plugins:nexus-staging-maven-plugin", + "SPDXID": "SPDXRef-maven-org.sonatype.plugins-nexus-staging-maven-plugin-75c946", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.sonatype.plugins/nexus-staging-maven-plugin" + } + ] + }, + { + "name": "org.hamcrest:hamcrest-library", + "SPDXID": "SPDXRef-maven-org.hamcrest-hamcrest-library-3.0-75c946", + "versionInfo": "3.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "BSD-3-Clause", + "copyrightText": "Copyright (c) 2000-2024, www.hamcrest.org", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.hamcrest/hamcrest-library@3.0" + } + ] + }, + { + "name": "org.springframework.boot:spring-boot-starter-test", + "SPDXID": "SPDXRef-maven-org.springframework.boot-spring-boot-starter-test-75c946", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.springframework.boot/spring-boot-starter-test" + } + ] + }, + { + "name": "org.apache.bcel:bcel", + "SPDXID": "SPDXRef-maven-org.apache.bcel-bcel-6.10.0-75c946", + "versionInfo": "6.10.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "copyrightText": "Copyright 2004-2024 The Apache Software Foundation", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.bcel/bcel@6.10.0" + } + ] + }, + { + "name": "com.github.siom79.japicmp:japicmp-maven-plugin", + "SPDXID": "SPDXRef-maven-com.github.siom79.japicmp-japicmp-maven-plugin-0.23.1-75c946", + "versionInfo": "0.23.1", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.github.siom79.japicmp/japicmp-maven-plugin@0.23.1" + } + ] + }, + { + "name": "junit:junit", + "SPDXID": "SPDXRef-maven-junit-junit-75c946", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/junit/junit" + } + ] + }, + { + "name": "junit:junit", + "SPDXID": "SPDXRef-maven-junit-junit-4.13.2-75c946", + "versionInfo": "4.13.2", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "EPL-1.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/junit/junit@4.13.2" + } + ] + }, + { + "name": "org.junit.vintage:junit-vintage-engine", + "SPDXID": "SPDXRef-maven-org.junit.vintage-junit-vintage-engine-75c946", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.junit.vintage/junit-vintage-engine" + } + ] + }, + { + "name": "com.github.npathai:hamcrest-optional", + "SPDXID": "SPDXRef-maven-com.github.npathai-hamcrest-optional-2.0.0-75c946", + "versionInfo": "2.0.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "MIT", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.github.npathai/hamcrest-optional@2.0.0" + } + ] + }, + { + "name": "org.springframework.boot:spring-boot-dependencies", + "SPDXID": "SPDXRef-maven-org.springframework.boot-spring-boot-dependencies-3.4.5-75c946", + "versionInfo": "3.4.5", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.springframework.boot/spring-boot-dependencies@3.4.5" + } + ] + }, + { + "name": "org.apache.maven.plugins:maven-gpg-plugin", + "SPDXID": "SPDXRef-maven-org.apache.maven.plugins-maven-gpg-plugin-75c946", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.maven.plugins/maven-gpg-plugin" + } + ] + }, + { + "name": "org.apache.maven.plugins:maven-javadoc-plugin", + "SPDXID": "SPDXRef-maven-org.apache.maven.plugins-maven-javadoc-plugin-3.12.0-75c946", + "versionInfo": "3.12.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause AND LicenseRef-scancode-public-domain AND MIT", + "copyrightText": "Copyright 2004-2025 The Apache Software Foundation, Copyright 2005, a http://www.mycompany.com MyCompany, Inc. a Note:If the project", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.maven.plugins/maven-javadoc-plugin@3.12.0" + } + ] + }, + { + "name": "org.hamcrest:hamcrest-core", + "SPDXID": "SPDXRef-maven-org.hamcrest-hamcrest-core-3.0-75c946", + "versionInfo": "3.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "BSD-3-Clause", + "copyrightText": "Copyright (c) 2000-2024, www.hamcrest.org", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.hamcrest/hamcrest-core@3.0" + } + ] + }, + { + "name": "org.apache.maven.plugins:maven-gpg-plugin", + "SPDXID": "SPDXRef-maven-org.apache.maven.plugins-maven-gpg-plugin-3.2.7-75c946", + "versionInfo": "3.2.7", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "copyrightText": "Copyright 2002-2024 The Apache Software Foundation", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.maven.plugins/maven-gpg-plugin@3.2.7" + } + ] + }, + { + "name": "org.apache.maven.plugins:maven-help-plugin", + "SPDXID": "SPDXRef-maven-org.apache.maven.plugins-maven-help-plugin-3.5.1-75c946", + "versionInfo": "3.5.1", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0 AND BSD-3-Clause AND EPL-1.0 AND EPL-2.0 AND LicenseRef-scancode-jdom AND MIT AND SAX-PD AND xpp", + "copyrightText": "Copyright 2001-2024 The Apache Software Foundation", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.maven.plugins/maven-help-plugin@3.5.1" + } + ] + }, + { + "name": "org.codehaus.mojo:versions-maven-plugin", + "SPDXID": "SPDXRef-maven-org.codehaus.mojo-versions-maven-plugin-2.18.0-75c946", + "versionInfo": "2.18.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "copyrightText": "Copyright MojoHaus and Contributors", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.codehaus.mojo/versions-maven-plugin@2.18.0" + } + ] + }, + { + "name": "org.jacoco:jacoco-maven-plugin", + "SPDXID": "SPDXRef-maven-org.jacoco-jacoco-maven-plugin-0.8.13-75c946", + "versionInfo": "0.8.13", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "EPL-2.0", + "copyrightText": "Copyright (c) 2009, 2025 Mountainminds GmbH & Co. KG and Contributors", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.jacoco/jacoco-maven-plugin@0.8.13" + } + ] + }, + { + "name": "com.squareup.okhttp3:okhttp", + "SPDXID": "SPDXRef-maven-com.squareup.okhttp3-okhttp-4.12.0-75c946", + "versionInfo": "4.12.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.squareup.okhttp3/okhttp@4.12.0" + } + ] + }, + { + "name": "org.apache.maven.plugins:maven-enforcer-plugin", + "SPDXID": "SPDXRef-maven-org.apache.maven.plugins-maven-enforcer-plugin-3.5.0-75c946", + "versionInfo": "3.5.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "copyrightText": "Copyright 2007-2024 The Apache Software Foundation", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.maven.plugins/maven-enforcer-plugin@3.5.0" + } + ] + }, + { + "name": "commons-io:commons-io", + "SPDXID": "SPDXRef-maven-commons-io-commons-io-2.16.1-75c946", + "versionInfo": "2.16.1", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "copyrightText": "Copyright 2002-2024 The Apache Software Foundation", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/commons-io/commons-io@2.16.1" + } + ] + }, + { + "name": "org.springframework.boot:spring-boot-maven-plugin", + "SPDXID": "SPDXRef-maven-org.springframework.boot-spring-boot-maven-plugin-3.4.5-75c946", + "versionInfo": "3.4.5", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "copyrightText": "Copyright (c) 2012-2025 VMware, Inc.", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.springframework.boot/spring-boot-maven-plugin@3.4.5" + } + ] + }, + { + "name": "org.apache.commons:commons-lang3", + "SPDXID": "SPDXRef-maven-org.apache.commons-commons-lang3-3.19.0-75c946", + "versionInfo": "3.19.0", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "Apache-2.0", + "copyrightText": "Copyright 2001-2017 The Apache Software Foundation", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.commons/commons-lang3@3.19.0" + } + ] + }, + { + "name": "com.github.spotbugs:spotbugs-maven-plugin", + "SPDXID": "SPDXRef-maven-com.github.spotbugs-spotbugs-maven-plugin-4.9.8.2-75c946", + "versionInfo": "4.9.8.2", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.github.spotbugs/spotbugs-maven-plugin@4.9.8.2" + } + ] + }, + { + "name": "org.apache.maven.plugins:maven-javadoc-plugin", + "SPDXID": "SPDXRef-maven-org.apache.maven.plugins-maven-javadoc-plugin-75c946", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.maven.plugins/maven-javadoc-plugin" + } + ] + }, + { + "name": "com.fasterxml.jackson.datatype:jackson-datatype-jsr310", + "SPDXID": "SPDXRef-maven-com.fasterxml.jackson.datatype-jackson-datatype-jsr310-75c946", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310" + } + ] + }, + { + "name": "org.apache.maven.plugins:maven-source-plugin", + "SPDXID": "SPDXRef-maven-org.apache.maven.plugins-maven-source-plugin-75c946", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.maven.plugins/maven-source-plugin" + } + ] + }, + { + "name": "actions/checkout", + "SPDXID": "SPDXRef-githubactions-actions-checkout-6..-75c946", + "versionInfo": "6.*.*", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:githubactions/actions/checkout@6.%2A.%2A" + } + ] + }, + { + "name": "github/codeql-action/analyze", + "SPDXID": "SPDXRef-githubactions-githubcodeql-action-analyze-4..-75c946", + "versionInfo": "4.*.*", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:githubactions/github/codeql-action/analyze@4.%2A.%2A" + } + ] + }, + { + "name": "github/codeql-action/autobuild", + "SPDXID": "SPDXRef-githubactions-githubcodeql-action-autobuild-4..-75c946", + "versionInfo": "4.*.*", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:githubactions/github/codeql-action/autobuild@4.%2A.%2A" + } + ] + }, + { + "name": "github/codeql-action/init", + "SPDXID": "SPDXRef-githubactions-githubcodeql-action-init-4..-75c946", + "versionInfo": "4.*.*", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:githubactions/github/codeql-action/init@4.%2A.%2A" + } + ] + }, + { + "name": "actions/setup-java", + "SPDXID": "SPDXRef-githubactions-actions-setup-java-5..-75c946", + "versionInfo": "5.*.*", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:githubactions/actions/setup-java@5.%2A.%2A" + } + ] + }, + { + "name": "com.github.hub4j/github-api", + "SPDXID": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "versionInfo": "main", + "downloadLocation": "git+https://github.com/hub4j/github-api", + "filesAnalyzed": false, + "licenseDeclared": "MIT", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:github/hub4j/github-api@main" + } + ] + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-githubactions-release-drafter-release-drafter-6..-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-io.jsonwebtoken-jjwt-api-0.13.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.infradna.tool-bridge-method-annotation-1.31-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.github.spotbugs-spotbugs-4.8.6-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.apache.maven.plugins-maven-javadoc-plugin-3.12.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.springframework.boot-spring-boot-maven-plugin-3.4.5-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-githubactions-githubcodeql-action-autobuild-4..-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.infradna.tool-bridge-method-injector-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.github.ekryd.sortpom-sortpom-maven-plugin-4.0.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.apache.maven.plugins-maven-gpg-plugin-3.2.7-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.github.tomakehurst-wiremock-jre8-standalone-2.35.2-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.hamcrest-hamcrest-library-3.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.sonatype.plugins-nexus-staging-maven-plugin-1.7.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.google.guava-guava-33.4.6-jre-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.fasterxml.jackson.core-jackson-databind-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-junit-junit-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.slf4j-slf4j-bom-2.0.17-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.squareup.okio-okio-3.16.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-io.jsonwebtoken-maven-surefire-plugin-0.11.5-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.fasterxml.jackson-jackson-bom-2.21.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.codehaus.mojo-versions-maven-plugin-2.18.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.fasterxml.jackson.datatype-jackson-datatype-jsr310-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-githubactions-githubcodeql-action-init-4..-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-githubactions-repo-sync-pull-request-2..-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-githubactions-actions-download-artifact-7..-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.apache.maven.plugins-maven-release-plugin-3.1.1-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.jenkins-ci-maven-compiler-plugin-3.14.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.apache.maven.plugins-maven-javadoc-plugin-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-githubactions-actions-setup-java-5..-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.hamcrest-hamcrest-library-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.github.spotbugs-spotbugs-annotations-4.8.6-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.jacoco-jacoco-maven-plugin-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.kohsuke-wordnet-random-name-1.6-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.hamcrest-hamcrest-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-githubactions-codecov-codecov-action-5.5.2-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.awaitility-awaitility-4.3.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-io.jsonwebtoken-jjwt-impl-0.13.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.junit.vintage-junit-vintage-engine-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.apache.maven.plugins-maven-enforcer-plugin-3.5.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.apache.commons-commons-lang3-3.19.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-githubactions-actions-checkout-6..-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.apache.maven.plugins-maven-site-plugin-3.21.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.github.spotbugs-spotbugs-maven-plugin-4.9.8.2-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.apache.maven.plugins-maven-jar-plugin-3.4.2-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.hamcrest-hamcrest-3.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.google.code.gson-gson-2.13.2-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.apache.bcel-bcel-6.10.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.springframework.boot-spring-boot-dependencies-3.4.5-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-githubactions-stefanzweifel-git-auto-commit-action-7..-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.apache.maven.plugins-maven-project-info-reports-plugin-3.9.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.infradna.tool-bridge-method-injector-1.31-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.apache.maven.plugins-maven-project-info-reports-plugin-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.springframework.boot-spring-boot-starter-test-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.github.npathai-hamcrest-optional-2.0.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.apache.maven.plugins-maven-help-plugin-3.5.1-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.squareup.okhttp3-okhttp-4.12.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.hamcrest-hamcrest-core-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.sonatype.plugins-nexus-staging-maven-plugin-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-junit-junit-4.13.2-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.apache.maven.plugins-maven-gpg-plugin-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.hamcrest-hamcrest-core-3.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-commons-io-commons-io-2.16.1-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.github.siom79.japicmp-japicmp-maven-plugin-0.23.1-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.junit-junit-bom-5.13.4-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.diffplug.spotless-spotless-maven-plugin-2.46.1-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.mockito-mockito-core-5.21.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.diffplug.spotless-spotless-maven-plugin-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.apache.maven.plugins-maven-source-plugin-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-githubactions-githubcodeql-action-analyze-4..-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-githubactions-actions-upload-artifact-6..-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-com.tngtech.archunit-archunit-1.4.1-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-io.jsonwebtoken-jjwt-jackson-0.13.0-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relatedSpdxElement": "SPDXRef-maven-org.jacoco-jacoco-maven-plugin-0.8.13-75c946", + "relationshipType": "DEPENDS_ON" + }, + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-github-hub4j-github-api-main-a85d1d", + "relationshipType": "DESCRIBES" + } + ] + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHSBOMTest/wiremock/getSBOM/mappings/1-user.json b/src/test/resources/org/kohsuke/github/GHSBOMTest/wiremock/getSBOM/mappings/1-user.json new file mode 100644 index 0000000000..6557ca146b --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHSBOMTest/wiremock/getSBOM/mappings/1-user.json @@ -0,0 +1,48 @@ +{ + "id": "0ba42e6c-e026-4ca8-a520-4ada5db860ab", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "1-user.json", + "headers": { + "Date": "Sun, 25 Jan 2026 20:41:50 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"15d7e1ad92a3639b979fc55254902e63ee0bfa5c8f6766990bf989044d491ce1\"", + "Last-Modified": "Sat, 24 Jan 2026 22:07:12 GMT", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4967", + "X-RateLimit-Reset": "1769376437", + "X-RateLimit-Used": "33", + "X-RateLimit-Resource": "core", + "X-GitHub-Request-Id": "D811:46DF2:7814A84:68A21E7:6976800E" + } + }, + "uuid": "0ba42e6c-e026-4ca8-a520-4ada5db860ab", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHSBOMTest/wiremock/getSBOM/mappings/2-r_h_github-api.json b/src/test/resources/org/kohsuke/github/GHSBOMTest/wiremock/getSBOM/mappings/2-r_h_github-api.json new file mode 100644 index 0000000000..548fa41554 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHSBOMTest/wiremock/getSBOM/mappings/2-r_h_github-api.json @@ -0,0 +1,48 @@ +{ + "id": "42f56d16-61bc-426a-a09b-afffded9a024", + "name": "repos_hub4j_github-api", + "request": { + "url": "/repos/hub4j/github-api", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "2-r_h_github-api.json", + "headers": { + "Date": "Sun, 25 Jan 2026 20:41:51 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"9f0cbcc557c793f0d6cc5f0a5913e8d87c01403a8f95e3142373acf8e03059ab\"", + "Last-Modified": "Sun, 25 Jan 2026 03:20:40 GMT", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "repo", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4965", + "X-RateLimit-Reset": "1769376437", + "X-RateLimit-Used": "35", + "X-RateLimit-Resource": "core", + "X-GitHub-Request-Id": "D813:19034C:6982D26:5A5E281:6976800F" + } + }, + "uuid": "42f56d16-61bc-426a-a09b-afffded9a024", + "persistent": true, + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHSBOMTest/wiremock/getSBOM/mappings/3-r_h_g_dependency-graph_sbom.json b/src/test/resources/org/kohsuke/github/GHSBOMTest/wiremock/getSBOM/mappings/3-r_h_g_dependency-graph_sbom.json new file mode 100644 index 0000000000..5a9f430d58 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHSBOMTest/wiremock/getSBOM/mappings/3-r_h_g_dependency-graph_sbom.json @@ -0,0 +1,47 @@ +{ + "id": "9f660fc2-8293-47e1-9a02-ff520ec1a3c8", + "name": "repos_hub4j_github-api_dependency-graph_sbom", + "request": { + "url": "/repos/hub4j/github-api/dependency-graph/sbom", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "3-r_h_g_dependency-graph_sbom.json", + "headers": { + "Date": "Sun, 25 Jan 2026 20:41:52 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With", + "ETag": "W/\"6a7fd8420f2ebcb127ef4b8379638f4d547d2e1bb1a100de08eaf29fb98364d3\"", + "X-OAuth-Scopes": "repo", + "X-Accepted-OAuth-Scopes": "repo", + "github-authentication-token-expiration": "2026-02-19 19:55:13 UTC", + "X-GitHub-Media-Type": "github.v3; format=json", + "x-github-api-version-selected": "2022-11-28", + "X-RateLimit-Limit": "100", + "X-RateLimit-Remaining": "99", + "X-RateLimit-Reset": "1769373772", + "X-RateLimit-Used": "1", + "X-RateLimit-Resource": "dependency_sbom", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Server": "github.com", + "X-GitHub-Request-Id": "D814:19034C:6982F8E:5A5E487:6976800F" + } + }, + "uuid": "9f660fc2-8293-47e1-9a02-ff520ec1a3c8", + "persistent": true, + "insertionIndex": 3 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/__files/1-user.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/__files/1-user.json new file mode 100644 index 0000000000..79bade964e --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/__files/1-user.json @@ -0,0 +1,46 @@ +{ + "login": "gsmet", + "id": 1279749, + "node_id": "MDQ6VXNlcjEyNzk3NDk=", + "avatar_url": "https://avatars.githubusercontent.com/u/1279749?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gsmet", + "html_url": "https://github.com/gsmet", + "followers_url": "https://api.github.com/users/gsmet/followers", + "following_url": "https://api.github.com/users/gsmet/following{/other_user}", + "gists_url": "https://api.github.com/users/gsmet/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gsmet/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gsmet/subscriptions", + "organizations_url": "https://api.github.com/users/gsmet/orgs", + "repos_url": "https://api.github.com/users/gsmet/repos", + "events_url": "https://api.github.com/users/gsmet/events{/privacy}", + "received_events_url": "https://api.github.com/users/gsmet/received_events", + "type": "User", + "site_admin": false, + "name": "Guillaume Smet", + "company": "Red Hat", + "blog": "https://www.redhat.com/", + "location": "Lyon, France", + "email": "guillaume.smet@gmail.com", + "hireable": null, + "bio": "Happy camper at Red Hat, working on Quarkus and the Hibernate portfolio.", + "twitter_username": "gsmet_", + "public_repos": 102, + "public_gists": 14, + "followers": 127, + "following": 3, + "created_at": "2011-12-22T11:03:22Z", + "updated_at": "2021-03-23T17:35:45Z", + "private_gists": 14, + "total_private_repos": 4, + "owned_private_repos": 1, + "disk_usage": 68258, + "collaborators": 1, + "two_factor_authentication": true, + "plan": { + "name": "free", + "space": 976562499, + "collaborators": 0, + "private_repos": 10000 + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/__files/2-r_h_ghworkflowruntest.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/__files/2-r_h_ghworkflowruntest.json new file mode 100644 index 0000000000..7e1a13a6a4 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/__files/2-r_h_ghworkflowruntest.json @@ -0,0 +1,126 @@ +{ + "id": 348674220, + "node_id": "MDEwOlJlcG9zaXRvcnkzNDg2NzQyMjA=", + "name": "GHWorkflowRunTest", + "full_name": "hub4j-test-org/GHWorkflowRunTest", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/GHWorkflowRunTest", + "description": "Repository used by GHWorkflowRunTest", + "fork": false, + "url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest", + "forks_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/deployments", + "created_at": "2021-03-17T10:50:49Z", + "updated_at": "2021-03-17T10:56:17Z", + "pushed_at": "2021-03-22T17:53:57Z", + "git_url": "git://github.com/hub4j-test-org/GHWorkflowRunTest.git", + "ssh_url": "git@github.com:hub4j-test-org/GHWorkflowRunTest.git", + "clone_url": "https://github.com/hub4j-test-org/GHWorkflowRunTest.git", + "svn_url": "https://github.com/hub4j-test-org/GHWorkflowRunTest", + "homepage": null, + "size": 3, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 7, + "license": null, + "forks": 0, + "open_issues": 7, + "watchers": 0, + "default_branch": "main", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "delete_branch_on_merge": false, + "organization": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "network_count": 0, + "subscribers_count": 9 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/__files/3-r_h_g_actions_workflows_slow-workflowyml.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/__files/3-r_h_g_actions_workflows_slow-workflowyml.json new file mode 100644 index 0000000000..c1c00ffc11 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/__files/3-r_h_g_actions_workflows_slow-workflowyml.json @@ -0,0 +1,12 @@ +{ + "id": 6820849, + "node_id": "MDg6V29ya2Zsb3c2ODIwODQ5", + "name": "Slow workflow", + "path": ".github/workflows/slow-workflow.yml", + "state": "active", + "created_at": "2021-03-17T11:55:06.000+01:00", + "updated_at": "2021-03-17T11:55:06.000+01:00", + "url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/workflows/6820849", + "html_url": "https://github.com/hub4j-test-org/GHWorkflowRunTest/blob/main/.github/workflows/slow-workflow.yml", + "badge_url": "https://github.com/hub4j-test-org/GHWorkflowRunTest/workflows/Slow%20workflow/badge.svg" +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/__files/4-r_h_g_actions_runs.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/__files/4-r_h_g_actions_runs.json new file mode 100644 index 0000000000..bbf71ed675 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/__files/4-r_h_g_actions_runs.json @@ -0,0 +1,179 @@ +{ + "total_count": 56, + "workflow_runs": [ + { + "id": 686034992, + "name": "Fast workflow", + "node_id": "MDExOldvcmtmbG93UnVuNjg2MDM0OTky", + "head_branch": "main", + "head_sha": "f6a5c19a67797d64426203b8a7a05a0fd74e5037", + "run_number": 52, + "event": "workflow_dispatch", + "status": "completed", + "conclusion": "success", + "workflow_id": 6820790, + "check_suite_id": 2341210664, + "check_suite_node_id": "MDEwOkNoZWNrU3VpdGUyMzQxMjEwNjY0", + "url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686034992", + "html_url": "https://github.com/hub4j-test-org/GHWorkflowRunTest/actions/runs/686034992", + "pull_requests": [], + "created_at": "2021-03-25T09:36:45Z", + "updated_at": "2021-03-25T09:37:04Z", + "jobs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686034992/jobs", + "logs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686034992/logs", + "check_suite_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/check-suites/2341210664", + "artifacts_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686034992/artifacts", + "cancel_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686034992/cancel", + "rerun_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686034992/rerun", + "workflow_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/workflows/6820790", + "head_commit": { + "id": "f6a5c19a67797d64426203b8a7a05a0fd74e5037", + "tree_id": "666bb9f951306171acb21632eca28a386cb35f73", + "message": "Create failing-workflow.yml", + "timestamp": "2021-03-17T10:56:14Z", + "author": { + "name": "Guillaume Smet", + "email": "guillaume.smet@gmail.com" + }, + "committer": { + "name": "GitHub", + "email": "noreply@github.com" + } + }, + "repository": { + "id": 348674220, + "node_id": "MDEwOlJlcG9zaXRvcnkzNDg2NzQyMjA=", + "name": "GHWorkflowRunTest", + "full_name": "hub4j-test-org/GHWorkflowRunTest", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/GHWorkflowRunTest", + "description": "Repository used by GHWorkflowRunTest", + "fork": false, + "url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest", + "forks_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/deployments" + }, + "head_repository": { + "id": 348674220, + "node_id": "MDEwOlJlcG9zaXRvcnkzNDg2NzQyMjA=", + "name": "GHWorkflowRunTest", + "full_name": "hub4j-test-org/GHWorkflowRunTest", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/GHWorkflowRunTest", + "description": "Repository used by GHWorkflowRunTest", + "fork": false, + "url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest", + "forks_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/deployments" + } + } + ] +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/__files/6-r_h_g_actions_runs.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/__files/6-r_h_g_actions_runs.json new file mode 100644 index 0000000000..af45cec363 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/__files/6-r_h_g_actions_runs.json @@ -0,0 +1,179 @@ +{ + "total_count": 1, + "workflow_runs": [ + { + "id": 686036126, + "name": "Slow workflow", + "node_id": "MDExOldvcmtmbG93UnVuNjg2MDM2MTI2", + "head_branch": "main", + "head_sha": "f6a5c19a67797d64426203b8a7a05a0fd74e5037", + "run_number": 16, + "event": "workflow_dispatch", + "status": "in_progress", + "conclusion": null, + "workflow_id": 6820849, + "check_suite_id": 2341213475, + "check_suite_node_id": "MDEwOkNoZWNrU3VpdGUyMzQxMjEzNDc1", + "url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126", + "html_url": "https://github.com/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126", + "pull_requests": [], + "created_at": "2021-03-25T09:37:09Z", + "updated_at": "2021-03-25T09:37:19Z", + "jobs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126/jobs", + "logs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126/logs", + "check_suite_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/check-suites/2341213475", + "artifacts_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126/artifacts", + "cancel_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126/cancel", + "rerun_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126/rerun", + "workflow_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/workflows/6820849", + "head_commit": { + "id": "f6a5c19a67797d64426203b8a7a05a0fd74e5037", + "tree_id": "666bb9f951306171acb21632eca28a386cb35f73", + "message": "Create failing-workflow.yml", + "timestamp": "2021-03-17T10:56:14Z", + "author": { + "name": "Guillaume Smet", + "email": "guillaume.smet@gmail.com" + }, + "committer": { + "name": "GitHub", + "email": "noreply@github.com" + } + }, + "repository": { + "id": 348674220, + "node_id": "MDEwOlJlcG9zaXRvcnkzNDg2NzQyMjA=", + "name": "GHWorkflowRunTest", + "full_name": "hub4j-test-org/GHWorkflowRunTest", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/GHWorkflowRunTest", + "description": "Repository used by GHWorkflowRunTest", + "fork": false, + "url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest", + "forks_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/deployments" + }, + "head_repository": { + "id": 348674220, + "node_id": "MDEwOlJlcG9zaXRvcnkzNDg2NzQyMjA=", + "name": "GHWorkflowRunTest", + "full_name": "hub4j-test-org/GHWorkflowRunTest", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/GHWorkflowRunTest", + "description": "Repository used by GHWorkflowRunTest", + "fork": false, + "url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest", + "forks_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/deployments" + } + } + ] +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/__files/8-r_h_g_actions_runs_686036126.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/__files/8-r_h_g_actions_runs_686036126.json new file mode 100644 index 0000000000..8530af18fb --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/__files/8-r_h_g_actions_runs_686036126.json @@ -0,0 +1,174 @@ +{ + "id": 686036126, + "name": "Slow workflow", + "node_id": "MDExOldvcmtmbG93UnVuNjg2MDM2MTI2", + "head_branch": "main", + "head_sha": "f6a5c19a67797d64426203b8a7a05a0fd74e5037", + "run_number": 16, + "event": "workflow_dispatch", + "status": "completed", + "conclusion": "cancelled", + "workflow_id": 6820849, + "check_suite_id": 2341213475, + "check_suite_node_id": "MDEwOkNoZWNrU3VpdGUyMzQxMjEzNDc1", + "url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126", + "html_url": "https://github.com/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126", + "pull_requests": [], + "created_at": "2021-03-25T09:37:09Z", + "updated_at": "2021-03-25T09:37:43Z", + "jobs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126/jobs", + "logs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126/logs", + "check_suite_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/check-suites/2341213475", + "artifacts_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126/artifacts", + "cancel_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126/cancel", + "rerun_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126/rerun", + "workflow_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/workflows/6820849", + "head_commit": { + "id": "f6a5c19a67797d64426203b8a7a05a0fd74e5037", + "tree_id": "666bb9f951306171acb21632eca28a386cb35f73", + "message": "Create failing-workflow.yml", + "timestamp": "2021-03-17T10:56:14Z", + "author": { + "name": "Guillaume Smet", + "email": "guillaume.smet@gmail.com" + }, + "committer": { + "name": "GitHub", + "email": "noreply@github.com" + } + }, + "repository": { + "id": 348674220, + "node_id": "MDEwOlJlcG9zaXRvcnkzNDg2NzQyMjA=", + "name": "GHWorkflowRunTest", + "full_name": "hub4j-test-org/GHWorkflowRunTest", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/GHWorkflowRunTest", + "description": "Repository used by GHWorkflowRunTest", + "fork": false, + "url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest", + "forks_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/deployments" + }, + "head_repository": { + "id": 348674220, + "node_id": "MDEwOlJlcG9zaXRvcnkzNDg2NzQyMjA=", + "name": "GHWorkflowRunTest", + "full_name": "hub4j-test-org/GHWorkflowRunTest", + "private": false, + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/hub4j-test-org/GHWorkflowRunTest", + "description": "Repository used by GHWorkflowRunTest", + "fork": false, + "url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest", + "forks_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/forks", + "keys_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/teams", + "hooks_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/hooks", + "issue_events_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues/events{/number}", + "events_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/events", + "assignees_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/assignees{/user}", + "branches_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/branches{/branch}", + "tags_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/tags", + "blobs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/statuses/{sha}", + "languages_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/languages", + "stargazers_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/stargazers", + "contributors_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/contributors", + "subscribers_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/subscribers", + "subscription_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/subscription", + "commits_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/contents/{+path}", + "compare_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/merges", + "archive_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/downloads", + "issues_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/issues{/number}", + "pulls_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/pulls{/number}", + "milestones_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/milestones{/number}", + "notifications_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/labels{/name}", + "releases_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/releases{/id}", + "deployments_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/deployments" + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/1-user.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/1-user.json new file mode 100644 index 0000000000..9e3e9b003b --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/1-user.json @@ -0,0 +1,46 @@ +{ + "id": "671767da-fd52-4e20-8fb9-3e65fb20e6a6", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "1-user.json", + "headers": { + "Server": "GitHub.com", + "Date": "Thu, 25 Mar 2021 09:37:07 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding, Accept, X-Requested-With" + ], + "ETag": "W/\"879f35eed38dcc75ca2b3a8999425e0d51678f4c73ffade3c5bcc853b59245b6\"", + "Last-Modified": "Tue, 23 Mar 2021 17:35:45 GMT", + "X-OAuth-Scopes": "repo, user, workflow", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "unknown, github.v3", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4961", + "X-RateLimit-Reset": "1616667526", + "X-RateLimit-Used": "39", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "CD00:609B:198A5DF:1A1E31A:605C59C3" + } + }, + "uuid": "671767da-fd52-4e20-8fb9-3e65fb20e6a6", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/2-r_h_ghworkflowruntest.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/2-r_h_ghworkflowruntest.json new file mode 100644 index 0000000000..58cf803d25 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/2-r_h_ghworkflowruntest.json @@ -0,0 +1,46 @@ +{ + "id": "8b1bfd77-24af-44ed-9b8d-c63d0d041797", + "name": "repos_hub4j-test-org_ghworkflowruntest", + "request": { + "url": "/repos/hub4j-test-org/GHWorkflowRunTest", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "2-r_h_ghworkflowruntest.json", + "headers": { + "Server": "GitHub.com", + "Date": "Thu, 25 Mar 2021 09:37:08 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding, Accept, X-Requested-With" + ], + "ETag": "W/\"c31f2677fb5b82356abd6930419a16b19ffad1abeca1de1d12e66e658b36d027\"", + "Last-Modified": "Wed, 17 Mar 2021 10:56:17 GMT", + "X-OAuth-Scopes": "repo, user, workflow", + "X-Accepted-OAuth-Scopes": "repo", + "X-GitHub-Media-Type": "unknown, github.v3", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4959", + "X-RateLimit-Reset": "1616667526", + "X-RateLimit-Used": "41", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "CD00:609B:198A646:1A1E38D:605C59C3" + } + }, + "uuid": "8b1bfd77-24af-44ed-9b8d-c63d0d041797", + "persistent": true, + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/3-r_h_g_actions_workflows_slow-workflowyml.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/3-r_h_g_actions_workflows_slow-workflowyml.json new file mode 100644 index 0000000000..593c1e4848 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/3-r_h_g_actions_workflows_slow-workflowyml.json @@ -0,0 +1,45 @@ +{ + "id": "6e0a2fd8-b590-4954-be47-bfdcf94b4094", + "name": "repos_hub4j-test-org_ghworkflowruntest_actions_workflows_slow-workflowyml", + "request": { + "url": "/repos/hub4j-test-org/GHWorkflowRunTest/actions/workflows/slow-workflow.yml", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "3-r_h_g_actions_workflows_slow-workflowyml.json", + "headers": { + "Server": "GitHub.com", + "Date": "Thu, 25 Mar 2021 09:37:08 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding, Accept, X-Requested-With" + ], + "ETag": "W/\"f3c669487342bb42726eeb2d3eca9c83111ab5ad0e817f04b531a49802e23276\"", + "X-OAuth-Scopes": "repo, user, workflow", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "unknown, github.v3", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4958", + "X-RateLimit-Reset": "1616667526", + "X-RateLimit-Used": "42", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "CD00:609B:198A685:1A1E3CD:605C59C4" + } + }, + "uuid": "6e0a2fd8-b590-4954-be47-bfdcf94b4094", + "persistent": true, + "insertionIndex": 3 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/4-r_h_g_actions_runs.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/4-r_h_g_actions_runs.json new file mode 100644 index 0000000000..67022b6f72 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/4-r_h_g_actions_runs.json @@ -0,0 +1,46 @@ +{ + "id": "9145f708-8236-444a-8bd3-c70e00d537d4", + "name": "repos_hub4j-test-org_ghworkflowruntest_actions_runs", + "request": { + "url": "/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs?per_page=1", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "4-r_h_g_actions_runs.json", + "headers": { + "Server": "GitHub.com", + "Date": "Thu, 25 Mar 2021 09:37:08 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding, Accept, X-Requested-With" + ], + "ETag": "W/\"9f779230ccdc33fc688e7cb418412e3a2bfd055aa700bd032dceba2d9d1a357b\"", + "X-OAuth-Scopes": "repo, user, workflow", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "unknown, github.v3", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4957", + "X-RateLimit-Reset": "1616667526", + "X-RateLimit-Used": "43", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "CD00:609B:198A6B2:1A1E3F0:605C59C4", + "Link": "; rel=\"next\", ; rel=\"last\"" + } + }, + "uuid": "9145f708-8236-444a-8bd3-c70e00d537d4", + "persistent": true, + "insertionIndex": 4 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/5-r_h_g_actions_workflows_6820849_dispatches.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/5-r_h_g_actions_workflows_6820849_dispatches.json new file mode 100644 index 0000000000..58f908e137 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/5-r_h_g_actions_workflows_6820849_dispatches.json @@ -0,0 +1,45 @@ +{ + "id": "ef20ff92-f593-4ed2-934b-8cb50f2237e3", + "name": "repos_hub4j-test-org_ghworkflowruntest_actions_workflows_6820849_dispatches", + "request": { + "url": "/repos/hub4j-test-org/GHWorkflowRunTest/actions/workflows/6820849/dispatches", + "method": "POST", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{\"ref\":\"main\"}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 204, + "headers": { + "Server": "GitHub.com", + "Date": "Thu, 25 Mar 2021 09:37:08 GMT", + "X-OAuth-Scopes": "repo, user, workflow", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "unknown, github.v3", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4956", + "X-RateLimit-Reset": "1616667526", + "X-RateLimit-Used": "44", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Vary": "Accept-Encoding, Accept, X-Requested-With", + "X-GitHub-Request-Id": "CD00:609B:198A6E1:1A1E41A:605C59C4" + } + }, + "uuid": "ef20ff92-f593-4ed2-934b-8cb50f2237e3", + "persistent": true, + "insertionIndex": 5 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/6-r_h_g_actions_runs.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/6-r_h_g_actions_runs.json new file mode 100644 index 0000000000..971bf61b59 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/6-r_h_g_actions_runs.json @@ -0,0 +1,45 @@ +{ + "id": "e9be2e68-8827-4b57-b511-901040b055fd", + "name": "repos_hub4j-test-org_ghworkflowruntest_actions_runs", + "request": { + "url": "/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs?branch=main&status=in_progress&event=workflow_dispatch&per_page=20", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "6-r_h_g_actions_runs.json", + "headers": { + "Server": "GitHub.com", + "Date": "Thu, 25 Mar 2021 09:37:24 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding, Accept, X-Requested-With" + ], + "ETag": "W/\"9713de6322f49f50d854fcc5c88e6db3f6a72e2a1211d12c8a306f773f5f9f72\"", + "X-OAuth-Scopes": "repo, user, workflow", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "unknown, github.v3", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4951", + "X-RateLimit-Reset": "1616667526", + "X-RateLimit-Used": "49", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "CD00:609B:198BA67:1A1F811:605C59D4" + } + }, + "uuid": "e9be2e68-8827-4b57-b511-901040b055fd", + "persistent": true, + "insertionIndex": 6 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/7-r_h_g_actions_runs_686036126_forceCancel.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/7-r_h_g_actions_runs_686036126_forceCancel.json new file mode 100644 index 0000000000..b350a66d66 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/7-r_h_g_actions_runs_686036126_forceCancel.json @@ -0,0 +1,50 @@ +{ + "id": "b62513a1-6dbb-4a4d-95b6-053e7661385e", + "name": "repos_hub4j-test-org_ghworkflowruntest_actions_runs_686036126_forceCcancel", + "request": { + "url": "/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126/force-cancel", + "method": "POST", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 202, + "body": "{}", + "headers": { + "Server": "GitHub.com", + "Date": "Thu, 25 Mar 2021 09:37:25 GMT", + "Content-Type": "application/json; charset=utf-8", + "X-OAuth-Scopes": "repo, user, workflow", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "unknown, github.v3", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4950", + "X-RateLimit-Reset": "1616667526", + "X-RateLimit-Used": "50", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Vary": "Accept-Encoding, Accept, X-Requested-With", + "X-GitHub-Request-Id": "CD00:609B:198BA93:1A1F838:605C59D4" + } + }, + "uuid": "b62513a1-6dbb-4a4d-95b6-053e7661385e", + "persistent": true, + "scenarioName": "scenario-1-repos-hub4j-test-org-GHWorkflowRunTest-actions-runs-686036126-forceCcancel", + "requiredScenarioState": "Started", + "newScenarioState": "scenario-1-repos-hub4j-test-org-GHWorkflowRunTest-actions-runs-686036126-forceCcancel-2", + "insertionIndex": 7 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/8-r_h_g_actions_runs_686036126.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/8-r_h_g_actions_runs_686036126.json new file mode 100644 index 0000000000..733d3058a9 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testForceCancel/mappings/8-r_h_g_actions_runs_686036126.json @@ -0,0 +1,45 @@ +{ + "id": "03f4192e-6863-4d68-a8c5-c9ee276b1c2e", + "name": "repos_hub4j-test-org_ghworkflowruntest_actions_runs_686036126", + "request": { + "url": "/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "8-r_h_g_actions_runs_686036126.json", + "headers": { + "Server": "GitHub.com", + "Date": "Thu, 25 Mar 2021 09:37:46 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding, Accept, X-Requested-With" + ], + "ETag": "W/\"19fcfa727b9da4b3a137deee61f79cd3d75ecc6c38b8001b872812184d2bbf44\"", + "X-OAuth-Scopes": "repo, user, workflow", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "unknown, github.v3", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4944", + "X-RateLimit-Reset": "1616667526", + "X-RateLimit-Used": "56", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "CD00:609B:198D1E3:1A21010:605C59EA" + } + }, + "uuid": "03f4192e-6863-4d68-a8c5-c9ee276b1c2e", + "persistent": true, + "insertionIndex": 8 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testManualRunAndBasicInformation/__files/6-r_h_g_actions_runs.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testManualRunAndBasicInformation/__files/6-r_h_g_actions_runs.json index 00b4c827d6..d2ce370591 100644 --- a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testManualRunAndBasicInformation/__files/6-r_h_g_actions_runs.json +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testManualRunAndBasicInformation/__files/6-r_h_g_actions_runs.json @@ -26,7 +26,7 @@ "cancel_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686034992/cancel", "rerun_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686034992/rerun", "workflow_url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/workflows/6820790", - "triggering_actor": { + "actor": { "login": "octocat", "id": 1, "node_id": "MDQ6VXNlcjE=", @@ -46,6 +46,26 @@ "type": "User", "site_admin": false }, + "triggering_actor": { + "login": "octocat_trigger", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat_trigger", + "html_url": "https://github.com/octocat_trigger", + "followers_url": "https://api.github.com/users/octocat_trigger/followers", + "following_url": "https://api.github.com/users/octocat_trigger/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat_trigger/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat_trigger/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat_trigger/subscriptions", + "organizations_url": "https://api.github.com/users/octocat_trigger/orgs", + "repos_url": "https://api.github.com/users/octocat_trigger/repos", + "events_url": "https://api.github.com/users/octocat_trigger/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat_trigger/received_events", + "type": "User", + "site_admin": false + }, "head_commit": { "id": "f6a5c19a67797d64426203b8a7a05a0fd74e5037", "tree_id": "666bb9f951306171acb21632eca28a386cb35f73", diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/__files/1-user.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/__files/1-user.json new file mode 100644 index 0000000000..c2803cc815 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/__files/1-user.json @@ -0,0 +1,3 @@ +{ + "login": "ayagmar" +} diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/__files/2-r_h_ghworkflowruntest.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/__files/2-r_h_ghworkflowruntest.json new file mode 100644 index 0000000000..f56c73309a --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/__files/2-r_h_ghworkflowruntest.json @@ -0,0 +1,9 @@ +{ + "id": 348674220, + "name": "GHWorkflowRunTest", + "full_name": "hub4j-test-org/GHWorkflowRunTest", + "url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest", + "owner": { + "login": "hub4j-test-org" + } +} diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/__files/3-r_h_g_actions_runs_686036126.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/__files/3-r_h_g_actions_runs_686036126.json new file mode 100644 index 0000000000..10cc6c479b --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/__files/3-r_h_g_actions_runs_686036126.json @@ -0,0 +1,10 @@ +{ + "id": 686036126, + "name": "Slow workflow", + "url": "https://api.github.com/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126", + "html_url": "https://github.com/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126", + "status": "completed", + "conclusion": "failure", + "workflow_id": 6820849, + "run_attempt": 1 +} diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/1-user.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/1-user.json new file mode 100644 index 0000000000..5bef72a999 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/1-user.json @@ -0,0 +1,23 @@ +{ + "id": "3e7dbf87-7930-4d75-aa8d-d65b76de98dc", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "1-user.json", + "headers": { + "Content-Type": "application/json; charset=utf-8" + } + }, + "uuid": "3e7dbf87-7930-4d75-aa8d-d65b76de98dc", + "persistent": true, + "insertionIndex": 1 +} diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/2-r_h_ghworkflowruntest.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/2-r_h_ghworkflowruntest.json new file mode 100644 index 0000000000..210b9ce9dd --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/2-r_h_ghworkflowruntest.json @@ -0,0 +1,23 @@ +{ + "id": "fd7591c0-77eb-4e52-9b4b-0dc73777a5a0", + "name": "repos_hub4j-test-org_ghworkflowruntest", + "request": { + "url": "/repos/hub4j-test-org/GHWorkflowRunTest", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "2-r_h_ghworkflowruntest.json", + "headers": { + "Content-Type": "application/json; charset=utf-8" + } + }, + "uuid": "fd7591c0-77eb-4e52-9b4b-0dc73777a5a0", + "persistent": true, + "insertionIndex": 2 +} diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/3-r_h_g_actions_runs_686036126.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/3-r_h_g_actions_runs_686036126.json new file mode 100644 index 0000000000..1b6cb31687 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/3-r_h_g_actions_runs_686036126.json @@ -0,0 +1,23 @@ +{ + "id": "77e5d70a-1cf7-44bf-b170-c458d7060d65", + "name": "repos_hub4j-test-org_ghworkflowruntest_actions_runs_686036126", + "request": { + "url": "/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "3-r_h_g_actions_runs_686036126.json", + "headers": { + "Content-Type": "application/json; charset=utf-8" + } + }, + "uuid": "77e5d70a-1cf7-44bf-b170-c458d7060d65", + "persistent": true, + "insertionIndex": 3 +} diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/4-r_h_g_actions_runs_686036126_rerun-failed-jobs.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/4-r_h_g_actions_runs_686036126_rerun-failed-jobs.json new file mode 100644 index 0000000000..84e6f34f54 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/4-r_h_g_actions_runs_686036126_rerun-failed-jobs.json @@ -0,0 +1,30 @@ +{ + "id": "16330f88-a67e-41e6-b636-58db8d3e8f0c", + "name": "repos_hub4j-test-org_ghworkflowruntest_actions_runs_686036126_rerun_failed_jobs", + "request": { + "url": "/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126/rerun-failed-jobs", + "method": "POST", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 201, + "body": "{}", + "headers": { + "Content-Type": "application/json; charset=utf-8" + } + }, + "uuid": "16330f88-a67e-41e6-b636-58db8d3e8f0c", + "persistent": true, + "insertionIndex": 4 +} diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/5-r_h_g_actions_runs_686036126_rerun-failed-jobs.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/5-r_h_g_actions_runs_686036126_rerun-failed-jobs.json new file mode 100644 index 0000000000..6c4135fa72 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/5-r_h_g_actions_runs_686036126_rerun-failed-jobs.json @@ -0,0 +1,30 @@ +{ + "id": "8b165443-a354-489f-8fee-73801637dbfd", + "name": "repos_hub4j-test-org_ghworkflowruntest_actions_runs_686036126_rerun_failed_jobs_debug", + "request": { + "url": "/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126/rerun-failed-jobs", + "method": "POST", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{\"enable_debug_logging\":true}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 201, + "body": "{}", + "headers": { + "Content-Type": "application/json; charset=utf-8" + } + }, + "uuid": "8b165443-a354-489f-8fee-73801637dbfd", + "persistent": true, + "insertionIndex": 5 +} diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/6-r_h_g_actions_runs_686036126_rerun.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/6-r_h_g_actions_runs_686036126_rerun.json new file mode 100644 index 0000000000..9a498ec76a --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/6-r_h_g_actions_runs_686036126_rerun.json @@ -0,0 +1,30 @@ +{ + "id": "d5f0000f-e754-447c-84fd-804df2dbed78", + "name": "repos_hub4j-test-org_ghworkflowruntest_actions_runs_686036126_rerun_debug", + "request": { + "url": "/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126/rerun", + "method": "POST", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{\"enable_debug_logging\":true}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 201, + "body": "{}", + "headers": { + "Content-Type": "application/json; charset=utf-8" + } + }, + "uuid": "d5f0000f-e754-447c-84fd-804df2dbed78", + "persistent": true, + "insertionIndex": 6 +} diff --git a/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/7-r_h_g_actions_runs_686036126_rerun.json b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/7-r_h_g_actions_runs_686036126_rerun.json new file mode 100644 index 0000000000..9775910ab4 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHWorkflowRunTest/wiremock/testRerunVariants/mappings/7-r_h_g_actions_runs_686036126_rerun.json @@ -0,0 +1,30 @@ +{ + "id": "4d1bb285-92fe-49d8-a4bf-72a08ca8a7d6", + "name": "repos_hub4j-test-org_ghworkflowruntest_actions_runs_686036126_rerun", + "request": { + "url": "/repos/hub4j-test-org/GHWorkflowRunTest/actions/runs/686036126/rerun", + "method": "POST", + "headers": { + "Accept": { + "equalTo": "application/vnd.github+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 201, + "body": "{}", + "headers": { + "Content-Type": "application/json; charset=utf-8" + } + }, + "uuid": "4d1bb285-92fe-49d8-a4bf-72a08ca8a7d6", + "persistent": true, + "insertionIndex": 7 +} diff --git a/src/test/resources/reflection-and-serialization-test-error-message b/src/test/resources/reflection-and-serialization-test-error-message index a56576c62c..a60293d3e6 100644 --- a/src/test/resources/reflection-and-serialization-test-error-message +++ b/src/test/resources/reflection-and-serialization-test-error-message @@ -1,5 +1,15 @@ The class "%1$s" needs to be configured or excluded for reflection / serialization and was not mentioned in one of the following resources: +Please do one of the following: +1. add "%1$s" to serialization.json and / or reflect-config.json +2. add "%1$s" to no-reflect-and-serialization-list + +DO NOT do both. + +Option 1: +The class is serialized or reflected over. Includes "GH*" classes the are populated using Jackson. +Does not include Builders and other classes that are only used locally. + src/main/resources/META-INF/reflect-config.json - example: { @@ -24,10 +34,12 @@ src/main/resources/META-INF/serialization.json - example: "name": "%1$s" } +Option 2: +The class is not serialized or reflected over. This is less common. + src/test/resources/no-reflect-and-serialization-list - example: %1$s -Please add it to either no-reflect-and-serialization-list or to serialization.json and / or reflect-config.json