mirror of
https://github.com/jlengrand/github-api.git
synced 2026-03-11 00:11:25 +00:00
Compare commits
263 Commits
github-api
...
github-api
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
349ef7a54c | ||
|
|
94df5fc389 | ||
|
|
906238a297 | ||
|
|
7963fa82b5 | ||
|
|
1aba6012fb | ||
|
|
ff4324ac67 | ||
|
|
11bc669e1d | ||
|
|
dcf26d58e4 | ||
|
|
4d46872c35 | ||
|
|
4f0d62f421 | ||
|
|
f7ad1f517b | ||
|
|
345d6197f3 | ||
|
|
bb4d44138a | ||
|
|
a8ef0cde53 | ||
|
|
77dc009c95 | ||
|
|
aa298c93cc | ||
|
|
dfb0a5240e | ||
|
|
9cfc3c22b5 | ||
|
|
b177d98e29 | ||
|
|
5405fb0370 | ||
|
|
72a1c24b3b | ||
|
|
f146ae94ec | ||
|
|
a0bbba748a | ||
|
|
81bf818573 | ||
|
|
d5913dc292 | ||
|
|
e1e901b794 | ||
|
|
2f2f26767e | ||
|
|
bffa78c1b8 | ||
|
|
c55719c67a | ||
|
|
cb3b4a6642 | ||
|
|
92c141cee6 | ||
|
|
fd1a1a1c23 | ||
|
|
b835884b2e | ||
|
|
660763908d | ||
|
|
fe8bdb755a | ||
|
|
67dc6d2d23 | ||
|
|
9c8d73cbe2 | ||
|
|
5db97d92dd | ||
|
|
ac470dddb5 | ||
|
|
43063fe8ce | ||
|
|
59e0046c1e | ||
|
|
36ab05c265 | ||
|
|
2b2be05dae | ||
|
|
fb1adbd1ef | ||
|
|
ab68a59b25 | ||
|
|
9c7de767e9 | ||
|
|
8ba5cf7c2e | ||
|
|
b194a19b98 | ||
|
|
1d344b016f | ||
|
|
474f3ef4ca | ||
|
|
9830927020 | ||
|
|
727932a442 | ||
|
|
cd92b51845 | ||
|
|
fe26d16411 | ||
|
|
d68c66ce2b | ||
|
|
e7bfbfb48f | ||
|
|
f2a88ae61c | ||
|
|
e2113f6ee5 | ||
|
|
3867224024 | ||
|
|
9ee0bf43bc | ||
|
|
2844542efa | ||
|
|
e3fcae9392 | ||
|
|
c6ccfa91f3 | ||
|
|
b6fcee1cb9 | ||
|
|
9071befb04 | ||
|
|
bdd5fe98f3 | ||
|
|
a3d3e83a49 | ||
|
|
08bde72028 | ||
|
|
108a136368 | ||
|
|
57d87ad6b1 | ||
|
|
0c22815ff7 | ||
|
|
0ca792ecfd | ||
|
|
987c34c69e | ||
|
|
c1c02bc8ab | ||
|
|
4ee369f27c | ||
|
|
c9012efdcb | ||
|
|
41524fc67d | ||
|
|
04ff61e981 | ||
|
|
532468dc67 | ||
|
|
9c9a2dae47 | ||
|
|
c8a868b57f | ||
|
|
4b3f81ee34 | ||
|
|
afa170ba7c | ||
|
|
46e3b2272e | ||
|
|
52472e90ec | ||
|
|
4ef0d00846 | ||
|
|
580f2537f2 | ||
|
|
3d9fd96026 | ||
|
|
f449b92721 | ||
|
|
3b0216b023 | ||
|
|
98cf839737 | ||
|
|
0bb0846505 | ||
|
|
70969400a3 | ||
|
|
147e8d5d12 | ||
|
|
cacc3e6edd | ||
|
|
a284eca147 | ||
|
|
0d3ba9d7f0 | ||
|
|
be8064d642 | ||
|
|
e30dba742d | ||
|
|
44b72ed647 | ||
|
|
666bd77dac | ||
|
|
0a6613e60d | ||
|
|
62e186c123 | ||
|
|
50dd8f5bcc | ||
|
|
d5fcac9c45 | ||
|
|
c2bed85190 | ||
|
|
183b463ef2 | ||
|
|
92fdac44a0 | ||
|
|
12829ecc73 | ||
|
|
51319c3b26 | ||
|
|
8fd827040b | ||
|
|
5ec46eae0d | ||
|
|
32c03301be | ||
|
|
df7f29b2ab | ||
|
|
e863113c36 | ||
|
|
8e2c1d7382 | ||
|
|
ab7b9cccba | ||
|
|
81bf61a161 | ||
|
|
b40f008647 | ||
|
|
734e41702b | ||
|
|
038dd20a91 | ||
|
|
1dd62b8550 | ||
|
|
715deebe05 | ||
|
|
b3fe3d8590 | ||
|
|
f74c3ed3ea | ||
|
|
2c9aebeeed | ||
|
|
7474f1e11f | ||
|
|
dba9c55b64 | ||
|
|
b432364397 | ||
|
|
696967bdd1 | ||
|
|
b76889efc3 | ||
|
|
e6a7b64ebe | ||
|
|
9daa0df311 | ||
|
|
612800bda5 | ||
|
|
a6bbb1dec9 | ||
|
|
873c93ab64 | ||
|
|
d15242e2d2 | ||
|
|
992d2b937c | ||
|
|
1e05ddad4b | ||
|
|
4f8a64610b | ||
|
|
b82366218c | ||
|
|
acbe1f4cb3 | ||
|
|
4c5e018583 | ||
|
|
6c0380e85c | ||
|
|
fde48e604f | ||
|
|
e83a4de5fb | ||
|
|
927d2799dc | ||
|
|
1ad701fe5d | ||
|
|
086425d2da | ||
|
|
beca54416a | ||
|
|
c92f5c5713 | ||
|
|
dee4e6caff | ||
|
|
dd5a39e72e | ||
|
|
e5ed52165c | ||
|
|
9484f8e0f5 | ||
|
|
947caffe0a | ||
|
|
870090e8df | ||
|
|
73f07f13c5 | ||
|
|
d1952bf591 | ||
|
|
5a612e1332 | ||
|
|
b00a9faea6 | ||
|
|
74db42a703 | ||
|
|
ddf625ca04 | ||
|
|
eca2f017d8 | ||
|
|
3190bde343 | ||
|
|
c6ebf42a47 | ||
|
|
c116b60d12 | ||
|
|
5d09e6d9ab | ||
|
|
2613ce0ac9 | ||
|
|
a88e9b28ea | ||
|
|
f0a3c26ee6 | ||
|
|
84c87ecb32 | ||
|
|
6573f44d41 | ||
|
|
3cacbc552c | ||
|
|
343d623e02 | ||
|
|
6b80bb2b11 | ||
|
|
56fe7452eb | ||
|
|
d3a66f6605 | ||
|
|
dd7b4712f1 | ||
|
|
9df5871f6b | ||
|
|
29aab9e9f4 | ||
|
|
af67eb7f0b | ||
|
|
10482c0141 | ||
|
|
a7a792251a | ||
|
|
aec2308144 | ||
|
|
0741b8aa6a | ||
|
|
3082622394 | ||
|
|
965c9cb0af | ||
|
|
495a46e2d8 | ||
|
|
05bda1192e | ||
|
|
6058af0ca1 | ||
|
|
1eb8bf9719 | ||
|
|
afc02faeda | ||
|
|
66f22de90f | ||
|
|
2949a2e0ff | ||
|
|
ba12efea9d | ||
|
|
e1180a12fb | ||
|
|
1393706f13 | ||
|
|
6f994f31f7 | ||
|
|
38aa99a063 | ||
|
|
85c44b3529 | ||
|
|
e1a2768de5 | ||
|
|
e1c9b27203 | ||
|
|
969f6ef826 | ||
|
|
7abc4d4e76 | ||
|
|
ac97147c1f | ||
|
|
dbd20fe396 | ||
|
|
44e57c9c4b | ||
|
|
488e5e531f | ||
|
|
42a6a8d770 | ||
|
|
f8e877ea05 | ||
|
|
65d6fc7272 | ||
|
|
63ce8e461b | ||
|
|
fbf4c48461 | ||
|
|
81a55db644 | ||
|
|
4d4edfa181 | ||
|
|
6f9182f1f6 | ||
|
|
fa600c03e2 | ||
|
|
4a53301e9f | ||
|
|
676984b3d5 | ||
|
|
e6d7f7248b | ||
|
|
50903b5c4a | ||
|
|
01e399fb91 | ||
|
|
911aeb7af0 | ||
|
|
7e5cd9abbc | ||
|
|
115527a21a | ||
|
|
eff4f4f601 | ||
|
|
16e0099a0d | ||
|
|
2c8c678275 | ||
|
|
3b51e87fbf | ||
|
|
6c6eef5e2b | ||
|
|
6e5910f44c | ||
|
|
a967189bc6 | ||
|
|
7069176cf6 | ||
|
|
44dcbe773d | ||
|
|
ca76975461 | ||
|
|
83122ac99e | ||
|
|
c3e9458555 | ||
|
|
057ba38873 | ||
|
|
81d7d6236b | ||
|
|
191dd49653 | ||
|
|
21e9dd6f51 | ||
|
|
cc2d14acc6 | ||
|
|
87f37e9f1c | ||
|
|
d536a9f874 | ||
|
|
b45f353fa9 | ||
|
|
a3073ec14e | ||
|
|
f77eb33029 | ||
|
|
c1c919097a | ||
|
|
e05348463c | ||
|
|
fdcf74eaf2 | ||
|
|
6d57a3e3b9 | ||
|
|
1f449c866e | ||
|
|
e12deccd24 | ||
|
|
3184ebb5ee | ||
|
|
4a35ed2b35 | ||
|
|
5c9474d1c8 | ||
|
|
2724211535 | ||
|
|
81068de0f1 | ||
|
|
df576e2738 | ||
|
|
bb1356b25d | ||
|
|
1b67960da4 | ||
|
|
d76718e8b2 |
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -10,3 +10,7 @@ We love getting PRs, but we hate asking people for the same basic changes every
|
|||||||
- [ ] Run `mvn clean compile` locally. This may reformat your code, commit those changes.
|
- [ ] Run `mvn clean compile` locally. This may reformat your code, commit those changes.
|
||||||
- [ ] 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` locally. If this command doesn't succeed, your change will not pass CI.
|
||||||
|
|
||||||
|
# When creating a PR:
|
||||||
|
|
||||||
|
- [ ] Fill in the "Description" above.
|
||||||
|
- [ ] Enable "Allow edits from maintainers".
|
||||||
|
|||||||
12
.github/dependabot.yml
vendored
Normal file
12
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "maven"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
time: "02:00"
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
time: "02:00"
|
||||||
2
.github/release-drafter.yml
vendored
2
.github/release-drafter.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
name-template: 'v$NEXT_PATCH_VERSION 🌈'
|
name-template: 'v$NEXT_PATCH_VERSION 🌈'
|
||||||
tag-template: 'v$NEXT_PATCH_VERSION'
|
tag-template: 'github-api-$NEXT_MINOR_VERSION'
|
||||||
categories:
|
categories:
|
||||||
- title: '🚀 Features'
|
- title: '🚀 Features'
|
||||||
labels:
|
labels:
|
||||||
|
|||||||
10
.github/workflows/maven-build.yml
vendored
10
.github/workflows/maven-build.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
java: [ 11 ]
|
java: [ 13 ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
java-version: ${{ matrix.java }}
|
java-version: ${{ matrix.java }}
|
||||||
- name: Cached .m2
|
- name: Cached .m2
|
||||||
uses: actions/cache@v1
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: ~/.m2/repository
|
path: ~/.m2/repository
|
||||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: ${{ matrix.java }}
|
java-version: ${{ matrix.java }}
|
||||||
- uses: actions/cache@v1
|
- uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: ~/.m2/repository
|
path: ~/.m2/repository
|
||||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||||
@@ -51,14 +51,14 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ ubuntu, windows ]
|
os: [ ubuntu, windows ]
|
||||||
java: [ 8, 11, 13 ]
|
java: [ 8, 11, 13, 15-ea ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: ${{ matrix.java }}
|
java-version: ${{ matrix.java }}
|
||||||
- uses: actions/cache@v1
|
- uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: ~/.m2/repository
|
path: ~/.m2/repository
|
||||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||||
|
|||||||
1324
CHANGELOG.md
1324
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
|||||||
# Java API for GitHub
|
# Java API for GitHub
|
||||||
|
|
||||||
[](https://mvnrepository.com/artifact/org.kohsuke/github-api)
|
[](https://mvnrepository.com/artifact/org.kohsuke/github-api)
|
||||||
[](https://gitter.im/github-api/github-api?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/hub4j/github-api?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
68
pom.xml
68
pom.xml
@@ -2,16 +2,16 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.kohsuke</groupId>
|
<groupId>org.kohsuke</groupId>
|
||||||
<artifactId>github-api</artifactId>
|
<artifactId>github-api</artifactId>
|
||||||
<version>1.111</version>
|
<version>1.116</version>
|
||||||
<name>GitHub API for Java</name>
|
<name>GitHub API for Java</name>
|
||||||
<url>https://github-api.kohsuke.org/</url>
|
<url>https://github-api.kohsuke.org/</url>
|
||||||
<description>GitHub API for Java</description>
|
<description>GitHub API for Java</description>
|
||||||
|
|
||||||
<scm>
|
<scm>
|
||||||
<connection>scm:git:git@github.com/github-api/${project.artifactId}.git</connection>
|
<connection>scm:git:git@github.com/hub4j/${project.artifactId}.git</connection>
|
||||||
<developerConnection>scm:git:ssh://git@github.com/github-api/${project.artifactId}.git</developerConnection>
|
<developerConnection>scm:git:ssh://git@github.com/hub4j/${project.artifactId}.git</developerConnection>
|
||||||
<url>https://github.com/github-api/github-api/</url>
|
<url>https://github.com/hub4j/github-api/</url>
|
||||||
<tag>github-api-1.111</tag>
|
<tag>github-api-1.116</tag>
|
||||||
</scm>
|
</scm>
|
||||||
|
|
||||||
<distributionManagement>
|
<distributionManagement>
|
||||||
@@ -27,14 +27,14 @@
|
|||||||
</repository>
|
</repository>
|
||||||
<site>
|
<site>
|
||||||
<id>github-pages</id>
|
<id>github-pages</id>
|
||||||
<url>gitsite:git@github.com/github-api/${project.artifactId}.git</url>
|
<url>gitsite:git@github.com/hub4j/${project.artifactId}.git</url>
|
||||||
</site>
|
</site>
|
||||||
</distributionManagement>
|
</distributionManagement>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<spotbugs-maven-plugin.version>4.0.0</spotbugs-maven-plugin.version>
|
<spotbugs-maven-plugin.version>4.0.4</spotbugs-maven-plugin.version>
|
||||||
<spotbugs.version>4.0.2</spotbugs.version>
|
<spotbugs.version>4.1.1</spotbugs.version>
|
||||||
<spotbugs-maven-plugin.failOnError>true</spotbugs-maven-plugin.failOnError>
|
<spotbugs-maven-plugin.failOnError>true</spotbugs-maven-plugin.failOnError>
|
||||||
<hamcrest.version>2.2</hamcrest.version>
|
<hamcrest.version>2.2</hamcrest.version>
|
||||||
<okhttp3.version>4.4.1</okhttp3.version>
|
<okhttp3.version>4.4.1</okhttp3.version>
|
||||||
@@ -79,6 +79,14 @@
|
|||||||
</testResources>
|
</testResources>
|
||||||
<pluginManagement>
|
<pluginManagement>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>2.22.2</version>
|
||||||
|
<configuration>
|
||||||
|
<!-- SUREFIRE-1226 workaround -->
|
||||||
|
<trimStackTrace>false</trimStackTrace>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-source-plugin</artifactId>
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
@@ -233,7 +241,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-site-plugin</artifactId>
|
<artifactId>maven-site-plugin</artifactId>
|
||||||
<version>3.9.0</version>
|
<version>3.9.1</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
@@ -253,12 +261,12 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-project-info-reports-plugin</artifactId>
|
<artifactId>maven-project-info-reports-plugin</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.1.0</version>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.bcel</groupId>
|
<groupId>org.apache.bcel</groupId>
|
||||||
<artifactId>bcel</artifactId>
|
<artifactId>bcel</artifactId>
|
||||||
<version>6.4.1</version>
|
<version>6.5.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</plugin>
|
</plugin>
|
||||||
@@ -280,16 +288,31 @@
|
|||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>2.22.2</version>
|
<executions>
|
||||||
<configuration>
|
<execution>
|
||||||
<!-- SUREFIRE-1226 workaround -->
|
<id>default-test</id>
|
||||||
<trimStackTrace>false</trimStackTrace>
|
<configuration>
|
||||||
</configuration>
|
<excludesFile>src/test/resources/slow-or-flaky-tests.txt</excludesFile>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>slow-or-flaky-test</id>
|
||||||
|
<phase>test</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>test</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<rerunFailingTestsCount>2</rerunFailingTestsCount>
|
||||||
|
<!-- There are some tests that take longer or are a little flaky. Run them here. -->
|
||||||
|
<includesFile>src/test/resources/slow-or-flaky-tests.txt</includesFile>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>animal-sniffer-maven-plugin</artifactId>
|
<artifactId>animal-sniffer-maven-plugin</artifactId>
|
||||||
<version>1.18</version>
|
<version>1.19</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<signature>
|
<signature>
|
||||||
<groupId>org.codehaus.mojo.signature</groupId>
|
<groupId>org.codehaus.mojo.signature</groupId>
|
||||||
@@ -322,7 +345,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>net.revelc.code.formatter</groupId>
|
<groupId>net.revelc.code.formatter</groupId>
|
||||||
<artifactId>formatter-maven-plugin</artifactId>
|
<artifactId>formatter-maven-plugin</artifactId>
|
||||||
<version>2.11.0</version>
|
<version>2.12.1</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<goals>
|
<goals>
|
||||||
@@ -330,6 +353,7 @@
|
|||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<configFile>src/main/resources/eclipse/formatter.xml</configFile>
|
<configFile>src/main/resources/eclipse/formatter.xml</configFile>
|
||||||
|
<cachedir>${project.build.directory}/.cache</cachedir>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
@@ -337,7 +361,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>net.revelc.code</groupId>
|
<groupId>net.revelc.code</groupId>
|
||||||
<artifactId>impsort-maven-plugin</artifactId>
|
<artifactId>impsort-maven-plugin</artifactId>
|
||||||
<version>1.3.2</version>
|
<version>1.4.1</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<groups>*,java.,javax.</groups>
|
<groups>*,java.,javax.</groups>
|
||||||
<removeUnused>true</removeUnused>
|
<removeUnused>true</removeUnused>
|
||||||
@@ -445,7 +469,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.kohsuke.stapler</groupId>
|
<groupId>org.kohsuke.stapler</groupId>
|
||||||
<artifactId>stapler</artifactId>
|
<artifactId>stapler</artifactId>
|
||||||
<version>1.259</version>
|
<version>1.260</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -495,7 +519,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mockito</groupId>
|
<groupId>org.mockito</groupId>
|
||||||
<artifactId>mockito-core</artifactId>
|
<artifactId>mockito-core</artifactId>
|
||||||
<version>3.3.3</version>
|
<version>3.4.6</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -507,7 +531,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.tomakehurst</groupId>
|
<groupId>com.github.tomakehurst</groupId>
|
||||||
<artifactId>wiremock-jre8-standalone</artifactId>
|
<artifactId>wiremock-jre8-standalone</artifactId>
|
||||||
<version>2.26.3</version>
|
<version>2.27.1</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -39,7 +39,9 @@ public class GHApp extends GHObject {
|
|||||||
*
|
*
|
||||||
* @param owner
|
* @param owner
|
||||||
* the owner
|
* the owner
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setOwner(GHUser owner) {
|
public void setOwner(GHUser owner) {
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
}
|
}
|
||||||
@@ -58,7 +60,9 @@ public class GHApp extends GHObject {
|
|||||||
*
|
*
|
||||||
* @param name
|
* @param name
|
||||||
* the name
|
* the name
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setName(String name) {
|
public void setName(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
@@ -77,7 +81,9 @@ public class GHApp extends GHObject {
|
|||||||
*
|
*
|
||||||
* @param description
|
* @param description
|
||||||
* the description
|
* the description
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setDescription(String description) {
|
public void setDescription(String description) {
|
||||||
this.description = description;
|
this.description = description;
|
||||||
}
|
}
|
||||||
@@ -96,7 +102,9 @@ public class GHApp extends GHObject {
|
|||||||
*
|
*
|
||||||
* @param externalUrl
|
* @param externalUrl
|
||||||
* the external url
|
* the external url
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setExternalUrl(String externalUrl) {
|
public void setExternalUrl(String externalUrl) {
|
||||||
this.externalUrl = externalUrl;
|
this.externalUrl = externalUrl;
|
||||||
}
|
}
|
||||||
@@ -115,7 +123,9 @@ public class GHApp extends GHObject {
|
|||||||
*
|
*
|
||||||
* @param events
|
* @param events
|
||||||
* the events
|
* the events
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setEvents(List<GHEvent> events) {
|
public void setEvents(List<GHEvent> events) {
|
||||||
this.events = events;
|
this.events = events;
|
||||||
}
|
}
|
||||||
@@ -134,7 +144,9 @@ public class GHApp extends GHObject {
|
|||||||
*
|
*
|
||||||
* @param installationsCount
|
* @param installationsCount
|
||||||
* the installations count
|
* the installations count
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setInstallationsCount(long installationsCount) {
|
public void setInstallationsCount(long installationsCount) {
|
||||||
this.installationsCount = installationsCount;
|
this.installationsCount = installationsCount;
|
||||||
}
|
}
|
||||||
@@ -157,7 +169,9 @@ public class GHApp extends GHObject {
|
|||||||
*
|
*
|
||||||
* @param permissions
|
* @param permissions
|
||||||
* the permissions
|
* the permissions
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setPermissions(Map<String, String> permissions) {
|
public void setPermissions(Map<String, String> permissions) {
|
||||||
this.permissions = permissions;
|
this.permissions = permissions;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,9 @@ public class GHAppInstallation extends GHObject {
|
|||||||
*
|
*
|
||||||
* @param root
|
* @param root
|
||||||
* the root
|
* the root
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setRoot(GitHub root) {
|
public void setRoot(GitHub root) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
}
|
}
|
||||||
@@ -78,7 +80,9 @@ public class GHAppInstallation extends GHObject {
|
|||||||
*
|
*
|
||||||
* @param account
|
* @param account
|
||||||
* the account
|
* the account
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setAccount(GHUser account) {
|
public void setAccount(GHUser account) {
|
||||||
this.account = account;
|
this.account = account;
|
||||||
}
|
}
|
||||||
@@ -97,7 +101,9 @@ public class GHAppInstallation extends GHObject {
|
|||||||
*
|
*
|
||||||
* @param accessTokenUrl
|
* @param accessTokenUrl
|
||||||
* the access token url
|
* the access token url
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setAccessTokenUrl(String accessTokenUrl) {
|
public void setAccessTokenUrl(String accessTokenUrl) {
|
||||||
this.accessTokenUrl = accessTokenUrl;
|
this.accessTokenUrl = accessTokenUrl;
|
||||||
}
|
}
|
||||||
@@ -116,7 +122,9 @@ public class GHAppInstallation extends GHObject {
|
|||||||
*
|
*
|
||||||
* @param repositoriesUrl
|
* @param repositoriesUrl
|
||||||
* the repositories url
|
* the repositories url
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setRepositoriesUrl(String repositoriesUrl) {
|
public void setRepositoriesUrl(String repositoriesUrl) {
|
||||||
this.repositoriesUrl = repositoriesUrl;
|
this.repositoriesUrl = repositoriesUrl;
|
||||||
}
|
}
|
||||||
@@ -135,7 +143,9 @@ public class GHAppInstallation extends GHObject {
|
|||||||
*
|
*
|
||||||
* @param appId
|
* @param appId
|
||||||
* the app id
|
* the app id
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setAppId(long appId) {
|
public void setAppId(long appId) {
|
||||||
this.appId = appId;
|
this.appId = appId;
|
||||||
}
|
}
|
||||||
@@ -154,7 +164,9 @@ public class GHAppInstallation extends GHObject {
|
|||||||
*
|
*
|
||||||
* @param targetId
|
* @param targetId
|
||||||
* the target id
|
* the target id
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setTargetId(long targetId) {
|
public void setTargetId(long targetId) {
|
||||||
this.targetId = targetId;
|
this.targetId = targetId;
|
||||||
}
|
}
|
||||||
@@ -173,7 +185,9 @@ public class GHAppInstallation extends GHObject {
|
|||||||
*
|
*
|
||||||
* @param targetType
|
* @param targetType
|
||||||
* the target type
|
* the target type
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setTargetType(GHTargetType targetType) {
|
public void setTargetType(GHTargetType targetType) {
|
||||||
this.targetType = targetType;
|
this.targetType = targetType;
|
||||||
}
|
}
|
||||||
@@ -192,7 +206,9 @@ public class GHAppInstallation extends GHObject {
|
|||||||
*
|
*
|
||||||
* @param permissions
|
* @param permissions
|
||||||
* the permissions
|
* the permissions
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setPermissions(Map<String, GHPermissionType> permissions) {
|
public void setPermissions(Map<String, GHPermissionType> permissions) {
|
||||||
this.permissions = permissions;
|
this.permissions = permissions;
|
||||||
}
|
}
|
||||||
@@ -211,7 +227,9 @@ public class GHAppInstallation extends GHObject {
|
|||||||
*
|
*
|
||||||
* @param events
|
* @param events
|
||||||
* the events
|
* the events
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setEvents(List<GHEvent> events) {
|
public void setEvents(List<GHEvent> events) {
|
||||||
this.events = events;
|
this.events = events;
|
||||||
}
|
}
|
||||||
@@ -230,7 +248,9 @@ public class GHAppInstallation extends GHObject {
|
|||||||
*
|
*
|
||||||
* @param singleFileName
|
* @param singleFileName
|
||||||
* the single file name
|
* the single file name
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setSingleFileName(String singleFileName) {
|
public void setSingleFileName(String singleFileName) {
|
||||||
this.singleFileName = singleFileName;
|
this.singleFileName = singleFileName;
|
||||||
}
|
}
|
||||||
@@ -249,7 +269,9 @@ public class GHAppInstallation extends GHObject {
|
|||||||
*
|
*
|
||||||
* @param repositorySelection
|
* @param repositorySelection
|
||||||
* the repository selection
|
* the repository selection
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setRepositorySelection(GHRepositorySelection repositorySelection) {
|
public void setRepositorySelection(GHRepositorySelection repositorySelection) {
|
||||||
this.repositorySelection = repositorySelection;
|
this.repositorySelection = repositorySelection;
|
||||||
}
|
}
|
||||||
@@ -274,7 +296,7 @@ public class GHAppInstallation extends GHObject {
|
|||||||
root.createRequest()
|
root.createRequest()
|
||||||
.method("DELETE")
|
.method("DELETE")
|
||||||
.withPreview(GAMBIT)
|
.withPreview(GAMBIT)
|
||||||
.withUrlPath(String.format("/app/installations/%d", id))
|
.withUrlPath(String.format("/app/installations/%d", getId()))
|
||||||
.send();
|
.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +315,9 @@ public class GHAppInstallation extends GHObject {
|
|||||||
@Preview
|
@Preview
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public GHAppCreateTokenBuilder createToken(Map<String, GHPermissionType> permissions) {
|
public GHAppCreateTokenBuilder createToken(Map<String, GHPermissionType> permissions) {
|
||||||
return new GHAppCreateTokenBuilder(root, String.format("/app/installations/%d/access_tokens", id), permissions);
|
return new GHAppCreateTokenBuilder(root,
|
||||||
|
String.format("/app/installations/%d/access_tokens", getId()),
|
||||||
|
permissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -308,6 +332,6 @@ public class GHAppInstallation extends GHObject {
|
|||||||
@Preview
|
@Preview
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public GHAppCreateTokenBuilder createToken() {
|
public GHAppCreateTokenBuilder createToken() {
|
||||||
return new GHAppCreateTokenBuilder(root, String.format("/app/installations/%d/access_tokens", id));
|
return new GHAppCreateTokenBuilder(root, String.format("/app/installations/%d/access_tokens", getId()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ public class GHAppInstallationToken {
|
|||||||
*
|
*
|
||||||
* @param root
|
* @param root
|
||||||
* the root
|
* the root
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setRoot(GitHub root) {
|
public void setRoot(GitHub root) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
}
|
}
|
||||||
@@ -56,7 +58,9 @@ public class GHAppInstallationToken {
|
|||||||
*
|
*
|
||||||
* @param permissions
|
* @param permissions
|
||||||
* the permissions
|
* the permissions
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setPermissions(Map<String, String> permissions) {
|
public void setPermissions(Map<String, String> permissions) {
|
||||||
this.permissions = permissions;
|
this.permissions = permissions;
|
||||||
}
|
}
|
||||||
@@ -75,7 +79,9 @@ public class GHAppInstallationToken {
|
|||||||
*
|
*
|
||||||
* @param token
|
* @param token
|
||||||
* the token
|
* the token
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setToken(String token) {
|
public void setToken(String token) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
@@ -94,7 +100,9 @@ public class GHAppInstallationToken {
|
|||||||
*
|
*
|
||||||
* @param repositories
|
* @param repositories
|
||||||
* the repositories
|
* the repositories
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setRepositories(List<GHRepository> repositories) {
|
public void setRepositories(List<GHRepository> repositories) {
|
||||||
this.repositories = repositories;
|
this.repositories = repositories;
|
||||||
}
|
}
|
||||||
@@ -113,7 +121,9 @@ public class GHAppInstallationToken {
|
|||||||
*
|
*
|
||||||
* @param repositorySelection
|
* @param repositorySelection
|
||||||
* the repository selection
|
* the repository selection
|
||||||
|
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setRepositorySelection(GHRepositorySelection repositorySelection) {
|
public void setRepositorySelection(GHRepositorySelection repositorySelection) {
|
||||||
this.repositorySelection = repositorySelection;
|
this.repositorySelection = repositorySelection;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ public class GHAsset extends GHObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getApiRoute() {
|
private String getApiRoute() {
|
||||||
return "/repos/" + owner.getOwnerName() + "/" + owner.getName() + "/releases/assets/" + id;
|
return "/repos/" + owner.getOwnerName() + "/" + owner.getName() + "/releases/assets/" + getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
GHAsset wrap(GHRelease release) {
|
GHAsset wrap(GHRelease release) {
|
||||||
|
|||||||
@@ -112,10 +112,12 @@ public class GHAuthorization extends GHObject {
|
|||||||
* Gets api url.
|
* Gets api url.
|
||||||
*
|
*
|
||||||
* @return the api url
|
* @return the api url
|
||||||
|
* @deprecated use {@link #getUrl()}
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@SuppressFBWarnings(value = "NM_CONFUSING", justification = "It's a part of the library API, cannot be changed")
|
@SuppressFBWarnings(value = "NM_CONFUSING", justification = "It's a part of the library API, cannot be changed")
|
||||||
public URL getApiURL() {
|
public URL getApiURL() {
|
||||||
return GitHubClient.parseURL(url);
|
return getUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import java.net.URL;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import javax.annotation.CheckForNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A branch in a repository.
|
* A branch in a repository.
|
||||||
*
|
*
|
||||||
@@ -101,7 +103,11 @@ public class GHBranch {
|
|||||||
* the io exception
|
* the io exception
|
||||||
*/
|
*/
|
||||||
public GHBranchProtection getProtection() throws IOException {
|
public GHBranchProtection getProtection() throws IOException {
|
||||||
return root.createRequest().withUrlPath(protection_url).fetch(GHBranchProtection.class).wrap(this);
|
return root.createRequest()
|
||||||
|
.withPreview(Previews.LUKE_CAGE)
|
||||||
|
.setRawUrlPath(protection_url)
|
||||||
|
.fetch(GHBranchProtection.class)
|
||||||
|
.wrap(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -120,7 +126,7 @@ public class GHBranch {
|
|||||||
* if disabling protection fails
|
* if disabling protection fails
|
||||||
*/
|
*/
|
||||||
public void disableProtection() throws IOException {
|
public void disableProtection() throws IOException {
|
||||||
root.createRequest().method("DELETE").withUrlPath(protection_url).send();
|
root.createRequest().method("DELETE").setRawUrlPath(protection_url).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -161,6 +167,59 @@ public class GHBranch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge a branch into this branch.
|
||||||
|
*
|
||||||
|
* @param headBranch
|
||||||
|
* the branch whose head will be merged
|
||||||
|
*
|
||||||
|
* @param commitMessage
|
||||||
|
* the commit message
|
||||||
|
*
|
||||||
|
* @return the merge {@link GHCommit} created, or {@code null} if the base already contains the head (nothing to
|
||||||
|
* merge).
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* if merging fails
|
||||||
|
*/
|
||||||
|
@CheckForNull
|
||||||
|
public GHCommit merge(GHBranch headBranch, String commitMessage) throws IOException {
|
||||||
|
return merge(headBranch.getName(), commitMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge a ref into this branch.
|
||||||
|
*
|
||||||
|
* @param head
|
||||||
|
* the ref name that will be merged into this branch. Follows the usual ref naming rules, could be a
|
||||||
|
* branch name, tag, or commit sha.
|
||||||
|
*
|
||||||
|
* @param commitMessage
|
||||||
|
* the commit message
|
||||||
|
*
|
||||||
|
* @return the merge {@link GHCommit} created, or {@code null} if the base already contains the head (nothing to
|
||||||
|
* merge).
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* if merging fails
|
||||||
|
*/
|
||||||
|
@CheckForNull
|
||||||
|
public GHCommit merge(String head, String commitMessage) throws IOException {
|
||||||
|
GHCommit result = root.createRequest()
|
||||||
|
.withUrlPath(owner.getApiTailUrl("merges"))
|
||||||
|
.method("POST")
|
||||||
|
.with("commit_message", commitMessage)
|
||||||
|
.with("base", this.name)
|
||||||
|
.with("head", head)
|
||||||
|
.fetch(GHCommit.class);
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
result.wrapUp(owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
String getApiRoute() {
|
String getApiRoute() {
|
||||||
return owner.getApiTailUrl("/branches/" + name);
|
return owner.getApiTailUrl("/branches/" + name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import static org.kohsuke.github.Previews.ZZZAX;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The type GHBranchProtection.
|
* The type GHBranchProtection.
|
||||||
|
*
|
||||||
|
* @see <a href="https://docs.github.com/en/rest/reference/repos#get-branch-protection">GitHub Branch Protection</a>
|
||||||
*/
|
*/
|
||||||
@SuppressFBWarnings(
|
@SuppressFBWarnings(
|
||||||
value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD",
|
value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD",
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
package org.kohsuke.github;
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a check run.
|
* Represents a check run.
|
||||||
@@ -14,6 +18,8 @@ import java.util.Date;
|
|||||||
@SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD", "URF_UNREAD_FIELD" },
|
@SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD", "URF_UNREAD_FIELD" },
|
||||||
justification = "JSON API")
|
justification = "JSON API")
|
||||||
public class GHCheckRun extends GHObject {
|
public class GHCheckRun extends GHObject {
|
||||||
|
|
||||||
|
@JsonProperty("repository")
|
||||||
GHRepository owner;
|
GHRepository owner;
|
||||||
GitHub root;
|
GitHub root;
|
||||||
|
|
||||||
@@ -34,7 +40,7 @@ public class GHCheckRun extends GHObject {
|
|||||||
|
|
||||||
GHCheckRun wrap(GHRepository owner) {
|
GHCheckRun wrap(GHRepository owner) {
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.root = owner.root;
|
wrap(owner.root);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +48,24 @@ public class GHCheckRun extends GHObject {
|
|||||||
this.root = root;
|
this.root = root;
|
||||||
if (owner != null) {
|
if (owner != null) {
|
||||||
owner.wrap(root);
|
owner.wrap(root);
|
||||||
|
if (pullRequests != null && pullRequests.length != 0) {
|
||||||
|
for (GHPullRequest singlePull : pullRequests) {
|
||||||
|
singlePull.wrap(owner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
if (checkSuite != null) {
|
||||||
|
if (owner != null) {
|
||||||
|
checkSuite.wrap(owner);
|
||||||
|
} else {
|
||||||
|
checkSuite.wrap(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (app != null) {
|
||||||
|
app.wrapUp(root);
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,8 +97,14 @@ public class GHCheckRun extends GHObject {
|
|||||||
return conclusion;
|
return conclusion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Final conclusion of the check.
|
||||||
|
*
|
||||||
|
* From <a href="https://docs.github.com/en/rest/reference/checks#create-a-check-run--parameters">Check Run
|
||||||
|
* Parameters - <code>conclusion</code></a>.
|
||||||
|
*/
|
||||||
public static enum Conclusion {
|
public static enum Conclusion {
|
||||||
SUCCESS, FAILURE, NEUTRAL, CANCELLED, TIMED_OUT, ACTION_REQUIRED
|
SUCCESS, FAILURE, NEUTRAL, CANCELLED, TIMED_OUT, ACTION_REQUIRED, SKIPPED
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -99,15 +128,22 @@ public class GHCheckRun extends GHObject {
|
|||||||
/**
|
/**
|
||||||
* Gets the pull requests participated in this check run.
|
* Gets the pull requests participated in this check run.
|
||||||
*
|
*
|
||||||
* @return Pull requests of this check run
|
* 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
|
||||||
*/
|
*/
|
||||||
GHPullRequest[] getPullRequests() throws IOException {
|
public List<GHPullRequest> getPullRequests() throws IOException {
|
||||||
if (pullRequests != null && pullRequests.length != 0) {
|
if (pullRequests != null && pullRequests.length != 0) {
|
||||||
for (GHPullRequest singlePull : pullRequests) {
|
for (GHPullRequest singlePull : pullRequests) {
|
||||||
singlePull.refresh();
|
// Only refresh if we haven't do so before
|
||||||
|
singlePull.refresh(singlePull.getTitle());
|
||||||
}
|
}
|
||||||
|
return Collections.unmodifiableList(Arrays.asList(pullRequests));
|
||||||
}
|
}
|
||||||
return pullRequests;
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ public final class GHCheckRunBuilder {
|
|||||||
.withPreview(Previews.ANTIOPE)
|
.withPreview(Previews.ANTIOPE)
|
||||||
.method("PATCH")
|
.method("PATCH")
|
||||||
.with("output", output2)
|
.with("output", output2)
|
||||||
.withUrlPath(repo.getApiTailUrl("check-runs/" + run.id))
|
.withUrlPath(repo.getApiTailUrl("check-runs/" + run.getId()))
|
||||||
.fetch(GHCheckRun.class)
|
.fetch(GHCheckRun.class)
|
||||||
.wrap(repo);
|
.wrap(repo);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
package org.kohsuke.github;
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a check suite.
|
* Represents a check suite.
|
||||||
@@ -14,6 +18,8 @@ import java.util.Date;
|
|||||||
@SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD", "URF_UNREAD_FIELD" },
|
@SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD", "URF_UNREAD_FIELD" },
|
||||||
justification = "JSON API")
|
justification = "JSON API")
|
||||||
public class GHCheckSuite extends GHObject {
|
public class GHCheckSuite extends GHObject {
|
||||||
|
|
||||||
|
@JsonProperty("repository")
|
||||||
GHRepository owner;
|
GHRepository owner;
|
||||||
GitHub root;
|
GitHub root;
|
||||||
|
|
||||||
@@ -32,7 +38,7 @@ public class GHCheckSuite extends GHObject {
|
|||||||
|
|
||||||
GHCheckSuite wrap(GHRepository owner) {
|
GHCheckSuite wrap(GHRepository owner) {
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.root = owner.root;
|
this.wrap(owner.root);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,6 +46,14 @@ public class GHCheckSuite extends GHObject {
|
|||||||
this.root = root;
|
this.root = root;
|
||||||
if (owner != null) {
|
if (owner != null) {
|
||||||
owner.wrap(root);
|
owner.wrap(root);
|
||||||
|
if (pullRequests != null && pullRequests.length != 0) {
|
||||||
|
for (GHPullRequest singlePull : pullRequests) {
|
||||||
|
singlePull.wrap(owner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (app != null) {
|
||||||
|
app.wrapUp(root);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -153,15 +167,22 @@ public class GHCheckSuite extends GHObject {
|
|||||||
/**
|
/**
|
||||||
* Gets the pull requests participated in this check suite.
|
* Gets the pull requests participated in this check suite.
|
||||||
*
|
*
|
||||||
* @return Pull requests
|
* Note this field is only populated for events. When getting a {@link GHCheckSuite} outside of an event, this is
|
||||||
|
* always empty.
|
||||||
|
*
|
||||||
|
* @return the list of {@link GHPullRequest}s for this check suite. Only populated for events.
|
||||||
|
* @throws IOException
|
||||||
|
* the io exception
|
||||||
*/
|
*/
|
||||||
GHPullRequest[] getPullRequests() throws IOException {
|
public List<GHPullRequest> getPullRequests() throws IOException {
|
||||||
if (pullRequests != null && pullRequests.length != 0) {
|
if (pullRequests != null && pullRequests.length != 0) {
|
||||||
for (GHPullRequest singlePull : pullRequests) {
|
for (GHPullRequest singlePull : pullRequests) {
|
||||||
singlePull.refresh();
|
// Only refresh if we haven't do so before
|
||||||
|
singlePull.refresh(singlePull.getTitle());
|
||||||
}
|
}
|
||||||
|
return Collections.unmodifiableList(Arrays.asList(pullRequests));
|
||||||
}
|
}
|
||||||
return pullRequests;
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ public class GHCommitComment extends GHObject implements Reactable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getApiTail() {
|
private String getApiTail() {
|
||||||
return String.format("/repos/%s/%s/comments/%s", owner.getOwnerName(), owner.getName(), id);
|
return String.format("/repos/%s/%s/comments/%s", owner.getOwnerName(), owner.getName(), getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
GHCommitComment wrap(GHRepository owner) {
|
GHCommitComment wrap(GHRepository owner) {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public class GHContent implements Refreshable {
|
|||||||
private String sha;
|
private String sha;
|
||||||
private String name;
|
private String name;
|
||||||
private String path;
|
private String path;
|
||||||
|
private String target;
|
||||||
private String content;
|
private String content;
|
||||||
private String url; // this is the API url
|
private String url; // this is the API url
|
||||||
private String git_url; // this is the Blob url
|
private String git_url; // this is the Blob url
|
||||||
@@ -99,6 +100,15 @@ public class GHContent implements Refreshable {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets target of a symlink. This will only be set if {@code "symlink".equals(getType())}
|
||||||
|
*
|
||||||
|
* @return the target
|
||||||
|
*/
|
||||||
|
public String getTarget() {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the decoded content that is stored at this location.
|
* Retrieve the decoded content that is stored at this location.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package org.kohsuke.github;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
|
import static org.kohsuke.github.Previews.BAPTISE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a repository
|
* Creates a repository
|
||||||
*
|
*
|
||||||
@@ -11,7 +13,7 @@ import java.net.URL;
|
|||||||
public class GHCreateRepositoryBuilder {
|
public class GHCreateRepositoryBuilder {
|
||||||
private final GitHub root;
|
private final GitHub root;
|
||||||
protected final Requester builder;
|
protected final Requester builder;
|
||||||
private final String apiUrlTail;
|
private String apiUrlTail;
|
||||||
|
|
||||||
GHCreateRepositoryBuilder(GitHub root, String apiUrlTail, String name) {
|
GHCreateRepositoryBuilder(GitHub root, String apiUrlTail, String name) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
@@ -200,6 +202,51 @@ public class GHCreateRepositoryBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies whether the repository is a template.
|
||||||
|
*
|
||||||
|
* @param enabled
|
||||||
|
* true if enabled
|
||||||
|
* @return a builder to continue with building
|
||||||
|
*/
|
||||||
|
@Preview
|
||||||
|
@Deprecated
|
||||||
|
public GHCreateRepositoryBuilder templateRepository(boolean enabled) {
|
||||||
|
this.builder.withPreview(BAPTISE);
|
||||||
|
this.builder.with("is_template", enabled);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the ownership of the repository.
|
||||||
|
*
|
||||||
|
* @param owner
|
||||||
|
* organization or personage
|
||||||
|
* @return a builder to continue with building
|
||||||
|
*/
|
||||||
|
public GHCreateRepositoryBuilder owner(String owner) {
|
||||||
|
this.builder.with("owner", owner);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create repository from template repository.
|
||||||
|
*
|
||||||
|
* @param templateOwner
|
||||||
|
* template repository owner
|
||||||
|
* @param templateRepo
|
||||||
|
* template repository
|
||||||
|
* @return a builder to continue with building
|
||||||
|
* @see <a href="https://developer.github.com/v3/previews/">GitHub API Previews</a>
|
||||||
|
*/
|
||||||
|
@Preview
|
||||||
|
@Deprecated
|
||||||
|
public GHCreateRepositoryBuilder fromTemplateRepository(String templateOwner, String templateRepo) {
|
||||||
|
this.builder.withPreview(BAPTISE);
|
||||||
|
this.apiUrlTail = "/repos/" + templateOwner + "/" + templateRepo + "/generate";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a repository with all the parameters.
|
* Creates a repository with all the parameters.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.kohsuke.github;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a deployment
|
* Represents a deployment
|
||||||
@@ -60,7 +61,8 @@ public class GHDeployment extends GHObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets payload.
|
* Gets payload. <b>NOTE:</b> only use this method if you can guarantee the payload will be a simple string,
|
||||||
|
* otherwise use {@link #getPayloadObject()}.
|
||||||
*
|
*
|
||||||
* @return the payload
|
* @return the payload
|
||||||
*/
|
*/
|
||||||
@@ -68,6 +70,25 @@ public class GHDeployment extends GHObject {
|
|||||||
return (String) payload;
|
return (String) payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets payload. <b>NOTE:</b> only use this method if you can guarantee the payload will be a JSON object (Map),
|
||||||
|
* otherwise use {@link #getPayloadObject()}.
|
||||||
|
*
|
||||||
|
* @return the payload
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getPayloadMap() {
|
||||||
|
return (Map<String, Object>) payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets payload without assuming its type. It could be a String or a Map.
|
||||||
|
*
|
||||||
|
* @return the payload
|
||||||
|
*/
|
||||||
|
public Object getPayloadObject() {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets environment.
|
* Gets environment.
|
||||||
*
|
*
|
||||||
@@ -122,7 +143,7 @@ public class GHDeployment extends GHObject {
|
|||||||
* @return the gh deployment status builder
|
* @return the gh deployment status builder
|
||||||
*/
|
*/
|
||||||
public GHDeploymentStatusBuilder createStatus(GHDeploymentState state) {
|
public GHDeploymentStatusBuilder createStatus(GHDeploymentState state) {
|
||||||
return new GHDeploymentStatusBuilder(owner, id, state);
|
return new GHDeploymentStatusBuilder(owner, getId(), state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
232
src/main/java/org/kohsuke/github/GHDiscussion.java
Normal file
232
src/main/java/org/kohsuke/github/GHDiscussion.java
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import javax.annotation.CheckForNull;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A discussion in GitHub Team.
|
||||||
|
*
|
||||||
|
* @author Charles Moulliard
|
||||||
|
* @see <a href="https://developer.github.com/v3/teams/discussions">GitHub Team Discussions</a>
|
||||||
|
*/
|
||||||
|
public class GHDiscussion extends GHObject {
|
||||||
|
|
||||||
|
@JacksonInject
|
||||||
|
private GitHub root;
|
||||||
|
private GHTeam team;
|
||||||
|
private long number;
|
||||||
|
private String body, title, htmlUrl;
|
||||||
|
|
||||||
|
@JsonProperty(value = "private")
|
||||||
|
private boolean isPrivate;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URL getHtmlUrl() throws IOException {
|
||||||
|
return GitHubClient.parseURL(htmlUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
GHDiscussion wrapUp(GHTeam team) {
|
||||||
|
this.team = team;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the team to which this discussion belongs.
|
||||||
|
*
|
||||||
|
* @return the team for this discussion
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public GHTeam getTeam() {
|
||||||
|
return team;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title of the discussion.
|
||||||
|
*
|
||||||
|
* @return the title
|
||||||
|
*/
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The description of this discussion.
|
||||||
|
*
|
||||||
|
* @return the body
|
||||||
|
*/
|
||||||
|
public String getBody() {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of this discussion.
|
||||||
|
*
|
||||||
|
* @return the number
|
||||||
|
*/
|
||||||
|
public long getNumber() {
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id number of this discussion. GitHub discussions have "number" instead of "id". This is provided for
|
||||||
|
* convenience.
|
||||||
|
*
|
||||||
|
* @return the id number for this discussion
|
||||||
|
* @see #getNumber()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long getId() {
|
||||||
|
return getNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the discussion is private to the team.
|
||||||
|
*
|
||||||
|
* @return {@code true} if discussion is private.
|
||||||
|
*/
|
||||||
|
public boolean isPrivate() {
|
||||||
|
return isPrivate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begins the creation of a new instance.
|
||||||
|
*
|
||||||
|
* Consumer must call {@link GHDiscussion.Creator#done()} to commit changes.
|
||||||
|
*
|
||||||
|
* @param team
|
||||||
|
* the team in which the discussion will be created.
|
||||||
|
* @return a {@link GHLabel.Creator}
|
||||||
|
* @throws IOException
|
||||||
|
* the io exception
|
||||||
|
*/
|
||||||
|
static GHDiscussion.Creator create(GHTeam team) throws IOException {
|
||||||
|
return new GHDiscussion.Creator(team);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GHDiscussion read(GHTeam team, long discussionNumber) throws IOException {
|
||||||
|
return team.root.createRequest()
|
||||||
|
.setRawUrlPath(getRawUrlPath(team, discussionNumber))
|
||||||
|
.fetch(GHDiscussion.class)
|
||||||
|
.wrapUp(team);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PagedIterable<GHDiscussion> readAll(GHTeam team) throws IOException {
|
||||||
|
return team.root.createRequest()
|
||||||
|
.setRawUrlPath(getRawUrlPath(team, null))
|
||||||
|
.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}
|
||||||
|
*/
|
||||||
|
@Preview
|
||||||
|
@Deprecated
|
||||||
|
public GHDiscussion.Updater update() {
|
||||||
|
return new GHDiscussion.Updater(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begins a single property update.
|
||||||
|
*
|
||||||
|
* @return a {@link GHDiscussion.Setter}
|
||||||
|
*/
|
||||||
|
@Preview
|
||||||
|
@Deprecated
|
||||||
|
public GHDiscussion.Setter set() {
|
||||||
|
return new GHDiscussion.Setter(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the discussion
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* the io exception
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link GHLabelBuilder} that updates a single property per request
|
||||||
|
*
|
||||||
|
* {@link #done()} is called automatically after the property is set.
|
||||||
|
*/
|
||||||
|
public static class Setter extends GHDiscussionBuilder<GHDiscussion> {
|
||||||
|
private Setter(@Nonnull GHDiscussion base) {
|
||||||
|
super(GHDiscussion.class, base.team, base);
|
||||||
|
requester.method("PATCH").setRawUrlPath(base.getUrl().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link GHLabelBuilder} that allows multiple properties to be updated per request.
|
||||||
|
*
|
||||||
|
* Consumer must call {@link #done()} to commit changes.
|
||||||
|
*/
|
||||||
|
public static class Updater extends GHDiscussionBuilder<Updater> {
|
||||||
|
private Updater(@Nonnull GHDiscussion base) {
|
||||||
|
super(GHDiscussion.Updater.class, base.team, base);
|
||||||
|
requester.method("PATCH").setRawUrlPath(base.getUrl().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link GHLabelBuilder} that creates a new {@link GHLabel}
|
||||||
|
*
|
||||||
|
* Consumer must call {@link #done()} to create the new instance.
|
||||||
|
*/
|
||||||
|
public static class Creator extends GHDiscussionBuilder<Creator> {
|
||||||
|
|
||||||
|
private Creator(@Nonnull GHTeam team) {
|
||||||
|
super(GHDiscussion.Creator.class, team, null);
|
||||||
|
requester.method("POST").setRawUrlPath(getRawUrlPath(team, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(team, number, body, title);
|
||||||
|
}
|
||||||
|
}
|
||||||
80
src/main/java/org/kohsuke/github/GHDiscussionBuilder.java
Normal file
80
src/main/java/org/kohsuke/github/GHDiscussionBuilder.java
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.annotation.CheckForNull;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for creating or updating a discussion.
|
||||||
|
*
|
||||||
|
* @param <S>
|
||||||
|
* Intermediate return type for this builder returned by calls to {@link #with(String, Object)}. If {@link S}
|
||||||
|
* the same as {@link GHLabel}, this builder will commit changes after each call to
|
||||||
|
* {@link #with(String, Object)}.
|
||||||
|
*/
|
||||||
|
class GHDiscussionBuilder<S> extends AbstractBuilder<GHDiscussion, S> {
|
||||||
|
|
||||||
|
private final GHTeam team;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param intermediateReturnType
|
||||||
|
* Intermediate return type for this builder returned by calls to {@link #with(String, Object)}. If
|
||||||
|
* {@link S} the same as {@link GHDiscussion}, this builder will commit changes after each call to
|
||||||
|
* {@link #with(String, Object)}.
|
||||||
|
* @param team
|
||||||
|
* the GitHub team. Updates will be sent to the root of this team.
|
||||||
|
* @param baseInstance
|
||||||
|
* instance on which to base this builder. If {@code null} a new instance will be created.
|
||||||
|
*/
|
||||||
|
protected GHDiscussionBuilder(@Nonnull Class<S> intermediateReturnType,
|
||||||
|
@Nonnull GHTeam team,
|
||||||
|
@CheckForNull GHDiscussion baseInstance) {
|
||||||
|
super(GHDiscussion.class, intermediateReturnType, team.root, baseInstance);
|
||||||
|
|
||||||
|
this.team = team;
|
||||||
|
|
||||||
|
if (baseInstance != null) {
|
||||||
|
requester.with("title", baseInstance.getTitle());
|
||||||
|
requester.with("body", baseInstance.getBody());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* body of discussion*
|
||||||
|
* @return either a continuing builder or an updated {@link GHDiscussion}
|
||||||
|
* @throws IOException
|
||||||
|
* if there is an I/O Exception
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public S body(String value) throws IOException {
|
||||||
|
return with("body", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public GHDiscussion done() throws IOException {
|
||||||
|
return super.done().wrapUp(team);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,6 +62,8 @@ public enum GHEvent {
|
|||||||
TEAM,
|
TEAM,
|
||||||
TEAM_ADD,
|
TEAM_ADD,
|
||||||
WATCH,
|
WATCH,
|
||||||
|
WORKFLOW_DISPATCH,
|
||||||
|
WORKFLOW_RUN,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Special event type that means "every possible event"
|
* Special event type that means "every possible event"
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ public abstract class GHEventPayload {
|
|||||||
installation.wrapUp(root);
|
installation.wrapUp(root);
|
||||||
|
|
||||||
List<GHRepository> repositories;
|
List<GHRepository> repositories;
|
||||||
if (action == "added")
|
if ("added".equals(action))
|
||||||
repositories = repositoriesAdded;
|
repositories = repositoriesAdded;
|
||||||
else // action == "removed"
|
else // action == "removed"
|
||||||
repositories = repositoriesRemoved;
|
repositories = repositoriesRemoved;
|
||||||
|
|||||||
@@ -50,6 +50,30 @@ public class GHGist extends GHObject {
|
|||||||
this.owner = root.getUser(owner);
|
this.owner = root.getUser(owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 id of the Gist.
|
||||||
|
* @deprecated Use {@link #getGistId()} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public long getId() {
|
||||||
|
return Long.parseLong(getGistId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 id of this Gist
|
||||||
|
*/
|
||||||
|
public String getGistId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets owner.
|
* Gets owner.
|
||||||
*
|
*
|
||||||
@@ -97,6 +121,11 @@ public class GHGist extends GHObject {
|
|||||||
return git_push_url;
|
return git_push_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the html url.
|
||||||
|
*
|
||||||
|
* @return the github html url
|
||||||
|
*/
|
||||||
public URL getHtmlUrl() {
|
public URL getHtmlUrl() {
|
||||||
return GitHubClient.parseURL(html_url);
|
return GitHubClient.parseURL(html_url);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import java.io.IOException;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builder pattern for creating a new Gist.
|
* Builder pattern for creating a new Gist.
|
||||||
*
|
*
|
||||||
@@ -59,7 +61,7 @@ public class GHGistBuilder {
|
|||||||
* the content
|
* the content
|
||||||
* @return Adds a new file.
|
* @return Adds a new file.
|
||||||
*/
|
*/
|
||||||
public GHGistBuilder file(String fileName, String content) {
|
public GHGistBuilder file(@Nonnull String fileName, @Nonnull String content) {
|
||||||
files.put(fileName, Collections.singletonMap("content", content));
|
files.put(fileName, Collections.singletonMap("content", content));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package org.kohsuke.github;
|
package org.kohsuke.github;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builder pattern for updating a Gist.
|
* Builder pattern for updating a Gist.
|
||||||
@@ -12,7 +15,7 @@ import java.util.LinkedHashMap;
|
|||||||
public class GHGistUpdater {
|
public class GHGistUpdater {
|
||||||
private final GHGist base;
|
private final GHGist base;
|
||||||
private final Requester builder;
|
private final Requester builder;
|
||||||
LinkedHashMap<String, Object> files;
|
LinkedHashMap<String, Map<String, String>> files;
|
||||||
|
|
||||||
GHGistUpdater(GHGist base) {
|
GHGistUpdater(GHGist base) {
|
||||||
this.base = base;
|
this.base = base;
|
||||||
@@ -32,16 +35,15 @@ public class GHGistUpdater {
|
|||||||
* @throws IOException
|
* @throws IOException
|
||||||
* the io exception
|
* the io exception
|
||||||
*/
|
*/
|
||||||
public GHGistUpdater addFile(String fileName, String content) throws IOException {
|
public GHGistUpdater addFile(@Nonnull String fileName, @Nonnull String content) throws IOException {
|
||||||
updateFile(fileName, content);
|
updateFile(fileName, content);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// // This method does not work.
|
public GHGistUpdater deleteFile(@Nonnull String fileName) throws IOException {
|
||||||
// public GHGistUpdater deleteFile(String fileName) throws IOException {
|
files.put(fileName, null);
|
||||||
// files.put(fileName, Collections.singletonMap("filename", null));
|
return this;
|
||||||
// return this;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rename file gh gist updater.
|
* Rename file gh gist updater.
|
||||||
@@ -54,8 +56,9 @@ public class GHGistUpdater {
|
|||||||
* @throws IOException
|
* @throws IOException
|
||||||
* the io exception
|
* the io exception
|
||||||
*/
|
*/
|
||||||
public GHGistUpdater renameFile(String fileName, String newFileName) throws IOException {
|
public GHGistUpdater renameFile(@Nonnull String fileName, @Nonnull String newFileName) throws IOException {
|
||||||
files.put(fileName, Collections.singletonMap("filename", newFileName));
|
Map<String, String> file = files.computeIfAbsent(fileName, d -> new HashMap<>());
|
||||||
|
file.put("filename", newFileName);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,8 +73,31 @@ public class GHGistUpdater {
|
|||||||
* @throws IOException
|
* @throws IOException
|
||||||
* the io exception
|
* the io exception
|
||||||
*/
|
*/
|
||||||
public GHGistUpdater updateFile(String fileName, String content) throws IOException {
|
public GHGistUpdater updateFile(@Nonnull String fileName, @Nonnull String content) throws IOException {
|
||||||
files.put(fileName, Collections.singletonMap("content", content));
|
Map<String, String> file = files.computeIfAbsent(fileName, d -> new HashMap<>());
|
||||||
|
file.put("content", content);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update file name and content
|
||||||
|
*
|
||||||
|
* @param fileName
|
||||||
|
* the file name
|
||||||
|
* @param newFileName
|
||||||
|
* the new file name
|
||||||
|
* @param content
|
||||||
|
* the content
|
||||||
|
* @return the gh gist updater
|
||||||
|
* @throws IOException
|
||||||
|
* the io exception
|
||||||
|
*/
|
||||||
|
public GHGistUpdater updateFile(@Nonnull String fileName, @Nonnull String newFileName, @Nonnull String content)
|
||||||
|
throws IOException {
|
||||||
|
Map<String, String> file = files.computeIfAbsent(fileName, d -> new HashMap<>());
|
||||||
|
file.put("content", content);
|
||||||
|
file.put("filename", newFileName);
|
||||||
|
files.put(fileName, file);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import java.util.Collections;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import static org.kohsuke.github.Previews.SQUIRREL_GIRL;
|
import static org.kohsuke.github.Previews.SQUIRREL_GIRL;
|
||||||
|
|
||||||
@@ -179,10 +180,12 @@ public class GHIssue extends GHObject implements Reactable {
|
|||||||
/**
|
/**
|
||||||
* Gets api url.
|
* Gets api url.
|
||||||
*
|
*
|
||||||
* @return the api url
|
* @return API URL of this object.
|
||||||
|
* @deprecated use {@link #getUrl()}
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public URL getApiURL() {
|
public URL getApiURL() {
|
||||||
return GitHubClient.parseURL(url);
|
return getUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -236,7 +239,7 @@ public class GHIssue extends GHObject implements Reactable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void editIssue(String key, Object value) throws IOException {
|
private void editIssue(String key, Object value) throws IOException {
|
||||||
root.createRequest().with(key, value).method("PATCH").withUrlPath(getIssuesApiRoute()).send();
|
root.createRequest().withNullable(key, value).method("PATCH").withUrlPath(getIssuesApiRoute()).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -293,9 +296,9 @@ public class GHIssue extends GHObject implements Reactable {
|
|||||||
*/
|
*/
|
||||||
public void setMilestone(GHMilestone milestone) throws IOException {
|
public void setMilestone(GHMilestone milestone) throws IOException {
|
||||||
if (milestone == null) {
|
if (milestone == null) {
|
||||||
editNullable("milestone", null);
|
editIssue("milestone", null);
|
||||||
} else {
|
} else {
|
||||||
edit("milestone", milestone.getNumber());
|
editIssue("milestone", milestone.getNumber());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,7 +573,8 @@ public class GHIssue extends GHObject implements Reactable {
|
|||||||
protected String getIssuesApiRoute() {
|
protected String getIssuesApiRoute() {
|
||||||
if (owner == null) {
|
if (owner == null) {
|
||||||
// Issues returned from search to do not have an owner. Attempt to use url.
|
// Issues returned from search to do not have an owner. Attempt to use url.
|
||||||
return StringUtils.prependIfMissing(getUrl().toString().replace(root.getApiUrl(), ""), "/");
|
final URL url = Objects.requireNonNull(getUrl(), "Missing instance URL!");
|
||||||
|
return StringUtils.prependIfMissing(url.toString().replace(root.getApiUrl(), ""), "/");
|
||||||
}
|
}
|
||||||
return "/repos/" + owner.getOwnerName() + "/" + owner.getName() + "/issues/" + number;
|
return "/repos/" + owner.getOwnerName() + "/" + owner.getName() + "/issues/" + number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,6 +149,6 @@ public class GHIssueComment extends GHObject implements Reactable {
|
|||||||
|
|
||||||
private String getApiRoute() {
|
private String getApiRoute() {
|
||||||
return "/repos/" + owner.getRepository().getOwnerName() + "/" + owner.getRepository().getName()
|
return "/repos/" + owner.getRepository().getOwnerName() + "/" + owner.getRepository().getName()
|
||||||
+ "/issues/comments/" + id;
|
+ "/issues/comments/" + getId();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import java.util.Date;
|
|||||||
/**
|
/**
|
||||||
* The type GHIssueEvent.
|
* The type GHIssueEvent.
|
||||||
*
|
*
|
||||||
|
* @see <a href="https://developer.github.com/v3/issues/events/">Github documentation for issue events</a>
|
||||||
|
*
|
||||||
* @author Martin van Zijl
|
* @author Martin van Zijl
|
||||||
*/
|
*/
|
||||||
public class GHIssueEvent {
|
public class GHIssueEvent {
|
||||||
@@ -18,6 +20,9 @@ public class GHIssueEvent {
|
|||||||
private String commit_id;
|
private String commit_id;
|
||||||
private String commit_url;
|
private String commit_url;
|
||||||
private String created_at;
|
private String created_at;
|
||||||
|
private GHMilestone milestone;
|
||||||
|
private GHLabel label;
|
||||||
|
private GHUser assignee;
|
||||||
|
|
||||||
private GHIssue issue;
|
private GHIssue issue;
|
||||||
|
|
||||||
@@ -111,6 +116,36 @@ public class GHIssueEvent {
|
|||||||
return issue;
|
return issue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link GHMilestone} that this issue was added to or removed from. Only present for events "milestoned"
|
||||||
|
* and "demilestoned", <code>null</code> otherwise.
|
||||||
|
*
|
||||||
|
* @return the milestone
|
||||||
|
*/
|
||||||
|
public GHMilestone getMilestone() {
|
||||||
|
return milestone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link GHLabel} that was added to or removed from the issue. Only present for events "labeled" and
|
||||||
|
* "unlabeled", <code>null</code> otherwise.
|
||||||
|
*
|
||||||
|
* @return the label
|
||||||
|
*/
|
||||||
|
public GHLabel getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link GHUser} that was assigned or unassigned from the issue. Only present for events "assigned" and
|
||||||
|
* "unassigned", <code>null</code> otherwise.
|
||||||
|
*
|
||||||
|
* @return the user
|
||||||
|
*/
|
||||||
|
public GHUser getAssignee() {
|
||||||
|
return assignee;
|
||||||
|
}
|
||||||
|
|
||||||
GHIssueEvent wrapUp(GitHub root) {
|
GHIssueEvent wrapUp(GitHub root) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -24,13 +24,13 @@
|
|||||||
|
|
||||||
package org.kohsuke.github;
|
package org.kohsuke.github;
|
||||||
|
|
||||||
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
|
|
||||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GitHub Preview API's license information
|
* The GitHub Preview API's license information
|
||||||
@@ -78,14 +78,6 @@ public class GHLicense extends GHObject {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return API URL of this object.
|
|
||||||
*/
|
|
||||||
@WithBridgeMethods(value = String.class, adapterMethod = "urlToString")
|
|
||||||
public URL getUrl() {
|
|
||||||
return GitHubClient.parseURL(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Featured licenses are bold in the new repository drop-down
|
* Featured licenses are bold in the new repository drop-down
|
||||||
*
|
*
|
||||||
@@ -199,7 +191,14 @@ public class GHLicense extends GHObject {
|
|||||||
if (description != null)
|
if (description != null)
|
||||||
return; // already populated
|
return; // already populated
|
||||||
|
|
||||||
root.createRequest().withUrlPath(url).fetchInto(this);
|
if (root == null || root.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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -210,12 +209,12 @@ public class GHLicense extends GHObject {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
GHLicense that = (GHLicense) o;
|
GHLicense that = (GHLicense) o;
|
||||||
return this.url.equals(that.url);
|
return Objects.equals(getUrl(), that.getUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return url.hashCode();
|
return Objects.hashCode(getUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
GHLicense wrap(GitHub root) {
|
GHLicense wrap(GitHub root) {
|
||||||
|
|||||||
@@ -26,10 +26,12 @@ public abstract class GHObject {
|
|||||||
*/
|
*/
|
||||||
protected transient Map<String, List<String>> responseHeaderFields;
|
protected transient Map<String, List<String>> responseHeaderFields;
|
||||||
|
|
||||||
protected String url;
|
private String url;
|
||||||
protected long id;
|
|
||||||
protected String created_at;
|
private long id;
|
||||||
protected String updated_at;
|
private String nodeId;
|
||||||
|
private String createdAt;
|
||||||
|
private String updatedAt;
|
||||||
|
|
||||||
GHObject() {
|
GHObject() {
|
||||||
}
|
}
|
||||||
@@ -74,12 +76,12 @@ public abstract class GHObject {
|
|||||||
*/
|
*/
|
||||||
@WithBridgeMethods(value = String.class, adapterMethod = "createdAtStr")
|
@WithBridgeMethods(value = String.class, adapterMethod = "createdAtStr")
|
||||||
public Date getCreatedAt() throws IOException {
|
public Date getCreatedAt() throws IOException {
|
||||||
return GitHubClient.parseDate(created_at);
|
return GitHubClient.parseDate(createdAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification = "Bridge method of getCreatedAt")
|
@SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification = "Bridge method of getCreatedAt")
|
||||||
private Object createdAtStr(Date id, Class type) {
|
private Object createdAtStr(Date id, Class type) {
|
||||||
return created_at;
|
return createdAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,7 +112,18 @@ public abstract class GHObject {
|
|||||||
* on error
|
* on error
|
||||||
*/
|
*/
|
||||||
public Date getUpdatedAt() throws IOException {
|
public Date getUpdatedAt() throws IOException {
|
||||||
return GitHubClient.parseDate(updated_at);
|
return GitHubClient.parseDate(updatedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Global node_id from Github object.
|
||||||
|
*
|
||||||
|
* @see <a href="https://developer.github.com/v4/guides/using-global-node-ids/">Using Global Node IDs</a>
|
||||||
|
*
|
||||||
|
* @return Global Node ID.
|
||||||
|
*/
|
||||||
|
public String getNodeId() {
|
||||||
|
return nodeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,6 +22,6 @@ class GHOrgHook extends GHHook {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
String getApiRoute() {
|
String getApiRoute() {
|
||||||
return String.format("/orgs/%s/hooks/%d", organization.getLogin(), id);
|
return String.format("/orgs/%s/hooks/%d", organization.getLogin(), getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,6 +128,22 @@ public class GHOrganization extends GHPerson {
|
|||||||
.toIterable(GHTeam[].class, item -> item.wrapUp(this));
|
.toIterable(GHTeam[].class, item -> item.wrapUp(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a single team by ID.
|
||||||
|
*
|
||||||
|
* @param teamId
|
||||||
|
* id of the team that we want to query for
|
||||||
|
* @return the team
|
||||||
|
* @throws IOException
|
||||||
|
* the io exception
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link GHOrganization#getTeam(long)}
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public GHTeam getTeam(int teamId) throws IOException {
|
||||||
|
return getTeam((long) teamId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a single team by ID.
|
* Gets a single team by ID.
|
||||||
*
|
*
|
||||||
@@ -139,9 +155,9 @@ public class GHOrganization extends GHPerson {
|
|||||||
*
|
*
|
||||||
* @see <a href= "https://developer.github.com/v3/teams/#get-team-by-name">documentation</a>
|
* @see <a href= "https://developer.github.com/v3/teams/#get-team-by-name">documentation</a>
|
||||||
*/
|
*/
|
||||||
public GHTeam getTeam(int teamId) throws IOException {
|
public GHTeam getTeam(long teamId) throws IOException {
|
||||||
return root.createRequest()
|
return root.createRequest()
|
||||||
.withUrlPath(String.format("/organizations/%d/team/%d", id, teamId))
|
.withUrlPath(String.format("/organizations/%d/team/%d", getId(), teamId))
|
||||||
.fetch(GHTeam.class)
|
.fetch(GHTeam.class)
|
||||||
.wrapUp(this);
|
.wrapUp(this);
|
||||||
}
|
}
|
||||||
@@ -405,7 +421,7 @@ public class GHOrganization extends GHPerson {
|
|||||||
* The enum Permission.
|
* The enum Permission.
|
||||||
*/
|
*/
|
||||||
public enum Permission {
|
public enum Permission {
|
||||||
ADMIN, PUSH, PULL
|
ADMIN, MAINTAIN, PUSH, TRIAGE, PULL
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ public abstract class GHPerson extends GHObject {
|
|||||||
protected String login, avatar_url;
|
protected String login, avatar_url;
|
||||||
|
|
||||||
// other fields (that only show up in full data)
|
// other fields (that only show up in full data)
|
||||||
protected String location, blog, email, name, company, type;
|
protected String location, blog, email, bio, name, company, type, twitter_username;
|
||||||
protected String html_url;
|
protected String html_url;
|
||||||
protected int followers, following, public_repos, public_gists;
|
protected int followers, following, public_repos, public_gists;
|
||||||
protected boolean site_admin;
|
protected boolean site_admin, hireable;
|
||||||
|
|
||||||
// other fields (that only show up in full data) that require privileged scope
|
// other fields (that only show up in full data) that require privileged scope
|
||||||
protected Integer total_private_repos;
|
protected Integer total_private_repos;
|
||||||
@@ -46,13 +46,16 @@ public abstract class GHPerson extends GHObject {
|
|||||||
* the io exception
|
* the io exception
|
||||||
*/
|
*/
|
||||||
protected synchronized void populate() throws IOException {
|
protected synchronized void populate() throws IOException {
|
||||||
if (created_at != null) {
|
if (super.getCreatedAt() != null) {
|
||||||
return; // already populated
|
return; // already populated
|
||||||
}
|
}
|
||||||
if (root == null || root.isOffline()) {
|
if (root == null || root.isOffline()) {
|
||||||
return; // cannot populate, will have to live with what we have
|
return; // cannot populate, will have to live with what we have
|
||||||
}
|
}
|
||||||
root.createRequest().withUrlPath(url).fetchInto(this);
|
URL url = getUrl();
|
||||||
|
if (url != null) {
|
||||||
|
root.createRequest().setRawUrlPath(url.toString()).fetchInto(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -151,10 +154,7 @@ public abstract class GHPerson extends GHObject {
|
|||||||
*/
|
*/
|
||||||
public GHRepository getRepository(String name) throws IOException {
|
public GHRepository getRepository(String name) throws IOException {
|
||||||
try {
|
try {
|
||||||
return root.createRequest()
|
return GHRepository.read(root, login, name);
|
||||||
.withUrlPath("/repos/" + login + '/' + name)
|
|
||||||
.fetch(GHRepository.class)
|
|
||||||
.wrap(root);
|
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -234,6 +234,18 @@ public abstract class GHPerson extends GHObject {
|
|||||||
return location;
|
return location;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the Twitter Username of this user, like "GitHub"
|
||||||
|
*
|
||||||
|
* @return the Twitter username
|
||||||
|
* @throws IOException
|
||||||
|
* the io exception
|
||||||
|
*/
|
||||||
|
public String getTwitterUsername() throws IOException {
|
||||||
|
populate();
|
||||||
|
return twitter_username;
|
||||||
|
}
|
||||||
|
|
||||||
public Date getCreatedAt() throws IOException {
|
public Date getCreatedAt() throws IOException {
|
||||||
populate();
|
populate();
|
||||||
return super.getCreatedAt();
|
return super.getCreatedAt();
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ public class GHProject extends GHObject {
|
|||||||
|
|
||||||
private String owner_url;
|
private String owner_url;
|
||||||
private String html_url;
|
private String html_url;
|
||||||
private String node_id;
|
|
||||||
private String name;
|
private String name;
|
||||||
private String body;
|
private String body;
|
||||||
private int number;
|
private int number;
|
||||||
@@ -81,10 +80,8 @@ public class GHProject extends GHObject {
|
|||||||
} else if (owner_url.contains("/users/")) {
|
} else if (owner_url.contains("/users/")) {
|
||||||
owner = root.createRequest().withUrlPath(getOwnerUrl().getPath()).fetch(GHUser.class).wrapUp(root);
|
owner = root.createRequest().withUrlPath(getOwnerUrl().getPath()).fetch(GHUser.class).wrapUp(root);
|
||||||
} else if (owner_url.contains("/repos/")) {
|
} else if (owner_url.contains("/repos/")) {
|
||||||
owner = root.createRequest()
|
String[] pathElements = getOwnerUrl().getPath().split("/");
|
||||||
.withUrlPath(getOwnerUrl().getPath())
|
owner = GHRepository.read(root, pathElements[1], pathElements[2]);
|
||||||
.fetch(GHRepository.class)
|
|
||||||
.wrap(root);
|
|
||||||
}
|
}
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
return null;
|
return null;
|
||||||
@@ -105,10 +102,12 @@ public class GHProject extends GHObject {
|
|||||||
/**
|
/**
|
||||||
* Gets node id.
|
* Gets node id.
|
||||||
*
|
*
|
||||||
|
* @deprecated Use {@link GHObject#getNodeId()}
|
||||||
* @return the node id
|
* @return the node id
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public String getNode_id() {
|
public String getNode_id() {
|
||||||
return node_id;
|
return getNodeId();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -191,7 +190,7 @@ public class GHProject extends GHObject {
|
|||||||
* @return the api route
|
* @return the api route
|
||||||
*/
|
*/
|
||||||
protected String getApiRoute() {
|
protected String getApiRoute() {
|
||||||
return "/projects/" + id;
|
return "/projects/" + getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -290,7 +289,7 @@ public class GHProject extends GHObject {
|
|||||||
final GHProject project = this;
|
final GHProject project = this;
|
||||||
return root.createRequest()
|
return root.createRequest()
|
||||||
.withPreview(INERTIA)
|
.withPreview(INERTIA)
|
||||||
.withUrlPath(String.format("/projects/%d/columns", id))
|
.withUrlPath(String.format("/projects/%d/columns", getId()))
|
||||||
.toIterable(GHProjectColumn[].class, item -> item.wrap(project));
|
.toIterable(GHProjectColumn[].class, item -> item.wrap(project));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,7 +307,7 @@ public class GHProject extends GHObject {
|
|||||||
.method("POST")
|
.method("POST")
|
||||||
.withPreview(INERTIA)
|
.withPreview(INERTIA)
|
||||||
.with("name", name)
|
.with("name", name)
|
||||||
.withUrlPath(String.format("/projects/%d/columns", id))
|
.withUrlPath(String.format("/projects/%d/columns", getId()))
|
||||||
.fetch(GHProjectColumn.class)
|
.fetch(GHProjectColumn.class)
|
||||||
.wrap(this);
|
.wrap(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ public class GHProjectCard extends GHObject {
|
|||||||
* @return the api route
|
* @return the api route
|
||||||
*/
|
*/
|
||||||
protected String getApiRoute() {
|
protected String getApiRoute() {
|
||||||
return String.format("/projects/columns/cards/%d", id);
|
return String.format("/projects/columns/cards/%d", getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ public class GHProjectColumn extends GHObject {
|
|||||||
* @return the api route
|
* @return the api route
|
||||||
*/
|
*/
|
||||||
protected String getApiRoute() {
|
protected String getApiRoute() {
|
||||||
return String.format("/projects/columns/%d", id);
|
return String.format("/projects/columns/%d", getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -139,7 +139,7 @@ public class GHProjectColumn extends GHObject {
|
|||||||
final GHProjectColumn column = this;
|
final GHProjectColumn column = this;
|
||||||
return root.createRequest()
|
return root.createRequest()
|
||||||
.withPreview(INERTIA)
|
.withPreview(INERTIA)
|
||||||
.withUrlPath(String.format("/projects/columns/%d/cards", id))
|
.withUrlPath(String.format("/projects/columns/%d/cards", getId()))
|
||||||
.toIterable(GHProjectCard[].class, item -> item.wrap(column));
|
.toIterable(GHProjectCard[].class, item -> item.wrap(column));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ public class GHProjectColumn extends GHObject {
|
|||||||
.method("POST")
|
.method("POST")
|
||||||
.withPreview(INERTIA)
|
.withPreview(INERTIA)
|
||||||
.with("note", note)
|
.with("note", note)
|
||||||
.withUrlPath(String.format("/projects/columns/%d/cards", id))
|
.withUrlPath(String.format("/projects/columns/%d/cards", getId()))
|
||||||
.fetch(GHProjectCard.class)
|
.fetch(GHProjectCard.class)
|
||||||
.wrap(this);
|
.wrap(this);
|
||||||
}
|
}
|
||||||
@@ -177,7 +177,7 @@ public class GHProjectColumn extends GHObject {
|
|||||||
.withPreview(INERTIA)
|
.withPreview(INERTIA)
|
||||||
.with("content_type", issue instanceof GHPullRequest ? "PullRequest" : "Issue")
|
.with("content_type", issue instanceof GHPullRequest ? "PullRequest" : "Issue")
|
||||||
.with("content_id", issue.getId())
|
.with("content_id", issue.getId())
|
||||||
.withUrlPath(String.format("/projects/columns/%d/cards", id))
|
.withUrlPath(String.format("/projects/columns/%d/cards", getId()))
|
||||||
.fetch(GHProjectCard.class)
|
.fetch(GHProjectCard.class)
|
||||||
.wrap(this);
|
.wrap(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import java.util.Collection;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.annotation.CheckForNull;
|
import javax.annotation.CheckForNull;
|
||||||
|
|
||||||
@@ -103,7 +104,9 @@ public class GHPullRequest extends GHIssue implements Refreshable {
|
|||||||
protected String getApiRoute() {
|
protected String getApiRoute() {
|
||||||
if (owner == null) {
|
if (owner == null) {
|
||||||
// Issues returned from search to do not have an owner. Attempt to use url.
|
// Issues returned from search to do not have an owner. Attempt to use url.
|
||||||
return StringUtils.prependIfMissing(getUrl().toString().replace(root.getApiUrl(), ""), "/");
|
final URL url = Objects.requireNonNull(getUrl(), "Missing instance URL!");
|
||||||
|
return StringUtils.prependIfMissing(url.toString().replace(root.getApiUrl(), ""), "/");
|
||||||
|
|
||||||
}
|
}
|
||||||
return "/repos/" + owner.getOwnerName() + "/" + owner.getName() + "/pulls/" + number;
|
return "/repos/" + owner.getOwnerName() + "/" + owner.getName() + "/pulls/" + number;
|
||||||
}
|
}
|
||||||
@@ -387,10 +390,14 @@ public class GHPullRequest extends GHIssue implements Refreshable {
|
|||||||
* Repopulates this object.
|
* Repopulates this object.
|
||||||
*/
|
*/
|
||||||
public void refresh() throws IOException {
|
public void refresh() throws IOException {
|
||||||
if (root.isOffline()) {
|
if (root == null || root.isOffline()) {
|
||||||
return; // cannot populate, will have to live with what we have
|
return; // cannot populate, will have to live with what we have
|
||||||
}
|
}
|
||||||
root.createRequest().withPreview(SHADOW_CAT).withUrlPath(url).fetchInto(this).wrapUp(owner);
|
|
||||||
|
URL url = getUrl();
|
||||||
|
if (url != null) {
|
||||||
|
root.createRequest().withPreview(SHADOW_CAT).setRawUrlPath(url.toString()).fetchInto(this).wrapUp(owner);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ public class GHPullRequestReview extends GHObject {
|
|||||||
* @return the api route
|
* @return the api route
|
||||||
*/
|
*/
|
||||||
protected String getApiRoute() {
|
protected String getApiRoute() {
|
||||||
return owner.getApiRoute() + "/reviews/" + id;
|
return owner.getApiRoute() + "/reviews/" + getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ public class GHPullRequestReviewComment extends GHObject implements Reactable {
|
|||||||
* @return the api route
|
* @return the api route
|
||||||
*/
|
*/
|
||||||
protected String getApiRoute() {
|
protected String getApiRoute() {
|
||||||
return "/repos/" + owner.getRepository().getFullName() + "/pulls/comments/" + id;
|
return "/repos/" + owner.getRepository().getFullName() + "/pulls/comments/" + getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
@@ -29,7 +30,7 @@ public class GHRateLimit {
|
|||||||
/**
|
/**
|
||||||
* Remaining calls that can be made.
|
* Remaining calls that can be made.
|
||||||
*
|
*
|
||||||
* @deprecated This value should never have been made public. Use {@link #getRemaining()}
|
* @deprecated This field should never have been made public. Use {@link #getRemaining()}
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public int remaining;
|
public int remaining;
|
||||||
@@ -37,7 +38,7 @@ public class GHRateLimit {
|
|||||||
/**
|
/**
|
||||||
* Allotted API call per hour.
|
* Allotted API call per hour.
|
||||||
*
|
*
|
||||||
* @deprecated This value should never have been made public. Use {@link #getLimit()}
|
* @deprecated This field should never have been made public. Use {@link #getLimit()}
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public int limit;
|
public int limit;
|
||||||
@@ -48,7 +49,7 @@ public class GHRateLimit {
|
|||||||
* date. To use this field in any meaningful way, it must be converted to a long using {@link Date#getTime()}
|
* date. To use this field in any meaningful way, it must be converted to a long using {@link Date#getTime()}
|
||||||
* multiplied by 1000.
|
* multiplied by 1000.
|
||||||
*
|
*
|
||||||
* @deprecated This value should never have been made public. Use {@link #getResetDate()}
|
* @deprecated This field should never have been made public. Use {@link #getResetDate()}
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Date reset;
|
public Date reset;
|
||||||
@@ -65,17 +66,58 @@ public class GHRateLimit {
|
|||||||
@Nonnull
|
@Nonnull
|
||||||
private final Record integrationManifest;
|
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
|
@Nonnull
|
||||||
static GHRateLimit Unknown() {
|
static final GHRateLimit DEFAULT = new GHRateLimit(UnknownLimitRecord.DEFAULT,
|
||||||
return new GHRateLimit(new UnknownLimitRecord(),
|
UnknownLimitRecord.DEFAULT,
|
||||||
new UnknownLimitRecord(),
|
UnknownLimitRecord.DEFAULT,
|
||||||
new UnknownLimitRecord(),
|
UnknownLimitRecord.DEFAULT);
|
||||||
new UnknownLimitRecord());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
@Nonnull
|
||||||
static GHRateLimit fromHeaderRecord(Record header) {
|
static GHRateLimit fromRecord(@Nonnull Record record, @Nonnull RateLimitTarget rateLimitTarget) {
|
||||||
return new GHRateLimit(header, new UnknownLimitRecord(), new UnknownLimitRecord(), new UnknownLimitRecord());
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
@@ -142,7 +184,7 @@ public class GHRateLimit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the rate limit reset date for this instance has passed.
|
* Whether the reset date for the Core API rate limit has passed.
|
||||||
*
|
*
|
||||||
* @return true if the rate limit reset date has passed. Otherwise false.
|
* @return true if the rate limit reset date has passed. Otherwise false.
|
||||||
* @since 1.100
|
* @since 1.100
|
||||||
@@ -152,7 +194,7 @@ public class GHRateLimit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The core object provides your rate limit status for all non-search-related resources in the REST API.
|
* The core object provides the rate limit status for all non-search-related resources in the REST API.
|
||||||
*
|
*
|
||||||
* @return a rate limit record
|
* @return a rate limit record
|
||||||
* @since 1.100
|
* @since 1.100
|
||||||
@@ -163,42 +205,43 @@ public class GHRateLimit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The search object provides your rate limit status for the Search API. TODO: integrate with header limit updating.
|
* The search record provides the rate limit status for the Search API.
|
||||||
* Issue #605.
|
|
||||||
*
|
*
|
||||||
* @return a rate limit record
|
* @return a rate limit record
|
||||||
|
* @since 1.115
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
Record getSearch() {
|
public Record getSearch() {
|
||||||
return search;
|
return search;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The graphql object provides your rate limit status for the GraphQL API. TODO: integrate with header limit
|
* The graphql record provides the rate limit status for the GraphQL API.
|
||||||
* updating. Issue #605.
|
|
||||||
*
|
*
|
||||||
* @return a rate limit record
|
* @return a rate limit record
|
||||||
|
* @since 1.115
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
Record getGraphQL() {
|
public Record getGraphQL() {
|
||||||
return graphql;
|
return graphql;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The integration_manifest object provides your rate limit status for the GitHub App Manifest code conversion
|
* The integration manifest record provides the rate limit status for the GitHub App Manifest code conversion
|
||||||
* endpoint. TODO: integrate with header limit updating. Issue #605.
|
* endpoint.
|
||||||
*
|
*
|
||||||
* @return a rate limit record
|
* @return a rate limit record
|
||||||
|
* @since 1.115
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
Record getIntegrationManifest() {
|
public Record getIntegrationManifest() {
|
||||||
return integrationManifest;
|
return integrationManifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "GHRateLimit {" + "core " + getCore().toString() + "search " + getSearch().toString() + "graphql "
|
return "GHRateLimit {" + "core " + getCore().toString() + ", search " + getSearch().toString() + ", graphql "
|
||||||
+ getGraphQL().toString() + "integrationManifest " + getIntegrationManifest().toString() + '}';
|
+ getGraphQL().toString() + ", integrationManifest " + getIntegrationManifest().toString() + "}";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -221,44 +264,111 @@ public class GHRateLimit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the appropriate {@link Record} for a particular url path.
|
* Merge a {@link GHRateLimit} with another one to create a new {@link GHRateLimit} keeping the latest
|
||||||
*
|
* {@link Record}s from each.
|
||||||
* @param urlPath
|
*
|
||||||
* the url path of the request
|
* @param newLimit
|
||||||
* @return the {@link Record} for a url path.
|
* {@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
|
@Nonnull
|
||||||
Record getRecordForUrlPath(@Nonnull String urlPath) {
|
GHRateLimit getMergedRateLimit(@Nonnull GHRateLimit newLimit) {
|
||||||
if (urlPath.equals("/rate_limit")) {
|
|
||||||
return new UnknownLimitRecord();
|
GHRateLimit merged = new GHRateLimit(getCore().currentOrUpdated(newLimit.getCore()),
|
||||||
} else if (urlPath.startsWith("/search")) {
|
getSearch().currentOrUpdated(newLimit.getSearch()),
|
||||||
return getSearch();
|
getGraphQL().currentOrUpdated(newLimit.getGraphQL()),
|
||||||
} else if (urlPath.startsWith("/graphql")) {
|
getIntegrationManifest().currentOrUpdated(newLimit.getIntegrationManifest()));
|
||||||
return getGraphQL();
|
|
||||||
} else if (urlPath.startsWith("/app-manifests")) {
|
if (merged.equals(this)) {
|
||||||
return getIntegrationManifest();
|
merged = this;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A limit record used as a placeholder when the the actual limit is not known.
|
* A limit record used as a placeholder when the the actual limit is not known.
|
||||||
* <p>
|
|
||||||
* Has a large limit and long duration so that it will doesn't expire too often.
|
|
||||||
*
|
*
|
||||||
* @since 1.100
|
* @since 1.100
|
||||||
*/
|
*/
|
||||||
public static class UnknownLimitRecord extends Record {
|
public static class UnknownLimitRecord extends Record {
|
||||||
|
|
||||||
// One hour
|
private static final long defaultUnknownLimitResetSeconds = Duration.ofSeconds(30).getSeconds();
|
||||||
private static final long unknownLimitResetSeconds = 60L * 60L;
|
|
||||||
|
/**
|
||||||
|
* 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 {@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;
|
||||||
|
|
||||||
static final int unknownLimit = 1000000;
|
static final int unknownLimit = 1000000;
|
||||||
static final int unknownRemaining = 999999;
|
static final int unknownRemaining = 999999;
|
||||||
|
|
||||||
private UnknownLimitRecord() {
|
// The default UnknownLimitRecord is an expired record.
|
||||||
super(unknownLimit, unknownRemaining, System.currentTimeMillis() / 1000L + unknownLimitResetSeconds);
|
private static final UnknownLimitRecord DEFAULT = new UnknownLimitRecord(Long.MIN_VALUE);
|
||||||
|
|
||||||
|
// The starting current UnknownLimitRecord is an expired record.
|
||||||
|
private static UnknownLimitRecord current = DEFAULT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
static synchronized Record current() {
|
||||||
|
if (current.isExpired()) {
|
||||||
|
current = new UnknownLimitRecord(System.currentTimeMillis() / 1000L + unknownLimitResetSeconds);
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the current UnknownLimitRecord. For use during testing only.
|
||||||
|
*/
|
||||||
|
static synchronized void reset() {
|
||||||
|
current = DEFAULT;
|
||||||
|
unknownLimitResetSeconds = defaultUnknownLimitResetSeconds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,14 +384,12 @@ public class GHRateLimit {
|
|||||||
private final int remaining;
|
private final int remaining;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allotted API call per hour.
|
* Allotted API call per time period.
|
||||||
*/
|
*/
|
||||||
private final int limit;
|
private final int limit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The time at which the current rate limit window resets in UTC epoch seconds.
|
* The time at which the current rate limit window resets in UTC epoch seconds.
|
||||||
*
|
|
||||||
* This is the raw value returned by the server.
|
|
||||||
*/
|
*/
|
||||||
private final long resetEpochSeconds;
|
private final long resetEpochSeconds;
|
||||||
|
|
||||||
@@ -291,9 +399,11 @@ public class GHRateLimit {
|
|||||||
private final long createdAtEpochSeconds = System.currentTimeMillis() / 1000;
|
private final long createdAtEpochSeconds = System.currentTimeMillis() / 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The time at which the rate limit will reset. This value is calculated based on
|
* The date at which the rate limit will reset, adjusted to local machine time if the local machine's clock not
|
||||||
* {@link #getResetEpochSeconds()} by calling {@link #calculateResetDate}. If the clock on the local machine not
|
* synchronized with to the same clock as the GitHub server.
|
||||||
* synchronized with the server clock, this time value will be adjusted to match the local machine's clock.
|
*
|
||||||
|
* @see #calculateResetDate(String)
|
||||||
|
* @see #getResetDate()
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private final Date resetDate;
|
private final Date resetDate;
|
||||||
@@ -341,12 +451,58 @@ public class GHRateLimit {
|
|||||||
this.resetDate = calculateResetDate(updatedAt);
|
this.resetDate = calculateResetDate(updatedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the current {@link Record} is outdated compared to another. Rate Limit dates are only accurate
|
||||||
|
* to the second, so we look at other information in the record as well.
|
||||||
|
*
|
||||||
|
* {@link Record}s with earlier {@link #getResetEpochSeconds()} are replaced by those with later.
|
||||||
|
* {@link Record}s with the same {@link #getResetEpochSeconds()} are replaced by those with less remaining
|
||||||
|
* count.
|
||||||
|
*
|
||||||
|
* {@link UnknownLimitRecord}s compare with each other like regular {@link Record}s.
|
||||||
|
*
|
||||||
|
* {@link Record}s are replaced by {@link UnknownLimitRecord}s only when the current {@link Record} is expired
|
||||||
|
* and the {@link UnknownLimitRecord} is not. Otherwise Regular {@link Record}s are not replaced by
|
||||||
|
* {@link UnknownLimitRecord}s.
|
||||||
|
*
|
||||||
|
* Expiration is only considered after other checks, meaning expired records may sometimes be replaced by other
|
||||||
|
* expired records.
|
||||||
|
*
|
||||||
|
* @param other
|
||||||
|
* the other {@link Record}
|
||||||
|
* @return the {@link Record} that is most current
|
||||||
|
*/
|
||||||
|
Record currentOrUpdated(@Nonnull Record other) {
|
||||||
|
// This set of checks avoids most calls to isExpired()
|
||||||
|
// Depends on UnknownLimitRecord.current() to prevent continuous updating of GHRateLimit rateLimit()
|
||||||
|
if (getResetEpochSeconds() > other.getResetEpochSeconds()
|
||||||
|
|| (getResetEpochSeconds() == other.getResetEpochSeconds()
|
||||||
|
&& getRemaining() <= other.getRemaining())) {
|
||||||
|
// If the current record has a later reset
|
||||||
|
// or the current record has the same reset and fewer or same requests remaining
|
||||||
|
// Then it is most recent
|
||||||
|
return this;
|
||||||
|
} else if (!(other instanceof UnknownLimitRecord)) {
|
||||||
|
// If the above is not the case that means other has a later reset
|
||||||
|
// or the same resent and fewer requests remaining.
|
||||||
|
// If the other record is not an unknown record, the the other is more recent
|
||||||
|
return other;
|
||||||
|
} else if (this.isExpired() && !other.isExpired()) {
|
||||||
|
// The other is an unknown record.
|
||||||
|
// If the current record has expired and the other hasn't, return the other.
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If none of the above, the current record is most valid.
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recalculates the {@link #resetDate} relative to the local machine clock.
|
* Recalculates the {@link #resetDate} relative to the local machine clock.
|
||||||
* <p>
|
* <p>
|
||||||
* {@link RateLimitChecker}s and {@link RateLimitHandler}s use {@link #getResetDate()} to make decisions about
|
* {@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
|
* how long to wait for until for the rate limit to reset. That means that {@link #getResetDate()} needs to be
|
||||||
* accurate to the local machine.
|
* calculated based on the local machine clock.
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* When we say that the clock on two machines is "synchronized", we mean that the UTC time returned from
|
* When we say that the clock on two machines is "synchronized", we mean that the UTC time returned from
|
||||||
@@ -415,7 +571,7 @@ public class GHRateLimit {
|
|||||||
* {@link #getResetDate()} or implement a {@link RateLimitChecker} instead.
|
* {@link #getResetDate()} or implement a {@link RateLimitChecker} instead.
|
||||||
*
|
*
|
||||||
* @return a long representing the time in epoch seconds when the rate limit will reset
|
* @return a long representing the time in epoch seconds when the rate limit will reset
|
||||||
* @see #getResetDate() #getResetDate()
|
* @see #getResetDate()
|
||||||
*/
|
*/
|
||||||
public long getResetEpochSeconds() {
|
public long getResetEpochSeconds() {
|
||||||
return resetEpochSeconds;
|
return resetEpochSeconds;
|
||||||
@@ -424,6 +580,8 @@ public class GHRateLimit {
|
|||||||
/**
|
/**
|
||||||
* Whether the rate limit reset date indicated by this instance is expired
|
* 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.
|
* @return true if the rate limit reset date has passed. Otherwise false.
|
||||||
*/
|
*/
|
||||||
public boolean isExpired() {
|
public boolean isExpired() {
|
||||||
@@ -431,8 +589,8 @@ public class GHRateLimit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the date at which the rate limit will reset, adjusted to local machine time if the local machine's
|
* The date at which the rate limit will reset, adjusted to local machine time if the local machine's clock not
|
||||||
* clock not synchronized with to the same clock as the GitHub server.
|
* 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.
|
* If attempting to wait for the rate limit to reset, consider implementing a {@link RateLimitChecker} instead.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -58,6 +58,6 @@ public class GHReaction extends GHObject {
|
|||||||
* the io exception
|
* the io exception
|
||||||
*/
|
*/
|
||||||
public void delete() throws IOException {
|
public void delete() throws IOException {
|
||||||
root.createRequest().method("DELETE").withPreview(SQUIRREL_GIRL).withUrlPath("/reactions/" + id).send();
|
root.createRequest().method("DELETE").withPreview(SQUIRREL_GIRL).withUrlPath("/reactions/" + getId()).send();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.kohsuke.github;
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -90,6 +91,78 @@ public class GHRef {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrive a ref of the given type for the current GitHub repository.
|
||||||
|
*
|
||||||
|
* @param repository
|
||||||
|
* the repository to read from
|
||||||
|
* @param refName
|
||||||
|
* eg: heads/branch
|
||||||
|
* @return refs matching the request type
|
||||||
|
* @throws IOException
|
||||||
|
* on failure communicating with GitHub, potentially due to an invalid ref type being requested
|
||||||
|
*/
|
||||||
|
static GHRef read(GHRepository repository, String refName) throws IOException {
|
||||||
|
// Also accept e.g. "refs/heads/branch" for consistency with createRef().
|
||||||
|
if (refName.startsWith("refs/")) {
|
||||||
|
refName = refName.replaceFirst("refs/", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// We would expect this to use `git/ref/%s` but some versions of GHE seem to not support it
|
||||||
|
// Instead use `git/refs/%s` and check the result actually matches the ref
|
||||||
|
GHRef result = null;
|
||||||
|
try {
|
||||||
|
result = repository.root.createRequest()
|
||||||
|
.withUrlPath(repository.getApiTailUrl(String.format("git/refs/%s", refName)))
|
||||||
|
.fetch(GHRef.class)
|
||||||
|
.wrap(repository.root);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// If the parse exception is due to the above returning an array instead of a single ref
|
||||||
|
// that means the individual ref did not exist. Handled by result check below.
|
||||||
|
// Otherwise, rethrow.
|
||||||
|
if (!(e.getCause() instanceof JsonMappingException)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the ref returned is the one requested
|
||||||
|
// Used .endsWith(refName) instead of .equals("refs/" + refName) to workaround a GitBucket
|
||||||
|
// issue where the "ref" field omits the "refs/" prefix. "endsWith()" is functionally
|
||||||
|
// the same for this scenario - the server refs matching is prefix-based, so
|
||||||
|
// a ref that ends with the correct string will always be the correct one.
|
||||||
|
if (result == null || !result.getRef().endsWith(refName)) {
|
||||||
|
throw new GHFileNotFoundException(String.format("git/refs/%s", refName)
|
||||||
|
+ " {\"message\":\"Not Found\",\"documentation_url\":\"https://developer.github.com/v3/git/refs/#get-a-reference\"}");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all refs of the given type for the current GitHub repository.
|
||||||
|
*
|
||||||
|
* @param repository
|
||||||
|
* the repository to read from
|
||||||
|
* @param refType
|
||||||
|
* the type of reg to search for e.g. <code>tags</code> or <code>commits</code>
|
||||||
|
* @return paged iterable of all refs of the specified type
|
||||||
|
* @throws IOException
|
||||||
|
* on failure communicating with GitHub, potentially due to an invalid ref type being requested
|
||||||
|
*/
|
||||||
|
static PagedIterable<GHRef> readMatching(GHRepository repository, String refType) throws IOException {
|
||||||
|
if (refType.startsWith("refs/")) {
|
||||||
|
refType = refType.replaceFirst("refs/", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = repository.getApiTailUrl(String.format("git/refs/%s", refType));
|
||||||
|
// if no types, do not end with slash just to be safe.
|
||||||
|
if (refType.equals("")) {
|
||||||
|
url = url.substring(0, url.length() - 1);
|
||||||
|
}
|
||||||
|
return repository.root.createRequest()
|
||||||
|
.withUrlPath(url)
|
||||||
|
.toIterable(GHRef[].class, item -> item.wrap(repository.root));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type GHObject.
|
* The type GHObject.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ public class GHRelease extends GHObject {
|
|||||||
* the io exception
|
* the io exception
|
||||||
*/
|
*/
|
||||||
public void delete() throws IOException {
|
public void delete() throws IOException {
|
||||||
root.createRequest().method("DELETE").withUrlPath(owner.getApiTailUrl("releases/" + id)).send();
|
root.createRequest().method("DELETE").withUrlPath(owner.getApiTailUrl("releases/" + getId())).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -283,6 +283,6 @@ public class GHRelease extends GHObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getApiTailUrl(String end) {
|
private String getApiTailUrl(String end) {
|
||||||
return owner.getApiTailUrl(format("releases/%s/%s", id, end));
|
return owner.getApiTailUrl(format("releases/%s/%s", getId(), end));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ public class GHReleaseUpdater {
|
|||||||
*/
|
*/
|
||||||
public GHRelease update() throws IOException {
|
public GHRelease update() throws IOException {
|
||||||
return builder.method("PATCH")
|
return builder.method("PATCH")
|
||||||
.withUrlPath(base.owner.getApiTailUrl("releases/" + base.id))
|
.withUrlPath(base.owner.getApiTailUrl("releases/" + base.getId()))
|
||||||
.fetch(GHRelease.class)
|
.fetch(GHRelease.class)
|
||||||
.wrap(base.owner);
|
.wrap(base.owner);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,6 @@ class GHRepoHook extends GHHook {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
String getApiRoute() {
|
String getApiRoute() {
|
||||||
return String.format("/repos/%s/%s/hooks/%d", repository.getOwnerName(), repository.getName(), id);
|
return String.format("/repos/%s/%s/hooks/%d", repository.getOwnerName(), repository.getName(), getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
package org.kohsuke.github;
|
package org.kohsuke.github;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.core.JsonParseException;
|
||||||
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
|
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
|
||||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
||||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||||
@@ -45,8 +46,10 @@ import java.util.Date;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
@@ -66,7 +69,9 @@ public class GHRepository extends GHObject {
|
|||||||
/* package almost final */ transient GitHub root;
|
/* package almost final */ transient GitHub root;
|
||||||
|
|
||||||
private String nodeId, description, homepage, name, full_name;
|
private String nodeId, description, homepage, name, full_name;
|
||||||
|
|
||||||
private String html_url; // this is the UI
|
private String html_url; // this is the UI
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The license information makes use of the preview API.
|
* The license information makes use of the preview API.
|
||||||
*
|
*
|
||||||
@@ -75,22 +80,30 @@ public class GHRepository extends GHObject {
|
|||||||
private GHLicense license;
|
private GHLicense license;
|
||||||
|
|
||||||
private String git_url, ssh_url, clone_url, svn_url, mirror_url;
|
private String git_url, ssh_url, clone_url, svn_url, mirror_url;
|
||||||
|
|
||||||
private GHUser owner; // not fully populated. beware.
|
private GHUser owner; // not fully populated. beware.
|
||||||
|
|
||||||
private boolean has_issues, has_wiki, fork, has_downloads, has_pages, archived, has_projects;
|
private boolean has_issues, has_wiki, fork, has_downloads, has_pages, archived, has_projects;
|
||||||
|
|
||||||
private boolean allow_squash_merge;
|
private boolean allow_squash_merge;
|
||||||
|
|
||||||
private boolean allow_merge_commit;
|
private boolean allow_merge_commit;
|
||||||
|
|
||||||
private boolean allow_rebase_merge;
|
private boolean allow_rebase_merge;
|
||||||
|
|
||||||
private boolean delete_branch_on_merge;
|
private boolean delete_branch_on_merge;
|
||||||
|
|
||||||
@JsonProperty("private")
|
@JsonProperty("private")
|
||||||
private boolean _private;
|
private boolean _private;
|
||||||
|
|
||||||
private int forks_count, stargazers_count, watchers_count, size, open_issues_count, subscribers_count;
|
private int forks_count, stargazers_count, watchers_count, size, open_issues_count, subscribers_count;
|
||||||
|
|
||||||
private String pushed_at;
|
private String pushed_at;
|
||||||
|
|
||||||
private Map<Integer, GHMilestone> milestones = new WeakHashMap<Integer, GHMilestone>();
|
private Map<Integer, GHMilestone> milestones = new WeakHashMap<Integer, GHMilestone>();
|
||||||
|
|
||||||
private String default_branch, language;
|
private String default_branch, language;
|
||||||
|
|
||||||
private Map<String, GHCommit> commits = new WeakHashMap<String, GHCommit>();
|
private Map<String, GHCommit> commits = new WeakHashMap<String, GHCommit>();
|
||||||
|
|
||||||
@SkipFromToString
|
@SkipFromToString
|
||||||
@@ -98,6 +111,12 @@ public class GHRepository extends GHObject {
|
|||||||
|
|
||||||
private GHRepository source, parent;
|
private GHRepository source, parent;
|
||||||
|
|
||||||
|
private Boolean isTemplate;
|
||||||
|
|
||||||
|
static GHRepository read(GitHub root, String owner, String name) throws IOException {
|
||||||
|
return root.createRequest().withUrlPath("/repos/" + owner + '/' + name).fetch(GHRepository.class).wrap(root);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create deployment gh deployment builder.
|
* Create deployment gh deployment builder.
|
||||||
*
|
*
|
||||||
@@ -679,6 +698,28 @@ public class GHRepository extends GHObject {
|
|||||||
return _private;
|
return _private;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is template boolean.
|
||||||
|
*
|
||||||
|
* @return the boolean
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@Preview
|
||||||
|
public boolean isTemplate() {
|
||||||
|
// isTemplate is still in preview, we do not want to retrieve it unless needed.
|
||||||
|
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.
|
* Has downloads boolean.
|
||||||
*
|
*
|
||||||
@@ -968,12 +1009,12 @@ public class GHRepository extends GHObject {
|
|||||||
@NonNull String method,
|
@NonNull String method,
|
||||||
@CheckForNull GHOrganization.Permission permission) throws IOException {
|
@CheckForNull GHOrganization.Permission permission) throws IOException {
|
||||||
Requester requester = root.createRequest().method(method);
|
Requester requester = root.createRequest().method(method);
|
||||||
|
|
||||||
if (permission != null) {
|
if (permission != null) {
|
||||||
requester = requester.with("permission", permission).inBody();
|
requester = requester.with("permission", permission).inBody();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (GHUser user : users) {
|
// Make sure that the users collection doesn't have any duplicates
|
||||||
|
for (GHUser user : new LinkedHashSet<GHUser>(users)) {
|
||||||
requester.withUrlPath(getApiTailUrl("collaborators/" + user.getLogin())).send();
|
requester.withUrlPath(getApiTailUrl("collaborators/" + user.getLogin())).send();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1000,8 +1041,9 @@ public class GHRepository extends GHObject {
|
|||||||
|
|
||||||
private void edit(String key, String value) throws IOException {
|
private void edit(String key, String value) throws IOException {
|
||||||
Requester requester = root.createRequest();
|
Requester requester = root.createRequest();
|
||||||
if (!key.equals("name"))
|
if (!key.equals("name")) {
|
||||||
requester.with("name", name); // even when we don't change the name, we need to send it in
|
requester.with("name", name); // even when we don't change the name, we need to send it in
|
||||||
|
}
|
||||||
requester.with(key, value).method("PATCH").withUrlPath(getApiTailUrl("")).send();
|
requester.with(key, value).method("PATCH").withUrlPath(getApiTailUrl("")).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1246,8 +1288,9 @@ public class GHRepository extends GHObject {
|
|||||||
// this API is asynchronous. we need to wait for a bit
|
// this API is asynchronous. we need to wait for a bit
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
GHRepository r = root.getMyself().getRepository(name);
|
GHRepository r = root.getMyself().getRepository(name);
|
||||||
if (r != null)
|
if (r != null) {
|
||||||
return r;
|
return r;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Thread.sleep(3000);
|
Thread.sleep(3000);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
@@ -1276,8 +1319,9 @@ public class GHRepository extends GHObject {
|
|||||||
// this API is asynchronous. we need to wait for a bit
|
// this API is asynchronous. we need to wait for a bit
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
GHRepository r = org.getRepository(name);
|
GHRepository r = org.getRepository(name);
|
||||||
if (r != null)
|
if (r != null) {
|
||||||
return r;
|
return r;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Thread.sleep(3000);
|
Thread.sleep(3000);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
@@ -1538,8 +1582,7 @@ public class GHRepository extends GHObject {
|
|||||||
* on failure communicating with GitHub, potentially due to an invalid ref type being requested
|
* on failure communicating with GitHub, potentially due to an invalid ref type being requested
|
||||||
*/
|
*/
|
||||||
public PagedIterable<GHRef> listRefs() throws IOException {
|
public PagedIterable<GHRef> listRefs() throws IOException {
|
||||||
final String url = String.format("/repos/%s/%s/git/refs", getOwnerName(), name);
|
return listRefs("");
|
||||||
return root.createRequest().withUrlPath(url).toIterable(GHRef[].class, item -> item.wrap(root));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1565,8 +1608,7 @@ public class GHRepository extends GHObject {
|
|||||||
* on failure communicating with GitHub, potentially due to an invalid ref type being requested
|
* on failure communicating with GitHub, potentially due to an invalid ref type being requested
|
||||||
*/
|
*/
|
||||||
public PagedIterable<GHRef> listRefs(String refType) throws IOException {
|
public PagedIterable<GHRef> listRefs(String refType) throws IOException {
|
||||||
final String url = String.format("/repos/%s/%s/git/refs/%s", getOwnerName(), name, refType);
|
return GHRef.readMatching(this, refType);
|
||||||
return root.createRequest().withUrlPath(url).toIterable(GHRef[].class, item -> item.wrap(root));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1579,15 +1621,7 @@ public class GHRepository extends GHObject {
|
|||||||
* on failure communicating with GitHub, potentially due to an invalid ref type being requested
|
* on failure communicating with GitHub, potentially due to an invalid ref type being requested
|
||||||
*/
|
*/
|
||||||
public GHRef getRef(String refName) throws IOException {
|
public GHRef getRef(String refName) throws IOException {
|
||||||
// Also accept e.g. "refs/heads/branch" for consistency with createRef().
|
return GHRef.read(this, refName);
|
||||||
if (refName.startsWith("refs/")) {
|
|
||||||
refName = refName.replaceFirst("refs/", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
return root.createRequest()
|
|
||||||
.withUrlPath(getApiTailUrl(String.format("git/refs/%s", refName)))
|
|
||||||
.fetch(GHRef.class)
|
|
||||||
.wrap(root);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2158,6 +2192,12 @@ public class GHRepository extends GHObject {
|
|||||||
if (root.isOffline() && owner != null) {
|
if (root.isOffline() && owner != null) {
|
||||||
owner.wrapUp(root);
|
owner.wrapUp(root);
|
||||||
}
|
}
|
||||||
|
if (source != null) {
|
||||||
|
source.wrap(root);
|
||||||
|
}
|
||||||
|
if (parent != null) {
|
||||||
|
parent.wrap(root);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2477,10 +2517,13 @@ public class GHRepository extends GHObject {
|
|||||||
* @see #getParent() #getParent()
|
* @see #getParent() #getParent()
|
||||||
*/
|
*/
|
||||||
public GHRepository getSource() throws IOException {
|
public GHRepository getSource() throws IOException {
|
||||||
if (source == null)
|
if (fork && source == null) {
|
||||||
|
populate();
|
||||||
|
}
|
||||||
|
if (source == null) {
|
||||||
return null;
|
return null;
|
||||||
if (source.root == null)
|
}
|
||||||
source = root.getRepository(source.getFullName());
|
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2495,10 +2538,13 @@ public class GHRepository extends GHObject {
|
|||||||
* @see #getSource() #getSource()
|
* @see #getSource() #getSource()
|
||||||
*/
|
*/
|
||||||
public GHRepository getParent() throws IOException {
|
public GHRepository getParent() throws IOException {
|
||||||
if (parent == null)
|
if (fork && parent == null) {
|
||||||
|
populate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent == null) {
|
||||||
return null;
|
return null;
|
||||||
if (parent.root == null)
|
}
|
||||||
parent = root.getRepository(parent.getFullName());
|
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2717,8 +2763,9 @@ public class GHRepository extends GHObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String getApiTailUrl(String tail) {
|
String getApiTailUrl(String tail) {
|
||||||
if (tail.length() > 0 && !tail.startsWith("/"))
|
if (tail.length() > 0 && !tail.startsWith("/")) {
|
||||||
tail = '/' + tail;
|
tail = '/' + tail;
|
||||||
|
}
|
||||||
return "/repos/" + getOwnerName() + "/" + name + tail;
|
return "/repos/" + getOwnerName() + "/" + name + tail;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2825,9 +2872,24 @@ public class GHRepository extends GHObject {
|
|||||||
* The IO exception
|
* The IO exception
|
||||||
*/
|
*/
|
||||||
void populate() throws IOException {
|
void populate() throws IOException {
|
||||||
if (root.isOffline())
|
if (root.isOffline()) {
|
||||||
return; // can't populate if the root is offline
|
return; // can't populate if the root is offline
|
||||||
|
}
|
||||||
|
|
||||||
root.createRequest().withApiUrl(root.getApiUrl() + full_name).fetchInto(this).wrap(root);
|
final URL url = Objects.requireNonNull(getUrl(), "Missing instance URL!");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// IMPORTANT: the url for repository records is does not reliably point to the API url.
|
||||||
|
// There is bug in Push event payloads that returns the wrong url.
|
||||||
|
// All other occurrences of "url" take the form "https://api.github.com/...".
|
||||||
|
// For Push event repository records, they take the form "https://github.com/{fullName}".
|
||||||
|
root.createRequest().withPreview(BAPTISE).setRawUrlPath(url.toString()).fetchInto(this).wrap(root);
|
||||||
|
} catch (HttpException e) {
|
||||||
|
if (e.getCause() instanceof JsonParseException) {
|
||||||
|
root.createRequest().withPreview(BAPTISE).withUrlPath("/repos/" + full_name).fetchInto(this).wrap(root);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package org.kohsuke.github;
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
|
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
|
||||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
@@ -315,21 +316,12 @@ public class GHRepositoryStatistics {
|
|||||||
* the io exception
|
* the io exception
|
||||||
*/
|
*/
|
||||||
public List<CodeFrequency> getCodeFrequency() throws IOException {
|
public List<CodeFrequency> getCodeFrequency() throws IOException {
|
||||||
// Map to arrays first, since there are no field names in the
|
|
||||||
// returned JSON.
|
|
||||||
try {
|
try {
|
||||||
Integer[][] list = root.createRequest()
|
CodeFrequency[] list = root.createRequest()
|
||||||
.withUrlPath(getApiTailUrl("code_frequency"))
|
.withUrlPath(getApiTailUrl("code_frequency"))
|
||||||
.fetch(Integer[][].class);
|
.fetch(CodeFrequency[].class);
|
||||||
|
|
||||||
// Convert to proper objects.
|
return Arrays.asList(list);
|
||||||
List<CodeFrequency> returnList = new ArrayList<>();
|
|
||||||
for (Integer[] item : list) {
|
|
||||||
CodeFrequency cf = new CodeFrequency(Arrays.asList(item));
|
|
||||||
returnList.add(cf);
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnList;
|
|
||||||
} catch (MismatchedInputException e) {
|
} catch (MismatchedInputException e) {
|
||||||
// This sometimes happens when retrieving code frequency statistics
|
// This sometimes happens when retrieving code frequency statistics
|
||||||
// for a repository for the first time. It is probably still being
|
// for a repository for the first time. It is probably still being
|
||||||
@@ -342,10 +334,12 @@ public class GHRepositoryStatistics {
|
|||||||
* The type CodeFrequency.
|
* The type CodeFrequency.
|
||||||
*/
|
*/
|
||||||
public static class CodeFrequency {
|
public static class CodeFrequency {
|
||||||
private int week;
|
|
||||||
private int additions;
|
|
||||||
private int deletions;
|
|
||||||
|
|
||||||
|
private final int week;
|
||||||
|
private final int additions;
|
||||||
|
private final int deletions;
|
||||||
|
|
||||||
|
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
|
||||||
private CodeFrequency(List<Integer> item) {
|
private CodeFrequency(List<Integer> item) {
|
||||||
week = item.get(0);
|
week = item.get(0);
|
||||||
additions = item.get(1);
|
additions = item.get(1);
|
||||||
@@ -428,7 +422,7 @@ public class GHRepositoryStatistics {
|
|||||||
* @return The list of commit counts for everyone combined, for the last 52 weeks.
|
* @return The list of commit counts for everyone combined, for the last 52 weeks.
|
||||||
*/
|
*/
|
||||||
public List<Integer> getAllCommits() {
|
public List<Integer> getAllCommits() {
|
||||||
return all;
|
return Collections.unmodifiableList(all);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -437,7 +431,7 @@ public class GHRepositoryStatistics {
|
|||||||
* @return The list of commit counts for the owner, for the last 52 weeks.
|
* @return The list of commit counts for the owner, for the last 52 weeks.
|
||||||
*/
|
*/
|
||||||
public List<Integer> getOwnerCommits() {
|
public List<Integer> getOwnerCommits() {
|
||||||
return owner;
|
return Collections.unmodifiableList(owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
Participation wrapUp(GitHub root) {
|
Participation wrapUp(GitHub root) {
|
||||||
@@ -455,28 +449,22 @@ public class GHRepositoryStatistics {
|
|||||||
* the io exception
|
* the io exception
|
||||||
*/
|
*/
|
||||||
public List<PunchCardItem> getPunchCard() throws IOException {
|
public List<PunchCardItem> getPunchCard() throws IOException {
|
||||||
// Map to ArrayLists first, since there are no field names in the
|
PunchCardItem[] list = root.createRequest()
|
||||||
// returned JSON.
|
.withUrlPath(getApiTailUrl("punch_card"))
|
||||||
Integer[][] list = root.createRequest().withUrlPath(getApiTailUrl("punch_card")).fetch(Integer[][].class);
|
.fetch(PunchCardItem[].class);
|
||||||
|
return Arrays.asList(list);
|
||||||
// Convert to proper objects.
|
|
||||||
ArrayList<PunchCardItem> returnList = new ArrayList<>();
|
|
||||||
for (Integer[] item : list) {
|
|
||||||
PunchCardItem pci = new PunchCardItem(Arrays.asList(item));
|
|
||||||
returnList.add(pci);
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type PunchCardItem.
|
* The type PunchCardItem.
|
||||||
*/
|
*/
|
||||||
public static class PunchCardItem {
|
public static class PunchCardItem {
|
||||||
private int dayOfWeek;
|
|
||||||
private int hourOfDay;
|
|
||||||
private int numberOfCommits;
|
|
||||||
|
|
||||||
|
private final int dayOfWeek;
|
||||||
|
private final int hourOfDay;
|
||||||
|
private final int numberOfCommits;
|
||||||
|
|
||||||
|
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
|
||||||
private PunchCardItem(List<Integer> item) {
|
private PunchCardItem(List<Integer> item) {
|
||||||
dayOfWeek = item.get(0);
|
dayOfWeek = item.get(0);
|
||||||
hourOfDay = item.get(1);
|
hourOfDay = item.get(1);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public abstract class GHSearchBuilder<T> extends GHQueryBuilder<T> {
|
|||||||
super(root);
|
super(root);
|
||||||
this.receiverType = receiverType;
|
this.receiverType = receiverType;
|
||||||
req.withUrlPath(getApiUrl());
|
req.withUrlPath(getApiUrl());
|
||||||
|
req.rateLimit(RateLimitTarget.SEARCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,23 +1,27 @@
|
|||||||
package org.kohsuke.github;
|
package org.kohsuke.github;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A team in GitHub organization.
|
* A team in GitHub organization.
|
||||||
*
|
*
|
||||||
* @author Kohsuke Kawaguchi
|
* @author Kohsuke Kawaguchi
|
||||||
*/
|
*/
|
||||||
public class GHTeam implements Refreshable {
|
public class GHTeam extends GHObject implements Refreshable {
|
||||||
|
private String html_url;
|
||||||
private String name;
|
private String name;
|
||||||
private String permission;
|
private String permission;
|
||||||
private String slug;
|
private String slug;
|
||||||
private String description;
|
private String description;
|
||||||
private Privacy privacy;
|
private Privacy privacy;
|
||||||
|
|
||||||
private int id;
|
|
||||||
private GHOrganization organization; // populated by GET /user/teams where Teams+Orgs are returned together
|
private GHOrganization organization; // populated by GET /user/teams where Teams+Orgs are returned together
|
||||||
|
|
||||||
protected /* final */ GitHub root;
|
protected /* final */ GitHub root;
|
||||||
@@ -130,12 +134,33 @@ public class GHTeam implements Refreshable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets id.
|
* Retrieves the discussions.
|
||||||
*
|
*
|
||||||
* @return the id
|
* @return the paged iterable
|
||||||
|
* @throws IOException
|
||||||
|
* the io exception
|
||||||
*/
|
*/
|
||||||
public int getId() {
|
@Nonnull
|
||||||
return id;
|
public PagedIterable<GHDiscussion> listDiscussions() throws IOException {
|
||||||
|
return GHDiscussion.readAll(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a single discussion by ID.
|
||||||
|
*
|
||||||
|
* @param discussionNumber
|
||||||
|
* id of the discussion that we want to query for
|
||||||
|
* @return the discussion
|
||||||
|
* @throws java.io.FileNotFoundException
|
||||||
|
* if the discussion does not exist
|
||||||
|
* @throws IOException
|
||||||
|
* the io exception
|
||||||
|
*
|
||||||
|
* @see <a href= "https://developer.github.com/v3/teams/discussions/#get-a-discussion">documentation</a>
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public GHDiscussion getDiscussion(long discussionNumber) throws IOException {
|
||||||
|
return GHDiscussion.read(this, discussionNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -149,6 +174,19 @@ public class GHTeam implements Refreshable {
|
|||||||
return root.createRequest().withUrlPath(api("/members")).toIterable(GHUser[].class, item -> item.wrapUp(root));
|
return root.createRequest().withUrlPath(api("/members")).toIterable(GHUser[].class, item -> item.wrapUp(root));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the teams that are children of this team.
|
||||||
|
*
|
||||||
|
* @return the paged iterable
|
||||||
|
* @throws IOException
|
||||||
|
* the io exception
|
||||||
|
*/
|
||||||
|
public PagedIterable<GHTeam> listChildTeams() throws IOException {
|
||||||
|
return root.createRequest()
|
||||||
|
.withUrlPath(api("/teams"))
|
||||||
|
.toIterable(GHTeam[].class, item -> item.wrapUp(this.organization));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets members.
|
* Gets members.
|
||||||
*
|
*
|
||||||
@@ -169,7 +207,7 @@ public class GHTeam implements Refreshable {
|
|||||||
*/
|
*/
|
||||||
public boolean hasMember(GHUser user) {
|
public boolean hasMember(GHUser user) {
|
||||||
try {
|
try {
|
||||||
root.createRequest().withUrlPath("/teams/" + id + "/members/" + user.getLogin()).send();
|
root.createRequest().withUrlPath("/teams/" + getId() + "/members/" + user.getLogin()).send();
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException ignore) {
|
} catch (IOException ignore) {
|
||||||
return false;
|
return false;
|
||||||
@@ -302,7 +340,22 @@ public class GHTeam implements Refreshable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String api(String tail) {
|
private String api(String tail) {
|
||||||
return "/teams/" + id + tail;
|
return "/teams/" + getId() + tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begins the creation of a new instance.
|
||||||
|
*
|
||||||
|
* 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 GHDiscussion.Creator createDiscussion(String title) throws IOException {
|
||||||
|
return GHDiscussion.create(this).title(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -321,4 +374,28 @@ public class GHTeam implements Refreshable {
|
|||||||
public void refresh() throws IOException {
|
public void refresh() throws IOException {
|
||||||
root.createRequest().withUrlPath(api("")).fetchInto(this).wrapUp(root);
|
root.createRequest().withUrlPath(api("")).fetchInto(this).wrapUp(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URL getHtmlUrl() {
|
||||||
|
return GitHubClient.parseURL(html_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) && privacy == ghTeam.privacy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(name, getUrl(), permission, slug, description, privacy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ public class GHTeamBuilder {
|
|||||||
* parentTeamId of team
|
* parentTeamId of team
|
||||||
* @return a builder to continue with building
|
* @return a builder to continue with building
|
||||||
*/
|
*/
|
||||||
public GHTeamBuilder parentTeamId(int parentTeamId) {
|
public GHTeamBuilder parentTeamId(long parentTeamId) {
|
||||||
this.builder.with("parent_team_id", parentTeamId);
|
this.builder.with("parent_team_id", parentTeamId);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,6 +173,19 @@ public class GHUser extends GHPerson {
|
|||||||
return org.hasPublicMember(this);
|
return org.hasPublicMember(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this user is marked as hireable, false otherwise
|
||||||
|
*
|
||||||
|
* @return if the user is marked as hireable
|
||||||
|
*/
|
||||||
|
public boolean isHireable() {
|
||||||
|
return hireable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBio() {
|
||||||
|
return bio;
|
||||||
|
}
|
||||||
|
|
||||||
static GHUser[] wrap(GHUser[] users, GitHub root) {
|
static GHUser[] wrap(GHUser[] users, GitHub root) {
|
||||||
for (GHUser f : users)
|
for (GHUser f : users)
|
||||||
f.root = root;
|
f.root = root;
|
||||||
|
|||||||
@@ -373,12 +373,20 @@ public class GitHub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current rate limit.
|
* 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
|
* @return the rate limit
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* the io exception
|
* the io exception
|
||||||
*/
|
*/
|
||||||
|
@Nonnull
|
||||||
public GHRateLimit getRateLimit() throws IOException {
|
public GHRateLimit getRateLimit() throws IOException {
|
||||||
return client.getRateLimit();
|
return client.getRateLimit();
|
||||||
}
|
}
|
||||||
@@ -388,8 +396,11 @@ public class GitHub {
|
|||||||
* GitHub Enterprise) or if no requests have been made.
|
* GitHub Enterprise) or if no requests have been made.
|
||||||
*
|
*
|
||||||
* @return the most recently observed rate limit data or {@code null}.
|
* @return the most recently observed rate limit data or {@code null}.
|
||||||
|
* @deprecated implement a {@link RateLimitChecker} and add it via
|
||||||
|
* {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}.
|
||||||
*/
|
*/
|
||||||
@CheckForNull
|
@Nonnull
|
||||||
|
@Deprecated
|
||||||
public GHRateLimit lastRateLimit() {
|
public GHRateLimit lastRateLimit() {
|
||||||
return client.lastRateLimit();
|
return client.lastRateLimit();
|
||||||
}
|
}
|
||||||
@@ -400,10 +411,13 @@ public class GitHub {
|
|||||||
* @return the current rate limit data.
|
* @return the current rate limit data.
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* if we couldn't get the current rate limit data.
|
* if we couldn't get the current rate limit data.
|
||||||
|
* @deprecated implement a {@link RateLimitChecker} and add it via
|
||||||
|
* {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}.
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@Deprecated
|
||||||
public GHRateLimit rateLimit() throws IOException {
|
public GHRateLimit rateLimit() throws IOException {
|
||||||
return client.rateLimit();
|
return client.rateLimit(RateLimitTarget.CORE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -413,7 +427,7 @@ public class GitHub {
|
|||||||
* @throws IOException
|
* @throws IOException
|
||||||
* the io exception
|
* the io exception
|
||||||
*/
|
*/
|
||||||
@WithBridgeMethods(GHUser.class)
|
@WithBridgeMethods(value = GHUser.class)
|
||||||
public GHMyself getMyself() throws IOException {
|
public GHMyself getMyself() throws IOException {
|
||||||
client.requireCredential();
|
client.requireCredential();
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
@@ -518,7 +532,7 @@ public class GitHub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the repository object from 'user/reponame' string that GitHub calls as "repository name"
|
* Gets the repository object from 'owner/repo' string that GitHub calls as "repository name"
|
||||||
*
|
*
|
||||||
* @param name
|
* @param name
|
||||||
* the name
|
* the name
|
||||||
@@ -529,9 +543,10 @@ public class GitHub {
|
|||||||
*/
|
*/
|
||||||
public GHRepository getRepository(String name) throws IOException {
|
public GHRepository getRepository(String name) throws IOException {
|
||||||
String[] tokens = name.split("/");
|
String[] tokens = name.split("/");
|
||||||
return createRequest().withUrlPath("/repos/" + tokens[0] + '/' + tokens[1])
|
if (tokens.length < 2) {
|
||||||
.fetch(GHRepository.class)
|
throw new IllegalArgumentException("Repository name must be in format owner/repo");
|
||||||
.wrap(this);
|
}
|
||||||
|
return GHRepository.read(this, tokens[0], tokens[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -727,7 +742,7 @@ public class GitHub {
|
|||||||
* @throws IOException
|
* @throws IOException
|
||||||
* the io exception
|
* the io exception
|
||||||
*
|
*
|
||||||
* @deprecated Use {@link GHOrganization#getTeam(int)}
|
* @deprecated Use {@link GHOrganization#getTeam(long)}
|
||||||
* @see <a href= "https://developer.github.com/v3/teams/#get-team-legacy">deprecation notice</a>
|
* @see <a href= "https://developer.github.com/v3/teams/#get-team-legacy">deprecation notice</a>
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import java.io.FileNotFoundException;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
@@ -359,6 +358,18 @@ public class GitHubBuilder implements Cloneable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a {@link RateLimitChecker} for the Core API for this {@link GitHubBuilder}.
|
||||||
|
*
|
||||||
|
* @param coreRateLimitChecker
|
||||||
|
* the {@link RateLimitChecker} for core GitHub API requests
|
||||||
|
* @return the git hub builder
|
||||||
|
* @see #withRateLimitChecker(RateLimitChecker, RateLimitTarget)
|
||||||
|
*/
|
||||||
|
public GitHubBuilder withRateLimitChecker(@Nonnull RateLimitChecker coreRateLimitChecker) {
|
||||||
|
return withRateLimitChecker(coreRateLimitChecker, RateLimitTarget.CORE);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a {@link RateLimitChecker} to this {@link GitHubBuilder}.
|
* Adds a {@link RateLimitChecker} to this {@link GitHubBuilder}.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -377,15 +388,15 @@ public class GitHubBuilder implements Cloneable {
|
|||||||
* request.
|
* request.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param coreRateLimitChecker
|
* @param rateLimitChecker
|
||||||
* the {@link RateLimitChecker} for core GitHub API requests
|
* the {@link RateLimitChecker} for requests
|
||||||
|
* @param rateLimitTarget
|
||||||
|
* the {@link RateLimitTarget} specifying which rate limit record to check
|
||||||
* @return the git hub builder
|
* @return the git hub builder
|
||||||
*/
|
*/
|
||||||
public GitHubBuilder withRateLimitChecker(@Nonnull RateLimitChecker coreRateLimitChecker) {
|
public GitHubBuilder withRateLimitChecker(@Nonnull RateLimitChecker rateLimitChecker,
|
||||||
this.rateLimitChecker = new GitHubRateLimitChecker(coreRateLimitChecker,
|
@Nonnull RateLimitTarget rateLimitTarget) {
|
||||||
RateLimitChecker.NONE,
|
this.rateLimitChecker = this.rateLimitChecker.with(rateLimitChecker, rateLimitTarget);
|
||||||
RateLimitChecker.NONE,
|
|
||||||
RateLimitChecker.NONE);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import com.fasterxml.jackson.databind.ObjectReader;
|
|||||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||||
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
|
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
|
||||||
import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
|
import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -19,15 +19,15 @@ import java.net.SocketException;
|
|||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.text.ParseException;
|
import java.time.Instant;
|
||||||
import java.text.SimpleDateFormat;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.TimeZone;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ import static java.util.logging.Level.*;
|
|||||||
* A GitHub API Client
|
* A GitHub API Client
|
||||||
* <p>
|
* <p>
|
||||||
* A GitHubClient can be used to send requests and retrieve their responses. GitHubClient is thread-safe and can be used
|
* A GitHubClient can be used to send requests and retrieve their responses. GitHubClient is thread-safe and can be used
|
||||||
* to send multiple requests. GitHubClient also track some GitHub API information such as {@link #rateLimit()}.
|
* to send multiple requests. GitHubClient also track some GitHub API information such as {@link GHRateLimit}.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
abstract class GitHubClient {
|
abstract class GitHubClient {
|
||||||
@@ -70,18 +70,18 @@ abstract class GitHubClient {
|
|||||||
|
|
||||||
private HttpConnector connector;
|
private HttpConnector connector;
|
||||||
|
|
||||||
private final Object headerRateLimitLock = new Object();
|
private final Object rateLimitLock = new Object();
|
||||||
private GHRateLimit headerRateLimit = null;
|
|
||||||
private volatile GHRateLimit rateLimit = null;
|
@Nonnull
|
||||||
|
private GHRateLimit rateLimit = GHRateLimit.DEFAULT;
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(GitHubClient.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(GitHubClient.class.getName());
|
||||||
|
|
||||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||||
static final String GITHUB_URL = "https://api.github.com";
|
static final String GITHUB_URL = "https://api.github.com";
|
||||||
|
|
||||||
private static final String[] TIME_FORMATS = { "yyyy/MM/dd HH:mm:ss ZZZZ", "yyyy-MM-dd'T'HH:mm:ss'Z'",
|
private static final DateTimeFormatter DATE_TIME_PARSER_SLASHES = DateTimeFormatter
|
||||||
"yyyy-MM-dd'T'HH:mm:ss.S'Z'" // GitHub App endpoints return a different date format
|
.ofPattern("yyyy/MM/dd HH:mm:ss Z");
|
||||||
};
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
MAPPER.setVisibility(new VisibilityChecker.Std(NONE, NONE, NONE, NONE, ANY));
|
MAPPER.setVisibility(new VisibilityChecker.Std(NONE, NONE, NONE, NONE, ANY));
|
||||||
@@ -141,10 +141,8 @@ abstract class GitHubClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private <T> T fetch(Class<T> type, String urlPath) throws IOException {
|
private <T> T fetch(Class<T> type, String urlPath) throws IOException {
|
||||||
return this
|
GitHubRequest request = GitHubRequest.newBuilder().withApiUrl(getApiUrl()).withUrlPath(urlPath).build();
|
||||||
.sendRequest(GitHubRequest.newBuilder().withApiUrl(getApiUrl()).withUrlPath(urlPath).build(),
|
return this.sendRequest(request, (responseInfo) -> GitHubResponse.parseBody(responseInfo, type)).body();
|
||||||
(responseInfo) -> GitHubResponse.parseBody(responseInfo, type))
|
|
||||||
.body();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -208,57 +206,110 @@ abstract class GitHubClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current rate limit from the server.
|
* 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
|
* @return the rate limit
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* the io exception
|
* the io exception
|
||||||
*/
|
*/
|
||||||
|
@Nonnull
|
||||||
public GHRateLimit getRateLimit() throws IOException {
|
public GHRateLimit getRateLimit() throws IOException {
|
||||||
GHRateLimit rateLimit;
|
return getRateLimit(RateLimitTarget.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
GHRateLimit getRateLimit(@Nonnull RateLimitTarget rateLimitTarget) throws IOException {
|
||||||
|
GHRateLimit result;
|
||||||
try {
|
try {
|
||||||
rateLimit = fetch(JsonRateLimit.class, "/rate_limit").resources;
|
GitHubRequest request = GitHubRequest.newBuilder()
|
||||||
|
.rateLimit(RateLimitTarget.NONE)
|
||||||
|
.withApiUrl(getApiUrl())
|
||||||
|
.withUrlPath("/rate_limit")
|
||||||
|
.build();
|
||||||
|
result = this
|
||||||
|
.sendRequest(request, (responseInfo) -> GitHubResponse.parseBody(responseInfo, JsonRateLimit.class))
|
||||||
|
.body().resources;
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
// GitHub Enterprise doesn't have the rate limit
|
// For some versions of GitHub Enterprise, the rate_limit endpoint returns a 404.
|
||||||
// return a default rate limit that
|
LOGGER.log(FINE, "/rate_limit returned 404 Not Found.");
|
||||||
rateLimit = GHRateLimit.Unknown();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.rateLimit = rateLimit;
|
// 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 updateRateLimit(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the most recently observed rate limit data or {@code null} if either there is no rate limit (for example
|
* Returns the most recently observed rate limit data.
|
||||||
* GitHub Enterprise) or if no requests have been made.
|
|
||||||
*
|
*
|
||||||
* @return the most recently observed rate limit data or {@code null}.
|
* Generally, instead of calling this you should implement a {@link RateLimitChecker} or call
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
public GHRateLimit lastRateLimit() {
|
|
||||||
synchronized (headerRateLimitLock) {
|
|
||||||
return headerRateLimit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current rate limit while trying not to actually make any remote requests unless absolutely necessary.
|
|
||||||
*
|
*
|
||||||
* @return the current rate limit data.
|
* @return the most recently observed rate limit data. This may include expired or
|
||||||
* @throws IOException
|
* {@link GHRateLimit.UnknownLimitRecord} entries.
|
||||||
* if we couldn't get the current rate limit data.
|
* @deprecated implement a {@link RateLimitChecker} and add it via
|
||||||
|
* {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}.
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public GHRateLimit rateLimit() throws IOException {
|
@Deprecated
|
||||||
synchronized (headerRateLimitLock) {
|
GHRateLimit lastRateLimit() {
|
||||||
if (headerRateLimit != null && !headerRateLimit.isExpired()) {
|
synchronized (rateLimitLock) {
|
||||||
return headerRateLimit;
|
return rateLimit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
GHRateLimit rateLimit(@Nonnull RateLimitTarget rateLimitTarget) throws IOException {
|
||||||
|
synchronized (rateLimitLock) {
|
||||||
|
if (rateLimit.getRecord(rateLimitTarget).isExpired()) {
|
||||||
|
getRateLimit(rateLimitTarget);
|
||||||
}
|
}
|
||||||
|
return rateLimit;
|
||||||
}
|
}
|
||||||
GHRateLimit rateLimit = this.rateLimit;
|
}
|
||||||
if (rateLimit == null || rateLimit.isExpired()) {
|
|
||||||
rateLimit = getRateLimit();
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @param observed
|
||||||
|
* {@link GHRateLimit.Record} constructed from the response header information
|
||||||
|
*/
|
||||||
|
private GHRateLimit updateRateLimit(@Nonnull GHRateLimit observed) {
|
||||||
|
synchronized (rateLimitLock) {
|
||||||
|
observed = rateLimit.getMergedRateLimit(observed);
|
||||||
|
|
||||||
|
if (rateLimit != observed) {
|
||||||
|
rateLimit = observed;
|
||||||
|
LOGGER.log(FINE, "Rate limit now: {0}", rateLimit);
|
||||||
|
}
|
||||||
|
return rateLimit;
|
||||||
}
|
}
|
||||||
return rateLimit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -337,39 +388,43 @@ abstract class GitHubClient {
|
|||||||
|
|
||||||
GitHubResponse.ResponseInfo responseInfo = null;
|
GitHubResponse.ResponseInfo responseInfo = null;
|
||||||
try {
|
try {
|
||||||
if (LOGGER.isLoggable(FINE)) {
|
try {
|
||||||
LOGGER.log(FINE,
|
if (LOGGER.isLoggable(FINE)) {
|
||||||
"GitHub API request [" + (login == null ? "anonymous" : login) + "]: " + request.method()
|
LOGGER.log(FINE,
|
||||||
+ " " + request.url().toString());
|
"GitHub API request [" + (login == null ? "anonymous" : login) + "]: "
|
||||||
|
+ request.method() + " " + request.url().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
rateLimitChecker.checkRateLimit(this, request);
|
||||||
|
|
||||||
|
responseInfo = getResponseInfo(request);
|
||||||
|
noteRateLimit(responseInfo);
|
||||||
|
detectOTPRequired(responseInfo);
|
||||||
|
|
||||||
|
if (isInvalidCached404Response(responseInfo)) {
|
||||||
|
// Setting "Cache-Control" to "no-cache" stops the cache from supplying
|
||||||
|
// "If-Modified-Since" or "If-None-Match" values.
|
||||||
|
// This makes GitHub give us current data (not incorrectly cached data)
|
||||||
|
request = request.toBuilder().setHeader("Cache-Control", "no-cache").build();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!(isRateLimitResponse(responseInfo) || isAbuseLimitResponse(responseInfo))) {
|
||||||
|
return createResponse(responseInfo, handler);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// For transient errors, retry
|
||||||
|
if (retryConnectionError(e, request.url(), retries)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw interpretApiError(e, request, responseInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
rateLimitChecker.checkRateLimit(this, request);
|
handleLimitingErrors(responseInfo);
|
||||||
|
} finally {
|
||||||
responseInfo = getResponseInfo(request);
|
IOUtils.closeQuietly(responseInfo);
|
||||||
noteRateLimit(responseInfo);
|
|
||||||
detectOTPRequired(responseInfo);
|
|
||||||
|
|
||||||
if (isInvalidCached404Response(responseInfo)) {
|
|
||||||
// Setting "Cache-Control" to "no-cache" stops the cache from supplying
|
|
||||||
// "If-Modified-Since" or "If-None-Match" values.
|
|
||||||
// This makes GitHub give us current data (not incorrectly cached data)
|
|
||||||
request = request.toBuilder().withHeader("Cache-Control", "no-cache").build();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!(isRateLimitResponse(responseInfo) || isAbuseLimitResponse(responseInfo))) {
|
|
||||||
return createResponse(responseInfo, handler);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
// For transient errors, retry
|
|
||||||
if (retryConnectionError(e, request.url(), retries)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw interpretApiError(e, request, responseInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLimitingErrors(responseInfo);
|
|
||||||
|
|
||||||
} while (--retries >= 0);
|
} while (--retries >= 0);
|
||||||
|
|
||||||
throw new GHIOException("Ran out of retries for URL: " + request.url().toString());
|
throw new GHIOException("Ran out of retries for URL: " + request.url().toString());
|
||||||
@@ -498,58 +553,25 @@ abstract class GitHubClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void noteRateLimit(@Nonnull GitHubResponse.ResponseInfo responseInfo) {
|
private void noteRateLimit(@Nonnull GitHubResponse.ResponseInfo responseInfo) {
|
||||||
if (responseInfo.request().urlPath().startsWith("/search")) {
|
|
||||||
// the search API uses a different rate limit
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String limitString = responseInfo.headerField("X-RateLimit-Limit");
|
|
||||||
if (StringUtils.isBlank(limitString)) {
|
|
||||||
// if we are missing a header, return fast
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String remainingString = responseInfo.headerField("X-RateLimit-Remaining");
|
|
||||||
if (StringUtils.isBlank(remainingString)) {
|
|
||||||
// if we are missing a header, return fast
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String resetString = responseInfo.headerField("X-RateLimit-Reset");
|
|
||||||
if (StringUtils.isBlank(resetString)) {
|
|
||||||
// if we are missing a header, return fast
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int limit, remaining;
|
|
||||||
long reset;
|
|
||||||
try {
|
try {
|
||||||
|
String limitString = Objects.requireNonNull(responseInfo.headerField("X-RateLimit-Limit"),
|
||||||
|
"Missing X-RateLimit-Limit");
|
||||||
|
String remainingString = Objects.requireNonNull(responseInfo.headerField("X-RateLimit-Remaining"),
|
||||||
|
"Missing X-RateLimit-Remaining");
|
||||||
|
String resetString = Objects.requireNonNull(responseInfo.headerField("X-RateLimit-Reset"),
|
||||||
|
"Missing X-RateLimit-Reset");
|
||||||
|
int limit, remaining;
|
||||||
|
long reset;
|
||||||
limit = Integer.parseInt(limitString);
|
limit = Integer.parseInt(limitString);
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
if (LOGGER.isLoggable(FINEST)) {
|
|
||||||
LOGGER.log(FINEST, "Malformed X-RateLimit-Limit header value " + limitString, e);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
|
|
||||||
remaining = Integer.parseInt(remainingString);
|
remaining = Integer.parseInt(remainingString);
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
if (LOGGER.isLoggable(FINEST)) {
|
|
||||||
LOGGER.log(FINEST, "Malformed X-RateLimit-Remaining header value " + remainingString, e);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
reset = Long.parseLong(resetString);
|
reset = Long.parseLong(resetString);
|
||||||
} catch (NumberFormatException e) {
|
GHRateLimit.Record observed = new GHRateLimit.Record(limit, remaining, reset, responseInfo);
|
||||||
|
updateRateLimit(GHRateLimit.fromRecord(observed, responseInfo.request().rateLimitTarget()));
|
||||||
|
} catch (NumberFormatException | NullPointerException e) {
|
||||||
if (LOGGER.isLoggable(FINEST)) {
|
if (LOGGER.isLoggable(FINEST)) {
|
||||||
LOGGER.log(FINEST, "Malformed X-RateLimit-Reset header value " + resetString, e);
|
LOGGER.log(FINEST, "Missing or malformed X-RateLimit header: ", e);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GHRateLimit.Record observed = new GHRateLimit.Record(limit, remaining, reset, responseInfo);
|
|
||||||
|
|
||||||
updateCoreRateLimit(observed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void detectOTPRequired(@Nonnull GitHubResponse.ResponseInfo responseInfo) throws GHIOException {
|
private static void detectOTPRequired(@Nonnull GitHubResponse.ResponseInfo responseInfo) throws GHIOException {
|
||||||
@@ -569,23 +591,6 @@ abstract class GitHubClient {
|
|||||||
"This operation requires a credential but none is given to the GitHub constructor");
|
"This operation requires a credential but none is given to the GitHub constructor");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the Rate Limit with the latest info from response header. Due to multi-threading requests might complete
|
|
||||||
* out of order, we want to pick the one with the most recent info from the server. Calls
|
|
||||||
* {@link #shouldReplace(GHRateLimit.Record, GHRateLimit.Record)}
|
|
||||||
*
|
|
||||||
* @param observed
|
|
||||||
* {@link GHRateLimit.Record} constructed from the response header information
|
|
||||||
*/
|
|
||||||
private void updateCoreRateLimit(@Nonnull GHRateLimit.Record observed) {
|
|
||||||
synchronized (headerRateLimitLock) {
|
|
||||||
if (headerRateLimit == null || shouldReplace(observed, headerRateLimit.getCore())) {
|
|
||||||
headerRateLimit = GHRateLimit.fromHeaderRecord(observed);
|
|
||||||
LOGGER.log(FINE, "Rate limit now: {0}", headerRateLimit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class GHApiInfo {
|
private static class GHApiInfo {
|
||||||
private String rate_limit_url;
|
private String rate_limit_url;
|
||||||
|
|
||||||
@@ -635,37 +640,6 @@ abstract class GitHubClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if one {@link GHRateLimit.Record} should replace another. Header date is only accurate to the second,
|
|
||||||
* so we look at the information in the record itself.
|
|
||||||
*
|
|
||||||
* {@link GHRateLimit.UnknownLimitRecord}s are always replaced by regular {@link GHRateLimit.Record}s. Regular
|
|
||||||
* {@link GHRateLimit.Record}s are never replaced by {@link GHRateLimit.UnknownLimitRecord}s. Candidates with
|
|
||||||
* resetEpochSeconds later than current record are more recent. Candidates with the same reset and a lower remaining
|
|
||||||
* count are more recent. Candidates with an earlier reset are older.
|
|
||||||
*
|
|
||||||
* @param candidate
|
|
||||||
* {@link GHRateLimit.Record} constructed from the response header information
|
|
||||||
* @param current
|
|
||||||
* the current {@link GHRateLimit.Record} record
|
|
||||||
*/
|
|
||||||
static boolean shouldReplace(@Nonnull GHRateLimit.Record candidate, @Nonnull GHRateLimit.Record current) {
|
|
||||||
if (candidate instanceof GHRateLimit.UnknownLimitRecord
|
|
||||||
&& !(current instanceof GHRateLimit.UnknownLimitRecord)) {
|
|
||||||
// Unknown candidate never replaces a regular record
|
|
||||||
return false;
|
|
||||||
} else if (current instanceof GHRateLimit.UnknownLimitRecord
|
|
||||||
&& !(candidate instanceof GHRateLimit.UnknownLimitRecord)) {
|
|
||||||
// Any real record should replace an unknown Record.
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// records of the same type compare to each other as normal.
|
|
||||||
return current.getResetEpochSeconds() < candidate.getResetEpochSeconds()
|
|
||||||
|| (current.getResetEpochSeconds() == candidate.getResetEpochSeconds()
|
|
||||||
&& current.getRemaining() > candidate.getRemaining());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static URL parseURL(String s) {
|
static URL parseURL(String s) {
|
||||||
try {
|
try {
|
||||||
return s == null ? null : new URL(s);
|
return s == null ? null : new URL(s);
|
||||||
@@ -677,22 +651,24 @@ abstract class GitHubClient {
|
|||||||
static Date parseDate(String timestamp) {
|
static Date parseDate(String timestamp) {
|
||||||
if (timestamp == null)
|
if (timestamp == null)
|
||||||
return null;
|
return null;
|
||||||
for (String f : TIME_FORMATS) {
|
|
||||||
try {
|
return Date.from(parseInstant(timestamp));
|
||||||
SimpleDateFormat df = new SimpleDateFormat(f);
|
}
|
||||||
df.setTimeZone(TimeZone.getTimeZone("GMT"));
|
|
||||||
return df.parse(timestamp);
|
static Instant parseInstant(String timestamp) {
|
||||||
} catch (ParseException e) {
|
if (timestamp == null)
|
||||||
// try next
|
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));
|
||||||
}
|
}
|
||||||
throw new IllegalStateException("Unable to parse the timestamp: " + timestamp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static String printDate(Date dt) {
|
static String printDate(Date dt) {
|
||||||
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
return DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(dt.getTime()).truncatedTo(ChronoUnit.SECONDS));
|
||||||
df.setTimeZone(TimeZone.getTimeZone("GMT"));
|
|
||||||
return df.format(dt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
|
|||||||
* A GitHub API Client for HttpUrlConnection
|
* A GitHub API Client for HttpUrlConnection
|
||||||
* <p>
|
* <p>
|
||||||
* A GitHubClient can be used to send requests and retrieve their responses. GitHubClient is thread-safe and can be used
|
* A GitHubClient can be used to send requests and retrieve their responses. GitHubClient is thread-safe and can be used
|
||||||
* to send multiple requests. GitHubClient also track some GitHub API information such as {@link #rateLimit()}.
|
* to send multiple requests. GitHubClient also track some GitHub API information such as {@link GHRateLimit}.
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* GitHubHttpUrlConnectionClient gets a new {@link HttpURLConnection} for each call to send.
|
* GitHubHttpUrlConnectionClient gets a new {@link HttpURLConnection} for each call to send.
|
||||||
@@ -235,6 +235,10 @@ class GitHubHttpUrlConnectionClient extends GitHubClient {
|
|||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(GitHubClient.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(GitHubClient.class.getName());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
IOUtils.closeQuietly(connection.getInputStream());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,26 +3,28 @@ package org.kohsuke.github;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A GitHub API Rate Limit Checker called before each request. This class provides the basic infrastructure for calling
|
* A GitHub API Rate Limit Checker called before each request.
|
||||||
* the appropriate {@link RateLimitChecker} for a request and retrying as many times as needed. This class supports more
|
*
|
||||||
* complex throttling strategies and polling, but leaves the specifics to the {@link RateLimitChecker} implementations.
|
|
||||||
* <p>
|
* <p>
|
||||||
* GitHub allots a certain number of requests to each user or application per period of time (usually per hour). The
|
* GitHub allots a certain number of requests to each user or application per period of time. The number of requests
|
||||||
* number of requests remaining is returned in the response header and can also be requested using
|
* remaining and the time when the number will be reset is returned in the response header and can also be requested
|
||||||
* {@link GitHub#getRateLimit()}. This requests per interval is referred to as the "rate limit".
|
* using {@link GitHub#getRateLimit()}. The "requests per interval" is referred to as the "rate limit".
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* GitHub prefers that clients stop before exceeding their rate limit rather than stopping after they exceed it. The
|
* Different parts of the GitHub API have separate rate limits, but most of REST API uses {@link RateLimitTarget#CORE}.
|
||||||
* {@link RateLimitChecker} is called before each request to check the rate limit and wait if the checker criteria are
|
* Checking your rate limit using {@link GitHub#getRateLimit()} does not effect your rate limit. GitHub prefers that
|
||||||
* met.
|
* clients stop before exceeding their rate limit rather than stopping after they exceed it.
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* Checking your rate limit using {@link GitHub#getRateLimit()} does not effect your rate limit, but each {@link GitHub}
|
* This class provides the infrastructure for calling the appropriate {@link RateLimitChecker} before each request and
|
||||||
* instance will attempt to cache and reuse the last see rate limit rather than making a new request.
|
* retrying than call many times as needed. Each {@link RateLimitChecker} decides whether to wait and for how long. This
|
||||||
|
* allows for a wide range of {@link RateLimitChecker} implementations, including complex throttling strategies and
|
||||||
|
* polling.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
class GitHubRateLimitChecker {
|
class GitHubRateLimitChecker {
|
||||||
@@ -39,6 +41,8 @@ class GitHubRateLimitChecker {
|
|||||||
@Nonnull
|
@Nonnull
|
||||||
private final RateLimitChecker integrationManifest;
|
private final RateLimitChecker integrationManifest;
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(GitHubRateLimitChecker.class.getName());
|
||||||
|
|
||||||
GitHubRateLimitChecker() {
|
GitHubRateLimitChecker() {
|
||||||
this(RateLimitChecker.NONE, RateLimitChecker.NONE, RateLimitChecker.NONE, RateLimitChecker.NONE);
|
this(RateLimitChecker.NONE, RateLimitChecker.NONE, RateLimitChecker.NONE, RateLimitChecker.NONE);
|
||||||
}
|
}
|
||||||
@@ -48,40 +52,57 @@ class GitHubRateLimitChecker {
|
|||||||
@Nonnull RateLimitChecker graphql,
|
@Nonnull RateLimitChecker graphql,
|
||||||
@Nonnull RateLimitChecker integrationManifest) {
|
@Nonnull RateLimitChecker integrationManifest) {
|
||||||
this.core = Objects.requireNonNull(core);
|
this.core = Objects.requireNonNull(core);
|
||||||
|
|
||||||
// for now only support rate limiting on core
|
|
||||||
// remove these asserts when that changes
|
|
||||||
assert search == RateLimitChecker.NONE;
|
|
||||||
assert graphql == RateLimitChecker.NONE;
|
|
||||||
assert integrationManifest == RateLimitChecker.NONE;
|
|
||||||
|
|
||||||
this.search = Objects.requireNonNull(search);
|
this.search = Objects.requireNonNull(search);
|
||||||
this.graphql = Objects.requireNonNull(graphql);
|
this.graphql = Objects.requireNonNull(graphql);
|
||||||
this.integrationManifest = Objects.requireNonNull(integrationManifest);
|
this.integrationManifest = Objects.requireNonNull(integrationManifest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new {@link GitHubRateLimitChecker} with a new checker for a particular target.
|
||||||
|
*
|
||||||
|
* Only one {@link RateLimitChecker} is allowed per target.
|
||||||
|
*
|
||||||
|
* @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}
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether there is sufficient requests remaining within this client's rate limit quota to make the current
|
* Checks whether there is sufficient requests remaining within this client's rate limit quota to make the current
|
||||||
* request.
|
* request.
|
||||||
* <p>
|
* <p>
|
||||||
* This method does not do the actual check. Instead it select the appropriate {@link RateLimitChecker} and
|
* This method does not do the actual check. Instead it selects the appropriate {@link RateLimitChecker} and
|
||||||
* {@link GHRateLimit.Record} for the current request's urlPath. If the {@link RateLimitChecker} for this the
|
* {@link GHRateLimit.Record} for the current request's {@link RateLimitTarget}. It then calls
|
||||||
* current request's urlPath is {@link RateLimitChecker#NONE} the rate limit is not checked. If not, it calls
|
* {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)}.
|
||||||
* {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)}. which decides if the rate limit has been
|
|
||||||
* exceeded and then sleeps for as long is it choose.
|
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* It is up to the {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} which decide if the rate limit
|
* It is up to {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} to which decide if the rate limit
|
||||||
* has been exceeded. If it has, that method will sleep for as long is it chooses and then return {@code true}. If
|
* has been exceeded. If it has, {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} will sleep for as
|
||||||
* not, that method will return {@code false}.
|
* long is it chooses and then return {@code true}. If not, that method will return {@code false}.
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* As long as {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} returns {@code true}, this method
|
* As long as {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} returns {@code true}, this method
|
||||||
* will request updated rate limit information and call
|
* will request updated rate limit information and call
|
||||||
* {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} again. This looping allows implementers of
|
* {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} again. This looping allows different
|
||||||
* {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} to apply any number of strategies to
|
* {@link RateLimitChecker} implementations to apply any number of strategies to controlling the speed at which
|
||||||
* controlling the speed at which requests are made. When it returns {@code false} this method will return and the
|
* requests are made.
|
||||||
* request will be sent.
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* When the {@link RateLimitChecker} returns {@code false} this method will return and the request processing will
|
||||||
|
* continue.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* If the {@link RateLimitChecker} for this the current request's urlPath is {@link RateLimitChecker#NONE} the rate
|
||||||
|
* limit is not checked.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param client
|
* @param client
|
||||||
@@ -92,14 +113,14 @@ class GitHubRateLimitChecker {
|
|||||||
* if there is an I/O error
|
* if there is an I/O error
|
||||||
*/
|
*/
|
||||||
void checkRateLimit(GitHubClient client, GitHubRequest request) throws IOException {
|
void checkRateLimit(GitHubClient client, GitHubRequest request) throws IOException {
|
||||||
RateLimitChecker guard = selectChecker(request.urlPath());
|
RateLimitChecker guard = selectChecker(request.rateLimitTarget());
|
||||||
if (guard == RateLimitChecker.NONE) {
|
if (guard == RateLimitChecker.NONE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For the first rate limit, accept the current limit if a valid one is already present.
|
// For the first rate limit, accept the current limit if a valid one is already present.
|
||||||
GHRateLimit rateLimit = client.rateLimit();
|
GHRateLimit rateLimit = client.rateLimit(request.rateLimitTarget());
|
||||||
GHRateLimit.Record rateLimitRecord = rateLimit.getRecordForUrlPath(request.urlPath());
|
GHRateLimit.Record rateLimitRecord = rateLimit.getRecord(request.rateLimitTarget());
|
||||||
long waitCount = 0;
|
long waitCount = 0;
|
||||||
try {
|
try {
|
||||||
while (guard.checkRateLimit(rateLimitRecord, waitCount)) {
|
while (guard.checkRateLimit(rateLimitRecord, waitCount)) {
|
||||||
@@ -112,8 +133,8 @@ class GitHubRateLimitChecker {
|
|||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
|
|
||||||
// After the first wait, always request a new rate limit from the server.
|
// After the first wait, always request a new rate limit from the server.
|
||||||
rateLimit = client.getRateLimit();
|
rateLimit = client.getRateLimit(request.rateLimitTarget());
|
||||||
rateLimitRecord = rateLimit.getRecordForUrlPath(request.urlPath());
|
rateLimitRecord = rateLimit.getRecord(request.rateLimitTarget());
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
throw (IOException) new InterruptedIOException(e.getMessage()).initCause(e);
|
throw (IOException) new InterruptedIOException(e.getMessage()).initCause(e);
|
||||||
@@ -121,25 +142,28 @@ class GitHubRateLimitChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the appropriate {@link RateLimitChecker} for a particular url path. Similar to
|
* Gets the appropriate {@link RateLimitChecker} for a particular target.
|
||||||
* {@link GHRateLimit#getRecordForUrlPath(String)}.
|
|
||||||
*
|
*
|
||||||
* @param urlPath
|
* Analogous with {@link GHRateLimit#getRecord(RateLimitTarget)}.
|
||||||
* the url path of the request
|
*
|
||||||
* @return the {@link RateLimitChecker} for a url path.
|
* @param rateLimitTarget
|
||||||
|
* the rate limit to check
|
||||||
|
* @return the {@link RateLimitChecker} for a particular target
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private RateLimitChecker selectChecker(@Nonnull String urlPath) {
|
private RateLimitChecker selectChecker(@Nonnull RateLimitTarget rateLimitTarget) {
|
||||||
if (urlPath.equals("/rate_limit")) {
|
if (rateLimitTarget == RateLimitTarget.NONE) {
|
||||||
return RateLimitChecker.NONE;
|
return RateLimitChecker.NONE;
|
||||||
} else if (urlPath.startsWith("/search")) {
|
} else if (rateLimitTarget == RateLimitTarget.CORE) {
|
||||||
|
return core;
|
||||||
|
} else if (rateLimitTarget == RateLimitTarget.SEARCH) {
|
||||||
return search;
|
return search;
|
||||||
} else if (urlPath.startsWith("/graphql")) {
|
} else if (rateLimitTarget == RateLimitTarget.GRAPHQL) {
|
||||||
return graphql;
|
return graphql;
|
||||||
} else if (urlPath.startsWith("/app-manifests")) {
|
} else if (rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST) {
|
||||||
return integrationManifest;
|
return integrationManifest;
|
||||||
} else {
|
} else {
|
||||||
return core;
|
throw new IllegalArgumentException("Unknown rate limit target: " + rateLimitTarget.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class GitHubRequest {
|
|||||||
private final String apiUrl;
|
private final String apiUrl;
|
||||||
private final String urlPath;
|
private final String urlPath;
|
||||||
private final String method;
|
private final String method;
|
||||||
|
private final RateLimitTarget rateLimitTarget;
|
||||||
private final InputStream body;
|
private final InputStream body;
|
||||||
private final boolean forceBody;
|
private final boolean forceBody;
|
||||||
|
|
||||||
@@ -56,6 +57,7 @@ class GitHubRequest {
|
|||||||
@Nonnull String apiUrl,
|
@Nonnull String apiUrl,
|
||||||
@Nonnull String urlPath,
|
@Nonnull String urlPath,
|
||||||
@Nonnull String method,
|
@Nonnull String method,
|
||||||
|
@Nonnull RateLimitTarget rateLimitTarget,
|
||||||
@CheckForNull InputStream body,
|
@CheckForNull InputStream body,
|
||||||
boolean forceBody) throws MalformedURLException {
|
boolean forceBody) throws MalformedURLException {
|
||||||
this.args = Collections.unmodifiableList(new ArrayList<>(args));
|
this.args = Collections.unmodifiableList(new ArrayList<>(args));
|
||||||
@@ -64,6 +66,7 @@ class GitHubRequest {
|
|||||||
this.apiUrl = apiUrl;
|
this.apiUrl = apiUrl;
|
||||||
this.urlPath = urlPath;
|
this.urlPath = urlPath;
|
||||||
this.method = method;
|
this.method = method;
|
||||||
|
this.rateLimitTarget = rateLimitTarget;
|
||||||
this.body = body;
|
this.body = body;
|
||||||
this.forceBody = forceBody;
|
this.forceBody = forceBody;
|
||||||
String tailApiUrl = buildTailApiUrl();
|
String tailApiUrl = buildTailApiUrl();
|
||||||
@@ -119,6 +122,16 @@ class GitHubRequest {
|
|||||||
return 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
|
* The arguments for this request. Depending on the {@link #method()} and {@code #inBody()} these maybe added to the
|
||||||
* url or to the request body.
|
* url or to the request body.
|
||||||
@@ -217,7 +230,15 @@ class GitHubRequest {
|
|||||||
* @return a {@link Builder} based on this request.
|
* @return a {@link Builder} based on this request.
|
||||||
*/
|
*/
|
||||||
public Builder<?> toBuilder() {
|
public Builder<?> toBuilder() {
|
||||||
return new Builder<>(args, headers, injectedMappingValues, apiUrl, urlPath, method, body, forceBody);
|
return new Builder<>(args,
|
||||||
|
headers,
|
||||||
|
injectedMappingValues,
|
||||||
|
apiUrl,
|
||||||
|
urlPath,
|
||||||
|
method,
|
||||||
|
rateLimitTarget,
|
||||||
|
body,
|
||||||
|
forceBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildTailApiUrl() {
|
private String buildTailApiUrl() {
|
||||||
@@ -281,6 +302,10 @@ class GitHubRequest {
|
|||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private String method;
|
private String method;
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private RateLimitTarget rateLimitTarget;
|
||||||
|
|
||||||
private InputStream body;
|
private InputStream body;
|
||||||
private boolean forceBody;
|
private boolean forceBody;
|
||||||
|
|
||||||
@@ -294,6 +319,7 @@ class GitHubRequest {
|
|||||||
GitHubClient.GITHUB_URL,
|
GitHubClient.GITHUB_URL,
|
||||||
"/",
|
"/",
|
||||||
"GET",
|
"GET",
|
||||||
|
RateLimitTarget.CORE,
|
||||||
null,
|
null,
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
@@ -304,6 +330,7 @@ class GitHubRequest {
|
|||||||
@Nonnull String apiUrl,
|
@Nonnull String apiUrl,
|
||||||
@Nonnull String urlPath,
|
@Nonnull String urlPath,
|
||||||
@Nonnull String method,
|
@Nonnull String method,
|
||||||
|
@Nonnull RateLimitTarget rateLimitTarget,
|
||||||
@CheckForNull @WillClose InputStream body,
|
@CheckForNull @WillClose InputStream body,
|
||||||
boolean forceBody) {
|
boolean forceBody) {
|
||||||
this.args = new ArrayList<>(args);
|
this.args = new ArrayList<>(args);
|
||||||
@@ -312,6 +339,7 @@ class GitHubRequest {
|
|||||||
this.apiUrl = apiUrl;
|
this.apiUrl = apiUrl;
|
||||||
this.urlPath = urlPath;
|
this.urlPath = urlPath;
|
||||||
this.method = method;
|
this.method = method;
|
||||||
|
this.rateLimitTarget = rateLimitTarget;
|
||||||
this.body = body;
|
this.body = body;
|
||||||
this.forceBody = forceBody;
|
this.forceBody = forceBody;
|
||||||
}
|
}
|
||||||
@@ -324,7 +352,15 @@ class GitHubRequest {
|
|||||||
* if the GitHub API URL cannot be constructed
|
* if the GitHub API URL cannot be constructed
|
||||||
*/
|
*/
|
||||||
public GitHubRequest build() throws MalformedURLException {
|
public GitHubRequest build() throws MalformedURLException {
|
||||||
return new GitHubRequest(args, headers, injectedMappingValues, apiUrl, urlPath, method, body, forceBody);
|
return new GitHubRequest(args,
|
||||||
|
headers,
|
||||||
|
injectedMappingValues,
|
||||||
|
apiUrl,
|
||||||
|
urlPath,
|
||||||
|
method,
|
||||||
|
rateLimitTarget,
|
||||||
|
body,
|
||||||
|
forceBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -348,9 +384,11 @@ class GitHubRequest {
|
|||||||
* the name
|
* the name
|
||||||
* @param value
|
* @param value
|
||||||
* the value
|
* the value
|
||||||
|
* @return the request builder
|
||||||
*/
|
*/
|
||||||
public void setHeader(String name, String value) {
|
public B setHeader(String name, String value) {
|
||||||
headers.put(name, value);
|
headers.put(name, value);
|
||||||
|
return (B) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -363,8 +401,11 @@ class GitHubRequest {
|
|||||||
* @return the request builder
|
* @return the request builder
|
||||||
*/
|
*/
|
||||||
public B withHeader(String name, String value) {
|
public B withHeader(String name, String value) {
|
||||||
setHeader(name, value);
|
String oldValue = headers.get(name);
|
||||||
return (B) this;
|
if (!StringUtils.isBlank(oldValue)) {
|
||||||
|
value = oldValue + ", " + value;
|
||||||
|
}
|
||||||
|
return setHeader(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -562,6 +603,18 @@ class GitHubRequest {
|
|||||||
return (B) this;
|
return (B) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method requester.
|
||||||
|
*
|
||||||
|
* @param rateLimitTarget
|
||||||
|
* the rate limit target for this request. Default is {@link RateLimitTarget#CORE}.
|
||||||
|
* @return the request builder
|
||||||
|
*/
|
||||||
|
public B rateLimit(@Nonnull RateLimitTarget rateLimitTarget) {
|
||||||
|
this.rateLimitTarget = rateLimitTarget;
|
||||||
|
return (B) this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Content type requester.
|
* Content type requester.
|
||||||
*
|
*
|
||||||
@@ -583,13 +636,17 @@ class GitHubRequest {
|
|||||||
* when needing to set query parameters on requests methods that don't usually have them, such as
|
* when needing to set query parameters on requests methods that don't usually have them, such as
|
||||||
* {@link GHRelease#uploadAsset(String, InputStream, String)}.
|
* {@link GHRelease#uploadAsset(String, InputStream, String)}.
|
||||||
*
|
*
|
||||||
* @param urlOrPath
|
* @param rawUrlPath
|
||||||
* the content type
|
* the content type
|
||||||
* @return the request builder
|
* @return the request builder
|
||||||
*/
|
*/
|
||||||
B setRawUrlPath(String urlOrPath) {
|
B setRawUrlPath(@Nonnull String rawUrlPath) {
|
||||||
Objects.requireNonNull(urlOrPath);
|
Objects.requireNonNull(rawUrlPath);
|
||||||
this.urlPath = urlOrPath;
|
// 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;
|
return (B) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -603,10 +660,10 @@ class GitHubRequest {
|
|||||||
* the content type
|
* the content type
|
||||||
* @return the request builder
|
* @return the request builder
|
||||||
*/
|
*/
|
||||||
public B withUrlPath(String... urlPathItems) {
|
public B withUrlPath(@Nonnull String urlPath, @Nonnull String... urlPathItems) {
|
||||||
// full url may be set and reset as needed
|
// full url may be set and reset as needed
|
||||||
if (urlPathItems.length == 1 && !urlPathItems[0].startsWith("/")) {
|
if (urlPathItems.length == 0 && !urlPath.startsWith("/")) {
|
||||||
return setRawUrlPath(urlPathItems[0]);
|
return setRawUrlPath(urlPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once full url is set, do not allow path setting
|
// Once full url is set, do not allow path setting
|
||||||
@@ -614,15 +671,14 @@ class GitHubRequest {
|
|||||||
throw new GHException("Cannot append to url path after setting a full url");
|
throw new GHException("Cannot append to url path after setting a full url");
|
||||||
}
|
}
|
||||||
|
|
||||||
String tailUrlPath = String.join("/", urlPathItems);
|
String tailUrlPath = urlPath;
|
||||||
|
if (urlPathItems.length != 0) {
|
||||||
if (this.urlPath.endsWith("/")) {
|
tailUrlPath += "/" + String.join("/", urlPathItems);
|
||||||
tailUrlPath = StringUtils.stripStart(tailUrlPath, "/");
|
|
||||||
} else {
|
|
||||||
tailUrlPath = StringUtils.prependIfMissing(tailUrlPath, "/");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.urlPath += urlPathEncode(tailUrlPath);
|
tailUrlPath = StringUtils.prependIfMissing(tailUrlPath, "/");
|
||||||
|
|
||||||
|
this.urlPath = urlPathEncode(tailUrlPath);
|
||||||
return (B) this;
|
return (B) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,7 +714,7 @@ class GitHubRequest {
|
|||||||
*/
|
*/
|
||||||
private static String urlPathEncode(String value) {
|
private static String urlPathEncode(String value) {
|
||||||
try {
|
try {
|
||||||
return new URI(null, null, value, null, null).toString();
|
return new URI(null, null, value, null, null).toASCIIString();
|
||||||
} catch (URISyntaxException ex) {
|
} catch (URISyntaxException ex) {
|
||||||
throw new AssertionError(ex);
|
throw new AssertionError(ex);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package org.kohsuke.github;
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonParseException;
|
||||||
import com.fasterxml.jackson.databind.InjectableValues;
|
import com.fasterxml.jackson.databind.InjectableValues;
|
||||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
@@ -16,6 +18,8 @@ import java.util.Comparator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.CheckForNull;
|
import javax.annotation.CheckForNull;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
@@ -31,6 +35,8 @@ import javax.annotation.Nonnull;
|
|||||||
*/
|
*/
|
||||||
class GitHubResponse<T> {
|
class GitHubResponse<T> {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(GitHubResponse.class.getName());
|
||||||
|
|
||||||
private final int statusCode;
|
private final int statusCode;
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@@ -72,9 +78,14 @@ class GitHubResponse<T> {
|
|||||||
@CheckForNull
|
@CheckForNull
|
||||||
static <T> T parseBody(ResponseInfo responseInfo, Class<T> type) throws IOException {
|
static <T> T parseBody(ResponseInfo responseInfo, Class<T> type) throws IOException {
|
||||||
|
|
||||||
if (responseInfo.statusCode() == HttpURLConnection.HTTP_NO_CONTENT && type != null && type.isArray()) {
|
if (responseInfo.statusCode() == HttpURLConnection.HTTP_NO_CONTENT) {
|
||||||
// no content
|
if (type != null && type.isArray()) {
|
||||||
return type.cast(Array.newInstance(type.getComponentType(), 0));
|
// no content for array should be empty array
|
||||||
|
return type.cast(Array.newInstance(type.getComponentType(), 0));
|
||||||
|
} else {
|
||||||
|
// no content for object should be null
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String data = responseInfo.getBodyAsString();
|
String data = responseInfo.getBodyAsString();
|
||||||
@@ -83,9 +94,10 @@ class GitHubResponse<T> {
|
|||||||
inject.addValue(ResponseInfo.class, responseInfo);
|
inject.addValue(ResponseInfo.class, responseInfo);
|
||||||
|
|
||||||
return GitHubClient.getMappingObjectReader(responseInfo).forType(type).readValue(data);
|
return GitHubClient.getMappingObjectReader(responseInfo).forType(type).readValue(data);
|
||||||
} catch (JsonMappingException e) {
|
} catch (JsonMappingException | JsonParseException e) {
|
||||||
String message = "Failed to deserialize " + data;
|
String message = "Failed to deserialize: " + data;
|
||||||
throw new IOException(message, e);
|
LOGGER.log(Level.FINE, message);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,9 +120,10 @@ class GitHubResponse<T> {
|
|||||||
String data = responseInfo.getBodyAsString();
|
String data = responseInfo.getBodyAsString();
|
||||||
try {
|
try {
|
||||||
return GitHubClient.getMappingObjectReader(responseInfo).withValueToUpdate(instance).readValue(data);
|
return GitHubClient.getMappingObjectReader(responseInfo).withValueToUpdate(instance).readValue(data);
|
||||||
} catch (JsonMappingException e) {
|
} catch (JsonMappingException | JsonParseException e) {
|
||||||
String message = "Failed to deserialize " + data;
|
String message = "Failed to deserialize: " + data;
|
||||||
throw new IOException(message, e);
|
LOGGER.log(Level.FINE, message);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +218,7 @@ class GitHubResponse<T> {
|
|||||||
* Initial response information supplied to a {@link BodyHandler} when a response is initially received and before
|
* Initial response information supplied to a {@link BodyHandler} when a response is initially received and before
|
||||||
* the body is processed.
|
* the body is processed.
|
||||||
*/
|
*/
|
||||||
static abstract class ResponseInfo {
|
static abstract class ResponseInfo implements Closeable {
|
||||||
|
|
||||||
private static final Comparator<String> nullableCaseInsensitiveComparator = Comparator
|
private static final Comparator<String> nullableCaseInsensitiveComparator = Comparator
|
||||||
.nullsFirst(String.CASE_INSENSITIVE_ORDER);
|
.nullsFirst(String.CASE_INSENSITIVE_ORDER);
|
||||||
@@ -307,14 +320,11 @@ class GitHubResponse<T> {
|
|||||||
* @throws IOException
|
* @throws IOException
|
||||||
* if an I/O Exception occurs.
|
* if an I/O Exception occurs.
|
||||||
*/
|
*/
|
||||||
|
@Nonnull
|
||||||
String getBodyAsString() throws IOException {
|
String getBodyAsString() throws IOException {
|
||||||
InputStreamReader r = null;
|
InputStreamReader r = null;
|
||||||
try {
|
r = new InputStreamReader(this.bodyStream(), StandardCharsets.UTF_8);
|
||||||
r = new InputStreamReader(this.bodyStream(), StandardCharsets.UTF_8);
|
return IOUtils.toString(r);
|
||||||
return IOUtils.toString(r);
|
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(r);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.lang.reflect.Array;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|||||||
@@ -15,6 +15,14 @@ class Previews {
|
|||||||
*/
|
*/
|
||||||
static final String ANTIOPE = "application/vnd.github.antiope-preview+json";
|
static final String ANTIOPE = "application/vnd.github.antiope-preview+json";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create repository from template repository
|
||||||
|
*
|
||||||
|
* @see <a href="https://developer.github.com/v3/previews/#create-and-use-repository-templates">GitHub API
|
||||||
|
* Previews</a>
|
||||||
|
*/
|
||||||
|
static final String BAPTISE = "application/vnd.github.baptiste-preview+json";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Commit Search
|
* Commit Search
|
||||||
*
|
*
|
||||||
@@ -58,6 +66,14 @@ class Previews {
|
|||||||
*/
|
*/
|
||||||
static final String MERCY = "application/vnd.github.mercy-preview+json";
|
static final String MERCY = "application/vnd.github.mercy-preview+json";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* New visibility parameter for the Repositories API
|
||||||
|
*
|
||||||
|
* @see <a href="https://developer.github.com/v3/previews/#new-visibility-parameter-for-the-repositories-api">GitHub
|
||||||
|
* API Previews</a>
|
||||||
|
*/
|
||||||
|
static final String NEBULA = "application/vnd.github.nebula-preview+json";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draft pull requests
|
* Draft pull requests
|
||||||
*
|
*
|
||||||
@@ -78,4 +94,5 @@ class Previews {
|
|||||||
* @see <a href="https://developer.github.com/v3/previews/#require-signed-commits">GitHub API Previews</a>
|
* @see <a href="https://developer.github.com/v3/previews/#require-signed-commits">GitHub API Previews</a>
|
||||||
*/
|
*/
|
||||||
static final String ZZZAX = "application/vnd.github.zzzax-preview+json";
|
static final String ZZZAX = "application/vnd.github.zzzax-preview+json";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,20 +5,17 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A GitHub API Rate Limit Checker called before each request
|
* A GitHub API Rate Limit Checker called before each request
|
||||||
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* GitHub allots a certain number of requests to each user or application per period of time (usually per hour). The
|
* GitHub allots a certain number of requests to each user or application per period of time. The number of requests
|
||||||
* number of requests remaining is returned in the response header and can also be requested using
|
* remaining and the time when the number will be reset is returned in the response header and can also be requested
|
||||||
* {@link GitHub#getRateLimit()}. This requests per interval is referred to as the "rate limit".
|
* using {@link GitHub#getRateLimit()}. The "requests per interval" is referred to as the "rate limit".
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* GitHub prefers that clients stop before exceeding their rate limit rather than stopping after they exceed it. The
|
* GitHub prefers that clients stop before exceeding their rate limit rather than stopping after they exceed it. The
|
||||||
* {@link RateLimitChecker} is called before each request to check the rate limit and wait if the checker criteria are
|
* {@link RateLimitChecker} is called before each request to check the rate limit and wait if the checker criteria are
|
||||||
* met.
|
* met.
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
|
||||||
* Checking your rate limit using {@link GitHub#getRateLimit()} does not effect your rate limit, but each {@link GitHub}
|
|
||||||
* instance will attempt to cache and reuse the last see rate limit rather than making a new request.
|
|
||||||
* </p>
|
|
||||||
*/
|
*/
|
||||||
public abstract class RateLimitChecker {
|
public abstract class RateLimitChecker {
|
||||||
|
|
||||||
@@ -33,28 +30,21 @@ public abstract class RateLimitChecker {
|
|||||||
* free to choose whatever strategy they prefer for what is considered to exceed the budget and how long to sleep.
|
* free to choose whatever strategy they prefer for what is considered to exceed the budget and how long to sleep.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* The caller of this method figures out which {@link GHRateLimit.Record} applies for the current request add
|
* The caller of this method figures out which {@link GHRateLimit.Record} applies for the current request and
|
||||||
* provides it to this method.
|
* provides it to this method.
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* It is important to remember that rate limit reset times are only accurate to the second. Trying to sleep to
|
* As long as this method returns {@code true} it is guaranteed that {@link GitHubRateLimitChecker} will retrieve
|
||||||
* exactly the reset time would be likely to produce worse behavior rather than better. For this reason
|
* updated rate limit information and call this method again with {@code count} incremented by one. When this
|
||||||
* {@link GitHubRateLimitChecker} may choose to add more sleep times when a checker indicates the rate limit was
|
* checker returns {@code false}, the calling {@link GitHubRateLimitChecker} will let the request continue.
|
||||||
* exceeded.
|
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* As long as this method returns {@code true} it is guaranteed that {@link GitHubRateLimitChecker} will get updated
|
* Rate limit reset times are only accurate to the second. Trying to sleep to exactly the reset time could result in
|
||||||
* rate limit information and call this method again with {@code count} incremented by one. After this method
|
* requests being sent before the new rate limit was available. For this reason, if this method returned
|
||||||
* returns {@code true} at least once, the calling {@link GitHubRateLimitChecker} may choose to wait some additional
|
* {@code true} at least once for a particular request, {@link GitHubRateLimitChecker} may choose to sleep for some
|
||||||
* period of time between calls to this checker.
|
* small additional between calls and before letting the request continue.
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
*
|
||||||
* After this checker returns {@code false}, the calling {@link GitHubRateLimitChecker} will let the request
|
|
||||||
* continue. If this method returned {@code true} at least once for a particular request, the calling
|
|
||||||
* {@link GitHubRateLimitChecker} may choose to wait some additional period of time before letting the request be
|
|
||||||
* sent.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param rateLimitRecord
|
* @param rateLimitRecord
|
||||||
* the current {@link GHRateLimit.Record} to check against.
|
* the current {@link GHRateLimit.Record} to check against.
|
||||||
* @param count
|
* @param count
|
||||||
|
|||||||
36
src/main/java/org/kohsuke/github/RateLimitTarget.java
Normal file
36
src/main/java/org/kohsuke/github/RateLimitTarget.java
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the rate limit record of an operation.
|
||||||
|
*
|
||||||
|
* @see GitHubBuilder#withRateLimitChecker(RateLimitChecker, RateLimitTarget)
|
||||||
|
*/
|
||||||
|
public enum RateLimitTarget {
|
||||||
|
/**
|
||||||
|
* Selects or updates the {@link GHRateLimit#getCore()} record.
|
||||||
|
*/
|
||||||
|
CORE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects or updates the {@link GHRateLimit#getSearch()} record.
|
||||||
|
*/
|
||||||
|
SEARCH,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects or updates the {@link GHRateLimit#getGraphQL()} record.
|
||||||
|
*/
|
||||||
|
GRAPHQL,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects or updates the {@link GHRateLimit#getIntegrationManifest()} record.
|
||||||
|
*/
|
||||||
|
INTEGRATION_MANIFEST,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects no rate limit.
|
||||||
|
*
|
||||||
|
* This request uses no rate limit. If the response header includes rate limit information, it will apply to
|
||||||
|
* {@link #CORE}.
|
||||||
|
*/
|
||||||
|
NONE
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.kohsuke.github;
|
package org.kohsuke.github;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The interface Refreshable.
|
* The interface Refreshable.
|
||||||
|
|||||||
@@ -23,6 +23,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.kohsuke.github;
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
@@ -108,7 +111,10 @@ class Requester extends GitHubRequest.Builder<Requester> {
|
|||||||
* the io exception
|
* the io exception
|
||||||
*/
|
*/
|
||||||
public InputStream fetchStream() throws IOException {
|
public InputStream fetchStream() throws IOException {
|
||||||
return client.sendRequest(this, (responseInfo) -> responseInfo.bodyStream()).body();
|
return client
|
||||||
|
.sendRequest(this,
|
||||||
|
(responseInfo) -> new ByteArrayInputStream(IOUtils.toByteArray(responseInfo.bodyStream())))
|
||||||
|
.body();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -280,14 +280,15 @@ public final class ObsoleteUrlFactory implements URLStreamHandlerFactory, Clonea
|
|||||||
if (c > '\u001f' && c < '\u007f')
|
if (c > '\u001f' && c < '\u007f')
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Buffer buffer = new Buffer();
|
try (Buffer buffer = new Buffer()) {
|
||||||
buffer.writeUtf8(s, 0, i);
|
buffer.writeUtf8(s, 0, i);
|
||||||
buffer.writeUtf8CodePoint('?');
|
buffer.writeUtf8CodePoint('?');
|
||||||
for (int j = i + Character.charCount(c); j < length; j += Character.charCount(c)) {
|
for (int j = i + Character.charCount(c); j < length; j += Character.charCount(c)) {
|
||||||
c = s.codePointAt(j);
|
c = s.codePointAt(j);
|
||||||
buffer.writeUtf8CodePoint(c > '\u001f' && c < '\u007f' ? c : '?');
|
buffer.writeUtf8CodePoint(c > '\u001f' && c < '\u007f' ? c : '?');
|
||||||
|
}
|
||||||
|
return buffer.readUtf8();
|
||||||
}
|
}
|
||||||
return buffer.readUtf8();
|
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|||||||
31
src/site/apt/createorglevelresources.apt
Normal file
31
src/site/apt/createorglevelresources.apt
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
Creating resources at the organization level
|
||||||
|
|
||||||
|
In order to create resources in GitHub for a given organization, you must first create an object of type <<<GHOrganization>>>.
|
||||||
|
As an example:
|
||||||
|
|
||||||
|
+-----+
|
||||||
|
GHOrganization organizationClient(GitHub gitHub, String organizationName) throws IOException {
|
||||||
|
return gitHub.getOrganization(organizationName);
|
||||||
|
}
|
||||||
|
+-----+
|
||||||
|
|
||||||
|
Now you can easily work with the GHOrganization, and all methods that will create resources will create them
|
||||||
|
in the given organization.
|
||||||
|
|
||||||
|
One of the most common use cases is to create a repository in a given organization:
|
||||||
|
|
||||||
|
+-----+
|
||||||
|
void createRepository(GHOrganization organization) {
|
||||||
|
organization.createRepository("repository-name")
|
||||||
|
.private_(true)
|
||||||
|
.wiki(false)
|
||||||
|
.projects(false)
|
||||||
|
.description("Description")
|
||||||
|
.allowMergeCommit(true)
|
||||||
|
.allowSquashMerge(false)
|
||||||
|
.allowRebaseMerge(false)
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
+-----+
|
||||||
|
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<menu name="Git Hub API for Java">
|
<menu name="Git Hub API for Java">
|
||||||
<item name="Introduction" href="/index.html"/>
|
<item name="Introduction" href="/index.html"/>
|
||||||
<item name="Download" href="https://mvnrepository.com/artifact/${project.groupId}/${project.artifactId}"/>
|
<item name="Download" href="https://mvnrepository.com/artifact/${project.groupId}/${project.artifactId}"/>
|
||||||
<item name="Source code" href="https://github.com/github-api/${project.artifactId}"/>
|
<item name="Source code" href="https://github.com/hub4j/${project.artifactId}"/>
|
||||||
<item name="Mailing List" href="https://groups.google.com/forum/#!forum/github-api"/>
|
<item name="Mailing List" href="https://groups.google.com/forum/#!forum/github-api"/>
|
||||||
</menu>
|
</menu>
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
<item name="JWT Authentication" href="/githubappjwtauth.html"/>
|
<item name="JWT Authentication" href="/githubappjwtauth.html"/>
|
||||||
<item name="App Installation Token " href="/githubappappinsttokenauth.html"/>
|
<item name="App Installation Token " href="/githubappappinsttokenauth.html"/>
|
||||||
</item>
|
</item>
|
||||||
|
<item name="Working with organizations" href="/createorglevelresources.html"/>
|
||||||
</menu>
|
</menu>
|
||||||
|
|
||||||
<menu name="References">
|
<menu name="References">
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public abstract class AbstractGitHubWireMockTest extends Assert {
|
|||||||
|
|
||||||
private final GitHubBuilder githubBuilder = createGitHubBuilder();
|
private final GitHubBuilder githubBuilder = createGitHubBuilder();
|
||||||
|
|
||||||
final static String GITHUB_API_TEST_ORG = "github-api-test-org";
|
final static String GITHUB_API_TEST_ORG = "hub4j-test-org";
|
||||||
|
|
||||||
final static String STUBBED_USER_LOGIN = "placeholder-user";
|
final static String STUBBED_USER_LOGIN = "placeholder-user";
|
||||||
final static String STUBBED_USER_PASSWORD = "placeholder-password";
|
final static String STUBBED_USER_PASSWORD = "placeholder-password";
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import java.util.Map.Entry;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.*;
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
|
import static org.hamcrest.CoreMatchers.sameInstance;
|
||||||
import static org.hamcrest.Matchers.hasProperty;
|
import static org.hamcrest.Matchers.hasProperty;
|
||||||
import static org.hamcrest.Matchers.oneOf;
|
import static org.hamcrest.Matchers.oneOf;
|
||||||
|
|
||||||
@@ -108,7 +109,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
|||||||
gitHub = getGitHubBuilder().withOAuthToken("bogus", "user")
|
gitHub = getGitHubBuilder().withOAuthToken("bogus", "user")
|
||||||
.withEndpoint(mockGitHub.apiServer().baseUrl())
|
.withEndpoint(mockGitHub.apiServer().baseUrl())
|
||||||
.build();
|
.build();
|
||||||
assertThat(gitHub.lastRateLimit(), nullValue());
|
assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT));
|
||||||
assertFalse(gitHub.isCredentialValid());
|
assertFalse(gitHub.isCredentialValid());
|
||||||
// For invalid credentials, we get a 401 but it includes anonymous rate limit headers
|
// 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(), not(instanceOf(GHRateLimit.UnknownLimitRecord.class)));
|
||||||
@@ -118,18 +119,22 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testCredentialValidEnterprise() throws IOException {
|
public void testCredentialValidEnterprise() throws IOException {
|
||||||
// Simulated GHE: getRateLimit returns 404
|
// Simulated GHE: getRateLimit returns 404
|
||||||
assertThat(gitHub.lastRateLimit(), nullValue());
|
assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT));
|
||||||
|
assertThat(gitHub.lastRateLimit().getCore().isExpired(), is(true));
|
||||||
assertTrue(gitHub.isCredentialValid());
|
assertTrue(gitHub.isCredentialValid());
|
||||||
// lastRateLimit stays null when 404 is encountered
|
|
||||||
assertThat(gitHub.lastRateLimit(), nullValue());
|
// 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")
|
gitHub = getGitHubBuilder().withOAuthToken("bogus", "user")
|
||||||
.withEndpoint(mockGitHub.apiServer().baseUrl())
|
.withEndpoint(mockGitHub.apiServer().baseUrl())
|
||||||
.build();
|
.build();
|
||||||
assertThat(gitHub.lastRateLimit(), nullValue());
|
assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT));
|
||||||
assertFalse(gitHub.isCredentialValid());
|
assertFalse(gitHub.isCredentialValid());
|
||||||
// Simulated GHE: For invalid credentials, we get a 401 that does not include ratelimit info
|
// Simulated GHE: For invalid credentials, we get a 401 that does not include ratelimit info
|
||||||
assertThat(gitHub.lastRateLimit(), nullValue());
|
assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -198,7 +203,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetIssues() throws Exception {
|
public void testGetIssues() throws Exception {
|
||||||
List<GHIssue> closedIssues = gitHub.getOrganization("github-api")
|
List<GHIssue> closedIssues = gitHub.getOrganization("hub4j")
|
||||||
.getRepository("github-api")
|
.getRepository("github-api")
|
||||||
.getIssues(GHIssueState.CLOSED);
|
.getIssues(GHIssueState.CLOSED);
|
||||||
// prior to using PagedIterable GHRepository.getIssues(GHIssueState) would only retrieve 30 issues
|
// prior to using PagedIterable GHRepository.getIssues(GHIssueState) would only retrieve 30 issues
|
||||||
@@ -211,7 +216,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testListIssues() throws IOException {
|
public void testListIssues() throws IOException {
|
||||||
Iterable<GHIssue> closedIssues = gitHub.getOrganization("github-api")
|
Iterable<GHIssue> closedIssues = gitHub.getOrganization("hub4j")
|
||||||
.getRepository("github-api")
|
.getRepository("github-api")
|
||||||
.listIssues(GHIssueState.CLOSED);
|
.listIssues(GHIssueState.CLOSED);
|
||||||
|
|
||||||
@@ -291,7 +296,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
|||||||
GHOrganization organization = gitHub.getOrganization(GITHUB_API_TEST_ORG);
|
GHOrganization organization = gitHub.getOrganization(GITHUB_API_TEST_ORG);
|
||||||
GHTeam teamByName = organization.getTeams().get("Core Developers");
|
GHTeam teamByName = organization.getTeams().get("Core Developers");
|
||||||
|
|
||||||
GHTeam teamById = gitHub.getTeam(teamByName.getId());
|
GHTeam teamById = gitHub.getTeam((int) teamByName.getId());
|
||||||
assertNotNull(teamById);
|
assertNotNull(teamById);
|
||||||
|
|
||||||
assertEquals(teamByName.getId(), teamById.getId());
|
assertEquals(teamByName.getId(), teamById.getId());
|
||||||
@@ -308,6 +313,13 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
assertEquals(teamByName.getId(), teamById.getId());
|
assertEquals(teamByName.getId(), teamById.getId());
|
||||||
assertEquals(teamByName.getDescription(), teamById.getDescription());
|
assertEquals(teamByName.getDescription(), teamById.getDescription());
|
||||||
|
|
||||||
|
GHTeam teamById2 = organization.getTeam((int) teamByName.getId());
|
||||||
|
assertNotNull(teamById2);
|
||||||
|
|
||||||
|
assertEquals(teamByName.getId(), teamById2.getId());
|
||||||
|
assertEquals(teamByName.getDescription(), teamById2.getDescription());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore("Needs mocking check")
|
@Ignore("Needs mocking check")
|
||||||
@@ -322,7 +334,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
|||||||
@Ignore("Needs mocking check")
|
@Ignore("Needs mocking check")
|
||||||
@Test
|
@Test
|
||||||
public void testFetchPullRequestAsList() throws Exception {
|
public void testFetchPullRequestAsList() throws Exception {
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
assertEquals("master", r.getMasterBranch());
|
assertEquals("master", r.getMasterBranch());
|
||||||
PagedIterable<GHPullRequest> i = r.listPullRequests(GHIssueState.CLOSED);
|
PagedIterable<GHPullRequest> i = r.listPullRequests(GHIssueState.CLOSED);
|
||||||
List<GHPullRequest> prs = i.toList();
|
List<GHPullRequest> prs = i.toList();
|
||||||
@@ -628,7 +640,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCommitStatus() throws Exception {
|
public void testCommitStatus() throws Exception {
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
|
|
||||||
GHCommitStatus state;
|
GHCommitStatus state;
|
||||||
|
|
||||||
@@ -644,7 +656,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCommitShortInfo() throws Exception {
|
public void testCommitShortInfo() throws Exception {
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f23");
|
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f23");
|
||||||
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Kohsuke Kawaguchi");
|
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Kohsuke Kawaguchi");
|
||||||
assertEquals(commit.getCommitShortInfo().getMessage(), "doc");
|
assertEquals(commit.getCommitShortInfo().getMessage(), "doc");
|
||||||
@@ -807,7 +819,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test // issue #99
|
@Test // issue #99
|
||||||
public void testReadme() throws IOException {
|
public void testReadme() throws IOException {
|
||||||
GHContent readme = gitHub.getRepository("github-api-test-org/test-readme").getReadme();
|
GHContent readme = gitHub.getRepository("hub4j-test-org/test-readme").getReadme();
|
||||||
assertEquals(readme.getName(), "README.md");
|
assertEquals(readme.getName(), "README.md");
|
||||||
assertEquals(readme.getContent(), "This is a markdown readme.\n");
|
assertEquals(readme.getContent(), "This is a markdown readme.\n");
|
||||||
}
|
}
|
||||||
@@ -815,7 +827,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
|||||||
@Ignore("Needs mocking check")
|
@Ignore("Needs mocking check")
|
||||||
@Test
|
@Test
|
||||||
public void testTrees() throws IOException {
|
public void testTrees() throws IOException {
|
||||||
GHTree masterTree = gitHub.getRepository("github-api/github-api").getTree("master");
|
GHTree masterTree = gitHub.getRepository("hub4j/github-api").getTree("master");
|
||||||
boolean foundReadme = false;
|
boolean foundReadme = false;
|
||||||
for (GHTreeEntry e : masterTree.getTree()) {
|
for (GHTreeEntry e : masterTree.getTree()) {
|
||||||
if ("readme".equalsIgnoreCase(e.getPath().replaceAll("\\.md", ""))) {
|
if ("readme".equalsIgnoreCase(e.getPath().replaceAll("\\.md", ""))) {
|
||||||
@@ -828,7 +840,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTreesRecursive() throws IOException {
|
public void testTreesRecursive() throws IOException {
|
||||||
GHTree masterTree = gitHub.getRepository("github-api/github-api").getTreeRecursive("master", 1);
|
GHTree masterTree = gitHub.getRepository("hub4j/github-api").getTreeRecursive("master", 1);
|
||||||
boolean foundThisFile = false;
|
boolean foundThisFile = false;
|
||||||
for (GHTreeEntry e : masterTree.getTree()) {
|
for (GHTreeEntry e : masterTree.getTree()) {
|
||||||
if (e.getPath().endsWith(AppTest.class.getSimpleName() + ".java")) {
|
if (e.getPath().endsWith(AppTest.class.getSimpleName() + ".java")) {
|
||||||
@@ -844,7 +856,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
|||||||
cleanupLabel("test");
|
cleanupLabel("test");
|
||||||
cleanupLabel("test2");
|
cleanupLabel("test2");
|
||||||
|
|
||||||
GHRepository r = gitHub.getRepository("github-api-test-org/test-labels");
|
GHRepository r = gitHub.getRepository("hub4j-test-org/test-labels");
|
||||||
List<GHLabel> lst = r.listLabels().toList();
|
List<GHLabel> lst = r.listLabels().toList();
|
||||||
for (GHLabel l : lst) {
|
for (GHLabel l : lst) {
|
||||||
assertThat(l.getUrl(), containsString(l.getName().replace(" ", "%20")));
|
assertThat(l.getUrl(), containsString(l.getName().replace(" ", "%20")));
|
||||||
@@ -943,7 +955,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
|||||||
void cleanupLabel(String name) {
|
void cleanupLabel(String name) {
|
||||||
if (mockGitHub.isUseProxy()) {
|
if (mockGitHub.isUseProxy()) {
|
||||||
try {
|
try {
|
||||||
GHLabel t = getGitHubBeforeAfter().getRepository("github-api-test-org/test-labels").getLabel(name);
|
GHLabel t = getGitHubBeforeAfter().getRepository("hub4j-test-org/test-labels").getLabel(name);
|
||||||
t.delete();
|
t.delete();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
||||||
@@ -1012,7 +1024,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void reactions() throws Exception {
|
public void reactions() throws Exception {
|
||||||
GHIssue i = gitHub.getRepository("github-api/github-api").getIssue(311);
|
GHIssue i = gitHub.getRepository("hub4j/github-api").getIssue(311);
|
||||||
|
|
||||||
List<GHReaction> l;
|
List<GHReaction> l;
|
||||||
// retrieval
|
// retrieval
|
||||||
@@ -1084,7 +1096,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
|||||||
public void blob() throws Exception {
|
public void blob() throws Exception {
|
||||||
Assume.assumeFalse(SystemUtils.IS_OS_WINDOWS);
|
Assume.assumeFalse(SystemUtils.IS_OS_WINDOWS);
|
||||||
|
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
String sha1 = "a12243f2fc5b8c2ba47dd677d0b0c7583539584d";
|
String sha1 = "a12243f2fc5b8c2ba47dd677d0b0c7583539584d";
|
||||||
|
|
||||||
assertBlobContent(r.readBlob(sha1));
|
assertBlobContent(r.readBlob(sha1));
|
||||||
|
|||||||
@@ -5,12 +5,15 @@ import org.junit.Test;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import javax.annotation.Nonnull;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Kohsuke Kawaguchi
|
* @author Kohsuke Kawaguchi
|
||||||
@@ -18,26 +21,50 @@ import static org.hamcrest.Matchers.equalTo;
|
|||||||
public class BridgeMethodTest extends Assert {
|
public class BridgeMethodTest extends Assert {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void lastStatus() throws IOException {
|
public void testBridgeMethods() throws IOException {
|
||||||
GHObject obj = new GHIssue();
|
|
||||||
|
|
||||||
List<Method> createdAtMethods = new ArrayList<>();
|
// Some would say this is redundant, given that bridge methods are so thin anyway
|
||||||
for (Method method : obj.getClass().getMethods()) {
|
// In the interest of maintaining binary compatibility, we'll do this anyway for a sampling of methods
|
||||||
if (method.getName().equalsIgnoreCase("getCreatedAt")) {
|
|
||||||
if (method.getReturnType() == Date.class) {
|
// Something odd here
|
||||||
createdAtMethods.add(0, method);
|
// verifyBridgeMethods(new GHCommit(), "getAuthor", GHCommit.GHAuthor.class, GitUser.class);
|
||||||
} else {
|
// verifyBridgeMethods(new GHCommit(), "getCommitter", GHCommit.GHAuthor.class, GitUser.class);
|
||||||
createdAtMethods.add(method);
|
|
||||||
}
|
verifyBridgeMethods(GHIssue.class, "getCreatedAt", Date.class, String.class);
|
||||||
|
verifyBridgeMethods(GHIssue.class, "getId", int.class, long.class, String.class);
|
||||||
|
verifyBridgeMethods(GHIssue.class, "getUrl", String.class, URL.class);
|
||||||
|
|
||||||
|
verifyBridgeMethods(GHOrganization.class, "getHtmlUrl", String.class, URL.class);
|
||||||
|
verifyBridgeMethods(GHOrganization.class, "getId", int.class, long.class, String.class);
|
||||||
|
verifyBridgeMethods(GHOrganization.class, "getUrl", String.class, URL.class);
|
||||||
|
|
||||||
|
verifyBridgeMethods(GHRepository.class, "getCollaborators", GHPersonSet.class, Set.class);
|
||||||
|
verifyBridgeMethods(GHRepository.class, "getHtmlUrl", String.class, URL.class);
|
||||||
|
verifyBridgeMethods(GHRepository.class, "getId", int.class, long.class, String.class);
|
||||||
|
verifyBridgeMethods(GHRepository.class, "getUrl", String.class, URL.class);
|
||||||
|
|
||||||
|
verifyBridgeMethods(GHUser.class, "getFollows", GHPersonSet.class, Set.class);
|
||||||
|
verifyBridgeMethods(GHUser.class, "getFollowers", GHPersonSet.class, Set.class);
|
||||||
|
verifyBridgeMethods(GHUser.class, "getOrganizations", GHPersonSet.class, Set.class);
|
||||||
|
verifyBridgeMethods(GHUser.class, "getId", int.class, long.class, String.class);
|
||||||
|
|
||||||
|
verifyBridgeMethods(GHTeam.class, "getId", int.class, long.class, String.class);
|
||||||
|
|
||||||
|
// verifyBridgeMethods(GitHub.class, "getMyself", GHMyself.class, GHUser.class);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void verifyBridgeMethods(@Nonnull Class<?> targetClass, @Nonnull String methodName, Class<?>... returnTypes) {
|
||||||
|
List<Class<?>> foundMethods = new ArrayList<>();
|
||||||
|
Method[] methods = targetClass.getMethods();
|
||||||
|
for (Method method : methods) {
|
||||||
|
if (method.getName().equalsIgnoreCase(methodName)) {
|
||||||
|
// Bridge methods are only
|
||||||
|
assertThat(method.getParameterCount(), equalTo(0));
|
||||||
|
foundMethods.add(method.getReturnType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assertThat(createdAtMethods.size(), equalTo(2));
|
assertThat(foundMethods, containsInAnyOrder(returnTypes));
|
||||||
|
|
||||||
assertThat(createdAtMethods.get(0).getParameterCount(), equalTo(0));
|
|
||||||
assertThat(createdAtMethods.get(1).getParameterCount(), equalTo(0));
|
|
||||||
|
|
||||||
assertThat(createdAtMethods.get(0).getReturnType(), is(Date.class));
|
|
||||||
assertThat(createdAtMethods.get(1).getReturnType(), is(String.class));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ public class GHAppTest extends AbstractGitHubWireMockTest {
|
|||||||
@Test
|
@Test
|
||||||
public void getGitHubApp() throws IOException {
|
public void getGitHubApp() throws IOException {
|
||||||
GHApp app = gitHub.getApp();
|
GHApp app = gitHub.getApp();
|
||||||
assertThat(app.id, is((long) 11111));
|
assertThat(app.getId(), is((long) 11111));
|
||||||
assertThat(app.getOwner().id, is((long) 111111111));
|
assertThat(app.getOwner().getId(), is((long) 111111111));
|
||||||
assertThat(app.getOwner().login, is("bogus"));
|
assertThat(app.getOwner().login, is("bogus"));
|
||||||
assertThat(app.getName(), is("Bogus-Development"));
|
assertThat(app.getName(), is("Bogus-Development"));
|
||||||
assertThat(app.getDescription(), is(""));
|
assertThat(app.getDescription(), is(""));
|
||||||
@@ -132,8 +132,8 @@ public class GHAppTest extends AbstractGitHubWireMockTest {
|
|||||||
Map<String, GHPermissionType> appPermissions = appInstallation.getPermissions();
|
Map<String, GHPermissionType> appPermissions = appInstallation.getPermissions();
|
||||||
GHUser appAccount = appInstallation.getAccount();
|
GHUser appAccount = appInstallation.getAccount();
|
||||||
|
|
||||||
assertThat(appInstallation.id, is((long) 11111111));
|
assertThat(appInstallation.getId(), is((long) 11111111));
|
||||||
assertThat(appAccount.id, is((long) 111111111));
|
assertThat(appAccount.getId(), is((long) 111111111));
|
||||||
assertThat(appAccount.login, is("bogus"));
|
assertThat(appAccount.login, is("bogus"));
|
||||||
assertThat(appInstallation.getRepositorySelection(), is(GHRepositorySelection.SELECTED));
|
assertThat(appInstallation.getRepositorySelection(), is(GHRepositorySelection.SELECTED));
|
||||||
assertThat(appInstallation.getAccessTokenUrl(), endsWith("/app/installations/11111111/access_tokens"));
|
assertThat(appInstallation.getAccessTokenUrl(), endsWith("/app/installations/11111111/access_tokens"));
|
||||||
|
|||||||
39
src/test/java/org/kohsuke/github/GHBranchProtectionTest.java
Normal file → Executable file
39
src/test/java/org/kohsuke/github/GHBranchProtectionTest.java
Normal file → Executable file
@@ -6,6 +6,8 @@ import org.kohsuke.github.GHBranchProtection.EnforceAdmins;
|
|||||||
import org.kohsuke.github.GHBranchProtection.RequiredReviews;
|
import org.kohsuke.github.GHBranchProtection.RequiredReviews;
|
||||||
import org.kohsuke.github.GHBranchProtection.RequiredStatusChecks;
|
import org.kohsuke.github.GHBranchProtection.RequiredStatusChecks;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
|
|
||||||
public class GHBranchProtectionTest extends AbstractGitHubWireMockTest {
|
public class GHBranchProtectionTest extends AbstractGitHubWireMockTest {
|
||||||
private static final String BRANCH = "master";
|
private static final String BRANCH = "master";
|
||||||
private static final String BRANCH_REF = "heads/" + BRANCH;
|
private static final String BRANCH_REF = "heads/" + BRANCH;
|
||||||
@@ -32,6 +34,14 @@ public class GHBranchProtectionTest extends AbstractGitHubWireMockTest {
|
|||||||
.includeAdmins()
|
.includeAdmins()
|
||||||
.enable();
|
.enable();
|
||||||
|
|
||||||
|
verifyBranchProtection(protection);
|
||||||
|
|
||||||
|
// Get goes through a different code path. Make sure it also gets the correct data.
|
||||||
|
protection = branch.getProtection();
|
||||||
|
verifyBranchProtection(protection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyBranchProtection(GHBranchProtection protection) {
|
||||||
RequiredStatusChecks statusChecks = protection.getRequiredStatusChecks();
|
RequiredStatusChecks statusChecks = protection.getRequiredStatusChecks();
|
||||||
assertNotNull(statusChecks);
|
assertNotNull(statusChecks);
|
||||||
assertTrue(statusChecks.isRequiresBranchUpToDate());
|
assertTrue(statusChecks.isRequiresBranchUpToDate());
|
||||||
@@ -54,11 +64,32 @@ public class GHBranchProtectionTest extends AbstractGitHubWireMockTest {
|
|||||||
assertTrue(repo.getBranch(BRANCH).isProtected());
|
assertTrue(repo.getBranch(BRANCH).isProtected());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisableProtectionOnly() throws Exception {
|
||||||
|
GHBranchProtection protection = branch.enableProtection().enable();
|
||||||
|
assertTrue(repo.getBranch(BRANCH).isProtected());
|
||||||
|
branch.disableProtection();
|
||||||
|
assertFalse(repo.getBranch(BRANCH).isProtected());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEnableRequireReviewsOnly() throws Exception {
|
public void testEnableRequireReviewsOnly() throws Exception {
|
||||||
GHBranchProtection protection = branch.enableProtection().requireReviews().enable();
|
GHBranchProtection protection = branch.enableProtection().requireReviews().enable();
|
||||||
|
|
||||||
|
RequiredReviews requiredReviews = protection.getRequiredReviews();
|
||||||
assertNotNull(protection.getRequiredReviews());
|
assertNotNull(protection.getRequiredReviews());
|
||||||
|
assertFalse(requiredReviews.isDismissStaleReviews());
|
||||||
|
assertFalse(requiredReviews.isRequireCodeOwnerReviews());
|
||||||
|
assertThat(protection.getRequiredReviews().getRequiredReviewers(), equalTo(1));
|
||||||
|
|
||||||
|
// Get goes through a different code path. Make sure it also gets the correct data.
|
||||||
|
protection = branch.getProtection();
|
||||||
|
requiredReviews = protection.getRequiredReviews();
|
||||||
|
|
||||||
|
assertNotNull(protection.getRequiredReviews());
|
||||||
|
assertFalse(requiredReviews.isDismissStaleReviews());
|
||||||
|
assertFalse(requiredReviews.isRequireCodeOwnerReviews());
|
||||||
|
assertThat(protection.getRequiredReviews().getRequiredReviewers(), equalTo(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -73,4 +104,12 @@ public class GHBranchProtectionTest extends AbstractGitHubWireMockTest {
|
|||||||
protection.disableSignedCommits();
|
protection.disableSignedCommits();
|
||||||
assertFalse(protection.getRequiredSignatures());
|
assertFalse(protection.getRequiredSignatures());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetProtection() throws Exception {
|
||||||
|
GHBranchProtection protection = branch.enableProtection().enable();
|
||||||
|
GHBranchProtection protectionTest = repo.getBranch(BRANCH).getProtection();
|
||||||
|
assertTrue(protectionTest instanceof GHBranchProtection);
|
||||||
|
assertTrue(repo.getBranch(BRANCH).isProtected());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
47
src/test/java/org/kohsuke/github/GHBranchTest.java
Normal file
47
src/test/java/org/kohsuke/github/GHBranchTest.java
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
|
public class GHBranchTest extends AbstractGitHubWireMockTest {
|
||||||
|
private static final String BRANCH_1 = "testBranch1";
|
||||||
|
private static final String BRANCH_2 = "testBranch2";
|
||||||
|
|
||||||
|
private GHRepository repository;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMergeBranch() throws Exception {
|
||||||
|
repository = getTempRepository();
|
||||||
|
|
||||||
|
String masterHead = repository.getRef("heads/master").getObject().getSha();
|
||||||
|
createRefAndPostContent(BRANCH_1, masterHead);
|
||||||
|
createRefAndPostContent(BRANCH_2, masterHead);
|
||||||
|
|
||||||
|
GHBranch otherBranch = repository.getBranch(BRANCH_2);
|
||||||
|
String commitMessage = "merging " + BRANCH_2;
|
||||||
|
GHCommit mergeCommit = repository.getBranch(BRANCH_1).merge(otherBranch, commitMessage);
|
||||||
|
assertThat(mergeCommit, notNullValue());
|
||||||
|
assertThat(mergeCommit.getCommitShortInfo().getMessage(), equalTo(commitMessage));
|
||||||
|
|
||||||
|
// Merging commit sha should work
|
||||||
|
commitMessage = "merging from " + mergeCommit.getSHA1();
|
||||||
|
GHBranch master = repository.getBranch("master");
|
||||||
|
mergeCommit = master.merge(mergeCommit.getSHA1(), commitMessage);
|
||||||
|
|
||||||
|
assertThat(mergeCommit, notNullValue());
|
||||||
|
assertThat(mergeCommit.getCommitShortInfo().getMessage(), equalTo(commitMessage));
|
||||||
|
|
||||||
|
mergeCommit = master.merge(mergeCommit.getSHA1(), commitMessage);
|
||||||
|
// Should be null since all changes already merged
|
||||||
|
assertThat(mergeCommit, nullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createRefAndPostContent(String branchName, String sha) throws IOException {
|
||||||
|
String refName = "refs/heads/" + branchName;
|
||||||
|
repository.createRef(refName, sha);
|
||||||
|
repository.createContent().content(branchName).message(branchName).path(branchName).branch(branchName).commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,7 +55,7 @@ public class GHCheckRunBuilderTest extends AbstractGitHubWireMockTest {
|
|||||||
.create();
|
.create();
|
||||||
assertEquals("completed", checkRun.getStatus());
|
assertEquals("completed", checkRun.getStatus());
|
||||||
assertEquals(1, checkRun.getOutput().getAnnotationsCount());
|
assertEquals(1, checkRun.getOutput().getAnnotationsCount());
|
||||||
assertEquals(546384586, checkRun.id);
|
assertEquals(546384586, checkRun.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -74,7 +74,7 @@ public class GHCheckRunBuilderTest extends AbstractGitHubWireMockTest {
|
|||||||
assertEquals("Big Run", checkRun.getOutput().getTitle());
|
assertEquals("Big Run", checkRun.getOutput().getTitle());
|
||||||
assertEquals("Lots of stuff here »", checkRun.getOutput().getSummary());
|
assertEquals("Lots of stuff here »", checkRun.getOutput().getSummary());
|
||||||
assertEquals(101, checkRun.getOutput().getAnnotationsCount());
|
assertEquals(101, checkRun.getOutput().getAnnotationsCount());
|
||||||
assertEquals(546384622, checkRun.id);
|
assertEquals(546384622, checkRun.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -86,7 +86,7 @@ public class GHCheckRunBuilderTest extends AbstractGitHubWireMockTest {
|
|||||||
.create();
|
.create();
|
||||||
assertEquals("completed", checkRun.getStatus());
|
assertEquals("completed", checkRun.getStatus());
|
||||||
assertEquals(0, checkRun.getOutput().getAnnotationsCount());
|
assertEquals(0, checkRun.getOutput().getAnnotationsCount());
|
||||||
assertEquals(546384705, checkRun.id);
|
assertEquals(546384705, checkRun.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -97,7 +97,7 @@ public class GHCheckRunBuilderTest extends AbstractGitHubWireMockTest {
|
|||||||
.create();
|
.create();
|
||||||
assertEquals("in_progress", checkRun.getStatus());
|
assertEquals("in_progress", checkRun.getStatus());
|
||||||
assertNull(checkRun.getConclusion());
|
assertNull(checkRun.getConclusion());
|
||||||
assertEquals(546469053, checkRun.id);
|
assertEquals(546469053, checkRun.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.kohsuke.github;
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -7,9 +8,11 @@ import org.junit.Test;
|
|||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.*;
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
|
import static org.hamcrest.Matchers.hasProperty;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration test for {@link GHContent}.
|
* Integration test for {@link GHContent}.
|
||||||
@@ -26,7 +29,7 @@ public class GHContentIntegrationTest extends AbstractGitHubWireMockTest {
|
|||||||
@After
|
@After
|
||||||
public void cleanup() throws Exception {
|
public void cleanup() throws Exception {
|
||||||
if (mockGitHub.isUseProxy()) {
|
if (mockGitHub.isUseProxy()) {
|
||||||
repo = getGitHubBeforeAfter().getRepository("github-api-test-org/GHContentIntegrationTest");
|
repo = getGitHubBeforeAfter().getRepository("hub4j-test-org/GHContentIntegrationTest");
|
||||||
try {
|
try {
|
||||||
GHContent content = repo.getFileContent(createdFilename);
|
GHContent content = repo.getFileContent(createdFilename);
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
@@ -39,12 +42,12 @@ public class GHContentIntegrationTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
repo = gitHub.getRepository("github-api-test-org/GHContentIntegrationTest");
|
repo = gitHub.getRepository("hub4j-test-org/GHContentIntegrationTest");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetFileContent() throws Exception {
|
public void testGetFileContent() throws Exception {
|
||||||
repo = gitHub.getRepository("github-api-test-org/GHContentIntegrationTest");
|
repo = gitHub.getRepository("hub4j-test-org/GHContentIntegrationTest");
|
||||||
GHContent content = repo.getFileContent("ghcontent-ro/a-file-with-content");
|
GHContent content = repo.getFileContent("ghcontent-ro/a-file-with-content");
|
||||||
|
|
||||||
assertTrue(content.isFile());
|
assertTrue(content.isFile());
|
||||||
@@ -123,7 +126,7 @@ public class GHContentIntegrationTest extends AbstractGitHubWireMockTest {
|
|||||||
} catch (GHFileNotFoundException e) {
|
} catch (GHFileNotFoundException e) {
|
||||||
assertThat(e.getMessage(),
|
assertThat(e.getMessage(),
|
||||||
endsWith(
|
endsWith(
|
||||||
"/repos/github-api-test-org/GHContentIntegrationTest/contents/test+directory%20%2350/test%20file-to+create-%231.txt {\"message\":\"Not Found\",\"documentation_url\":\"https://developer.github.com/v3/repos/contents/#get-contents\"}"));
|
"/repos/hub4j-test-org/GHContentIntegrationTest/contents/test+directory%20%2350/test%20file-to+create-%231.txt {\"message\":\"Not Found\",\"documentation_url\":\"https://developer.github.com/v3/repos/contents/#get-contents\"}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,4 +161,34 @@ public class GHContentIntegrationTest extends AbstractGitHubWireMockTest {
|
|||||||
+ "123456789012345678901234567890123456789012345678901234567890");
|
+ "123456789012345678901234567890123456789012345678901234567890");
|
||||||
ghContentBuilder.commit();
|
ghContentBuilder.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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"));
|
||||||
|
|
||||||
|
final GHContent fileContent2 = repo.getFileContent(fileContent.getPath());
|
||||||
|
assertThat(IOUtils.readLines(fileContent2.read(), StandardCharsets.UTF_8), hasItems("test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetFileContentWithSymlink() throws Exception {
|
||||||
|
final GHRepository repo = gitHub.getRepository("hub4j-test-org/GHContentIntegrationTest");
|
||||||
|
|
||||||
|
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"));
|
||||||
|
|
||||||
|
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(""));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package org.kohsuke.github;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Martin van Zijl
|
* @author Martin van Zijl
|
||||||
@@ -10,17 +12,42 @@ import java.io.IOException;
|
|||||||
public class GHDeploymentTest extends AbstractGitHubWireMockTest {
|
public class GHDeploymentTest extends AbstractGitHubWireMockTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetDeploymentById() throws IOException {
|
public void testGetDeploymentByIdStringPayload() throws IOException {
|
||||||
GHRepository repo = getRepository();
|
final GHRepository repo = getRepository();
|
||||||
GHDeployment deployment = repo.getDeployment(178653229);
|
final GHDeployment deployment = repo.getDeployment(178653229);
|
||||||
assertNotNull(deployment);
|
assertNotNull(deployment);
|
||||||
|
assertEquals(178653229, deployment.getId());
|
||||||
|
assertEquals("production", deployment.getEnvironment());
|
||||||
|
assertEquals("custom", deployment.getPayload());
|
||||||
|
assertEquals("custom", deployment.getPayloadObject());
|
||||||
|
assertEquals("master", deployment.getRef());
|
||||||
|
assertEquals("3a09d2de4a9a1322a0ba2c3e2f54a919ca8fe353", deployment.getSha());
|
||||||
|
assertEquals("deploy", deployment.getTask());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetDeploymentByIdObjectPayload() throws IOException {
|
||||||
|
final GHRepository repo = getRepository();
|
||||||
|
final GHDeployment deployment = repo.getDeployment(178653229);
|
||||||
|
assertNotNull(deployment);
|
||||||
|
assertEquals(178653229, deployment.getId());
|
||||||
|
assertEquals("production", deployment.getEnvironment());
|
||||||
|
assertEquals("master", deployment.getRef());
|
||||||
|
assertEquals("3a09d2de4a9a1322a0ba2c3e2f54a919ca8fe353", deployment.getSha());
|
||||||
|
assertEquals("deploy", deployment.getTask());
|
||||||
|
final Map<String, Object> payload = deployment.getPayloadMap();
|
||||||
|
assertEquals(4, payload.size());
|
||||||
|
assertEquals(1, payload.get("custom1"));
|
||||||
|
assertEquals("two", payload.get("custom2"));
|
||||||
|
assertEquals(Arrays.asList("3", 3, "three"), payload.get("custom3"));
|
||||||
|
assertNull(payload.get("custom4"));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected GHRepository getRepository() throws IOException {
|
protected GHRepository getRepository() throws IOException {
|
||||||
return getRepository(gitHub);
|
return getRepository(gitHub);
|
||||||
}
|
}
|
||||||
|
|
||||||
private GHRepository getRepository(GitHub gitHub) throws IOException {
|
private GHRepository getRepository(final GitHub gitHub) throws IOException {
|
||||||
return gitHub.getOrganization("github-api-test-org").getRepository("github-api");
|
return gitHub.getOrganization("hub4j-test-org").getRepository("github-api");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
145
src/test/java/org/kohsuke/github/GHDiscussionTest.java
Normal file
145
src/test/java/org/kohsuke/github/GHDiscussionTest.java
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Charles Moulliard
|
||||||
|
*/
|
||||||
|
public class GHDiscussionTest extends AbstractGitHubWireMockTest {
|
||||||
|
private final String TEAM_SLUG = "dummy-team";
|
||||||
|
private GHTeam team;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
team = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(TEAM_SLUG);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void cleanupDiscussions() throws Exception {
|
||||||
|
// only need to clean up if we're pointing to the live site
|
||||||
|
if (mockGitHub.isUseProxy()) {
|
||||||
|
for (GHDiscussion discussion : getGitHubBeforeAfter().getOrganization(GITHUB_API_TEST_ORG)
|
||||||
|
.getTeamBySlug(TEAM_SLUG)
|
||||||
|
.listDiscussions()) {
|
||||||
|
discussion.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreatedDiscussion() throws IOException {
|
||||||
|
GHDiscussion discussion = team.createDiscussion("Some Discussion").body("This is a public discussion").done();
|
||||||
|
assertThat(discussion, notNullValue());
|
||||||
|
assertThat(discussion.getTeam(), equalTo(team));
|
||||||
|
assertThat(discussion.getTitle(), equalTo("Some Discussion"));
|
||||||
|
assertThat(discussion.getBody(), equalTo("This is a public discussion"));
|
||||||
|
assertThat(discussion.isPrivate(), is(false));
|
||||||
|
|
||||||
|
discussion = team.createDiscussion("Some Discussion")
|
||||||
|
.body("This is another public discussion")
|
||||||
|
.private_(false)
|
||||||
|
.done();
|
||||||
|
assertThat(discussion, notNullValue());
|
||||||
|
assertThat(discussion.getTeam(), equalTo(team));
|
||||||
|
assertThat(discussion.getTitle(), equalTo("Some Discussion"));
|
||||||
|
assertThat(discussion.getBody(), equalTo("This is another public discussion"));
|
||||||
|
assertThat(discussion.isPrivate(), is(false));
|
||||||
|
|
||||||
|
discussion = team.createDiscussion("Some Discussion")
|
||||||
|
.body("This is a private (secret) discussion")
|
||||||
|
.private_(true)
|
||||||
|
.done();
|
||||||
|
assertThat(discussion, notNullValue());
|
||||||
|
assertThat(discussion.getTeam(), equalTo(team));
|
||||||
|
assertThat(discussion.getTitle(), equalTo("Some Discussion"));
|
||||||
|
assertThat(discussion.getBody(), equalTo("This is a private (secret) discussion"));
|
||||||
|
assertThat(discussion.isPrivate(), is(true));
|
||||||
|
|
||||||
|
try {
|
||||||
|
team.createDiscussion("Some Discussion").done();
|
||||||
|
fail("Body is required.");
|
||||||
|
} catch (HttpException e) {
|
||||||
|
assertThat(e, instanceOf(HttpException.class));
|
||||||
|
assertThat(e.getMessage(),
|
||||||
|
containsString("https://developer.github.com/v3/teams/discussions/#create-a-discussion"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAndEditDiscussion() throws IOException {
|
||||||
|
GHDiscussion created = team.createDiscussion("Some Discussion").body("This is a test discussion").done();
|
||||||
|
|
||||||
|
GHDiscussion discussion = team.getDiscussion(created.getNumber());
|
||||||
|
|
||||||
|
// Test convenience getId() override
|
||||||
|
assertThat(discussion.getNumber(), equalTo(created.getId()));
|
||||||
|
assertThat(discussion.getTeam(), equalTo(team));
|
||||||
|
assertThat(discussion.getTitle(), equalTo("Some Discussion"));
|
||||||
|
assertThat(discussion.getBody(), equalTo("This is a test discussion"));
|
||||||
|
assertThat(discussion.isPrivate(), is(false));
|
||||||
|
|
||||||
|
// Test equality
|
||||||
|
assertThat(discussion, equalTo(created));
|
||||||
|
|
||||||
|
discussion = discussion.set().body("This is a test discussion changed");
|
||||||
|
assertThat(discussion.getTeam(), notNullValue());
|
||||||
|
|
||||||
|
assertThat(discussion.getTitle(), equalTo("Some Discussion"));
|
||||||
|
assertThat(discussion.getBody(), equalTo("This is a test discussion changed"));
|
||||||
|
|
||||||
|
discussion = discussion.set().title("Title changed");
|
||||||
|
|
||||||
|
assertThat(discussion.getTitle(), equalTo("Title changed"));
|
||||||
|
assertThat(discussion.getBody(), equalTo("This is a test discussion changed"));
|
||||||
|
|
||||||
|
GHDiscussion discussion2 = gitHub.getOrganization(GITHUB_API_TEST_ORG)
|
||||||
|
.getTeamBySlug(TEAM_SLUG)
|
||||||
|
.getDiscussion(discussion.getNumber());
|
||||||
|
|
||||||
|
assertThat(discussion2, equalTo(discussion));
|
||||||
|
assertThat(discussion2.getTitle(), equalTo("Title changed"));
|
||||||
|
assertThat(discussion2.getBody(), equalTo("This is a test discussion changed"));
|
||||||
|
|
||||||
|
discussion = discussion.update().body("This is a test discussion updated").title("Title updated").done();
|
||||||
|
|
||||||
|
assertThat(discussion.getTeam(), notNullValue());
|
||||||
|
assertThat(discussion.getTitle(), equalTo("Title updated"));
|
||||||
|
assertThat(discussion.getBody(), equalTo("This is a test discussion updated"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testListDiscussion() throws IOException {
|
||||||
|
team.createDiscussion("Some Discussion A").body("This is a test discussion").done();
|
||||||
|
team.createDiscussion("Some Discussion B").body("This is a test discussion").done();
|
||||||
|
team.createDiscussion("Some Discussion C").body("This is a test discussion").done();
|
||||||
|
|
||||||
|
Set<GHDiscussion> all = team.listDiscussions().toSet();
|
||||||
|
assertThat(all.size(), equalTo(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToDeleteDiscussion() throws IOException {
|
||||||
|
GHDiscussion discussion = team.createDiscussion("Some Discussion").body("This is a test discussion").done();
|
||||||
|
|
||||||
|
assertThat(discussion.getTitle(), equalTo("Some Discussion"));
|
||||||
|
|
||||||
|
discussion.delete();
|
||||||
|
try {
|
||||||
|
gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(TEAM_SLUG).getDiscussion(discussion.getNumber());
|
||||||
|
fail();
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
assertThat(e.getMessage(),
|
||||||
|
containsString("https://developer.github.com/v3/teams/discussions/#get-a-single-discussion"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,19 +3,22 @@ package org.kohsuke.github;
|
|||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.hamcrest.CoreMatchers.nullValue;
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
|
|
||||||
public class GHEventPayloadTest {
|
public class GHEventPayloadTest extends AbstractGitHubWireMockTest {
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public final PayloadRule payload = new PayloadRule(".json");
|
public final PayloadRule payload = new PayloadRule(".json");
|
||||||
|
|
||||||
|
public GHEventPayloadTest() {
|
||||||
|
useDefaultGitHub = false;
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void commit_comment() throws Exception {
|
public void commit_comment() throws Exception {
|
||||||
GHEventPayload.CommitComment event = GitHub.offline()
|
GHEventPayload.CommitComment event = GitHub.offline()
|
||||||
@@ -283,6 +286,70 @@ public class GHEventPayloadTest {
|
|||||||
assertThat(event.getSender().getLogin(), is("baxterthehacker"));
|
assertThat(event.getSender().getLogin(), is("baxterthehacker"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Payload("push.fork")
|
||||||
|
public void pushToFork() throws Exception {
|
||||||
|
gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()).build();
|
||||||
|
|
||||||
|
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
|
||||||
|
event = gitHub.parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), GHEventPayload.Push.class);
|
||||||
|
assertThat(event.getRepository().getUrl().toString(), is("https://github.com/hub4j-test-org/github-api"));
|
||||||
|
assertThat(event.getRepository().getHttpTransportUrl(), is("https://github.com/hub4j-test-org/github-api.git"));
|
||||||
|
|
||||||
|
event.getRepository().populate();
|
||||||
|
|
||||||
|
// After populate the url is fixed to point to the correct API endpoint
|
||||||
|
assertThat(event.getRepository().getUrl().toString(),
|
||||||
|
is(mockGitHub.apiServer().baseUrl() + "/repos/hub4j-test-org/github-api"));
|
||||||
|
assertThat(event.getRepository().getHttpTransportUrl(), is("https://github.com/hub4j-test-org/github-api.git"));
|
||||||
|
|
||||||
|
// ensure that root has been bound after populate
|
||||||
|
event.getRepository().getSource().getRef("heads/master");
|
||||||
|
event.getRepository().getParent().getRef("heads/master");
|
||||||
|
|
||||||
|
// Source
|
||||||
|
event = gitHub.parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), GHEventPayload.Push.class);
|
||||||
|
assertThat(event.getRepository().getSource().getFullName(), is("hub4j/github-api"));
|
||||||
|
|
||||||
|
// Parent
|
||||||
|
event = gitHub.parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), GHEventPayload.Push.class);
|
||||||
|
assertThat(event.getRepository().getParent().getFullName(), is("hub4j/github-api"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// TODO implement support classes and write test
|
// TODO implement support classes and write test
|
||||||
// @Test
|
// @Test
|
||||||
// public void release() throws Exception {}
|
// public void release() throws Exception {}
|
||||||
@@ -321,7 +388,25 @@ public class GHEventPayloadTest {
|
|||||||
@Payload("check-run")
|
@Payload("check-run")
|
||||||
public void checkRunEvent() throws Exception {
|
public void checkRunEvent() throws Exception {
|
||||||
GHEventPayload.CheckRun event = GitHub.offline()
|
GHEventPayload.CheckRun event = GitHub.offline()
|
||||||
.parseEventPayload(payload.asReader(), GHEventPayload.CheckRun.class);
|
.parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), GHEventPayload.CheckRun.class);
|
||||||
|
GHCheckRun checkRun = verifyBasicCheckRunEvent(event);
|
||||||
|
assertThat("pull body not populated offline", checkRun.getPullRequests().get(0).getBody(), nullValue());
|
||||||
|
assertThat("using offline github", mockGitHub.getRequestCount(), equalTo(0));
|
||||||
|
|
||||||
|
gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()).build();
|
||||||
|
event = gitHub.parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), GHEventPayload.CheckRun.class);
|
||||||
|
checkRun = verifyBasicCheckRunEvent(event);
|
||||||
|
|
||||||
|
int expectedRequestCount = mockGitHub.isUseProxy() ? 3 : 2;
|
||||||
|
assertThat("pull body should be populated",
|
||||||
|
checkRun.getPullRequests().get(0).getBody(),
|
||||||
|
equalTo("This is a pretty simple change that we need to pull into master."));
|
||||||
|
assertThat("multiple getPullRequests() calls are made, the pull is populated only once",
|
||||||
|
mockGitHub.getRequestCount(),
|
||||||
|
equalTo(expectedRequestCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
private GHCheckRun verifyBasicCheckRunEvent(GHEventPayload.CheckRun event) throws IOException {
|
||||||
assertThat(event.getRepository().getName(), is("Hello-World"));
|
assertThat(event.getRepository().getName(), is("Hello-World"));
|
||||||
assertThat(event.getRepository().getOwner().getLogin(), is("Codertocat"));
|
assertThat(event.getRepository().getOwner().getLogin(), is("Codertocat"));
|
||||||
assertThat(event.getAction(), is("created"));
|
assertThat(event.getAction(), is("created"));
|
||||||
@@ -340,9 +425,9 @@ public class GHEventPayloadTest {
|
|||||||
assertThat(formatter.format(checkRun.getCompletedAt()), is("2019-05-15T20:22:22Z"));
|
assertThat(formatter.format(checkRun.getCompletedAt()), is("2019-05-15T20:22:22Z"));
|
||||||
|
|
||||||
assertThat(checkRun.getConclusion(), is("success"));
|
assertThat(checkRun.getConclusion(), is("success"));
|
||||||
assertThat(checkRun.getUrl().toString(),
|
assertThat(checkRun.getUrl().toString(), endsWith("/repos/Codertocat/Hello-World/check-runs/128620228"));
|
||||||
is("https://api.github.com/repos/Codertocat/Hello-World/check-runs/128620228"));
|
assertThat(checkRun.getHtmlUrl().toString(),
|
||||||
assertThat(checkRun.getHtmlUrl().toString(), is("https://github.com/Codertocat/Hello-World/runs/128620228"));
|
endsWith("https://github.com/Codertocat/Hello-World/runs/128620228"));
|
||||||
assertThat(checkRun.getDetailsUrl().toString(), is("https://octocoders.io"));
|
assertThat(checkRun.getDetailsUrl().toString(), is("https://octocoders.io"));
|
||||||
assertThat(checkRun.getApp().getId(), is(29310L));
|
assertThat(checkRun.getApp().getId(), is(29310L));
|
||||||
assertThat(checkRun.getCheckSuite().getId(), is(118578147L));
|
assertThat(checkRun.getCheckSuite().getId(), is(118578147L));
|
||||||
@@ -351,18 +436,41 @@ public class GHEventPayloadTest {
|
|||||||
assertThat(checkRun.getOutput().getText(), nullValue());
|
assertThat(checkRun.getOutput().getText(), nullValue());
|
||||||
assertThat(checkRun.getOutput().getAnnotationsCount(), is(0));
|
assertThat(checkRun.getOutput().getAnnotationsCount(), is(0));
|
||||||
assertThat(checkRun.getOutput().getAnnotationsUrl().toString(),
|
assertThat(checkRun.getOutput().getAnnotationsUrl().toString(),
|
||||||
is("https://api.github.com/repos/Codertocat/Hello-World/check-runs/128620228/annotations"));
|
endsWith("/repos/Codertocat/Hello-World/check-runs/128620228/annotations"));
|
||||||
|
|
||||||
// Checks the deserialization of sender
|
// Checks the deserialization of sender
|
||||||
assertThat(event.getSender().getId(), is(21031067L));
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Payload("check-suite")
|
@Payload("check-suite")
|
||||||
public void checkSuiteEvent() throws Exception {
|
public void checkSuiteEvent() throws Exception {
|
||||||
GHEventPayload.CheckSuite event = GitHub.offline()
|
GHEventPayload.CheckSuite event = GitHub.offline()
|
||||||
.parseEventPayload(payload.asReader(), GHEventPayload.CheckSuite.class);
|
.parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub), GHEventPayload.CheckSuite.class);
|
||||||
|
GHCheckSuite checkSuite = verifyBasicCheckSuiteEvent(event);
|
||||||
|
assertThat("pull body not populated offline", checkSuite.getPullRequests().get(0).getBody(), nullValue());
|
||||||
|
assertThat("using offline github", mockGitHub.getRequestCount(), equalTo(0));
|
||||||
|
|
||||||
|
gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()).build();
|
||||||
|
event = gitHub.parseEventPayload(payload.asReader(mockGitHub::mapToMockGitHub),
|
||||||
|
GHEventPayload.CheckSuite.class);
|
||||||
|
checkSuite = verifyBasicCheckSuiteEvent(event);
|
||||||
|
|
||||||
|
int expectedRequestCount = mockGitHub.isUseProxy() ? 3 : 2;
|
||||||
|
assertThat("pull body should be populated",
|
||||||
|
checkSuite.getPullRequests().get(0).getBody(),
|
||||||
|
equalTo("This is a pretty simple change that we need to pull into master."));
|
||||||
|
assertThat("multiple getPullRequests() calls are made, the pull is populated only once",
|
||||||
|
mockGitHub.getRequestCount(),
|
||||||
|
lessThanOrEqualTo(expectedRequestCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
private GHCheckSuite verifyBasicCheckSuiteEvent(GHEventPayload.CheckSuite event) throws IOException {
|
||||||
assertThat(event.getRepository().getName(), is("Hello-World"));
|
assertThat(event.getRepository().getName(), is("Hello-World"));
|
||||||
assertThat(event.getRepository().getOwner().getLogin(), is("Codertocat"));
|
assertThat(event.getRepository().getOwner().getLogin(), is("Codertocat"));
|
||||||
assertThat(event.getAction(), is("completed"));
|
assertThat(event.getAction(), is("completed"));
|
||||||
@@ -379,7 +487,7 @@ public class GHEventPayloadTest {
|
|||||||
assertThat(checkSuite.getAfter(), is("ec26c3e57ca3a959ca5aad62de7213c562f8c821"));
|
assertThat(checkSuite.getAfter(), is("ec26c3e57ca3a959ca5aad62de7213c562f8c821"));
|
||||||
assertThat(checkSuite.getLatestCheckRunsCount(), is(1));
|
assertThat(checkSuite.getLatestCheckRunsCount(), is(1));
|
||||||
assertThat(checkSuite.getCheckRunsUrl().toString(),
|
assertThat(checkSuite.getCheckRunsUrl().toString(),
|
||||||
is("https://api.github.com/repos/Codertocat/Hello-World/check-suites/118578147/check-runs"));
|
endsWith("/repos/Codertocat/Hello-World/check-suites/118578147/check-runs"));
|
||||||
assertThat(checkSuite.getHeadCommit().getMessage(), is("Update README.md"));
|
assertThat(checkSuite.getHeadCommit().getMessage(), is("Update README.md"));
|
||||||
assertThat(checkSuite.getHeadCommit().getId(), is("ec26c3e57ca3a959ca5aad62de7213c562f8c821"));
|
assertThat(checkSuite.getHeadCommit().getId(), is("ec26c3e57ca3a959ca5aad62de7213c562f8c821"));
|
||||||
assertThat(checkSuite.getHeadCommit().getTreeId(), is("31b122c26a97cf9af023e9ddab94a82c6e77b0ea"));
|
assertThat(checkSuite.getHeadCommit().getTreeId(), is("31b122c26a97cf9af023e9ddab94a82c6e77b0ea"));
|
||||||
@@ -391,6 +499,11 @@ public class GHEventPayloadTest {
|
|||||||
assertThat(formatter.format(checkSuite.getHeadCommit().getTimestamp()), is("2019-05-15T15:20:30Z"));
|
assertThat(formatter.format(checkSuite.getHeadCommit().getTimestamp()), is("2019-05-15T15:20:30Z"));
|
||||||
|
|
||||||
assertThat(checkSuite.getApp().getId(), is(29310L));
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package org.kohsuke.github;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
import java.io.FileNotFoundException;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.hamcrest.core.Is.is;
|
import static org.hamcrest.core.Is.is;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,9 +19,12 @@ public class GHGistTest extends AbstractGitHubWireMockTest {
|
|||||||
.description("Test Gist")
|
.description("Test Gist")
|
||||||
.file("abc.txt", "abc")
|
.file("abc.txt", "abc")
|
||||||
.file("def.txt", "def")
|
.file("def.txt", "def")
|
||||||
|
.file("ghi.txt", "ghi")
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
assertThat(gist.getCreatedAt(), is(notNullValue()));
|
assertThat(gist.getCreatedAt(), is(notNullValue()));
|
||||||
|
assertThat(gist.getDescription(), equalTo("Test Gist"));
|
||||||
|
assertThat(gist.getFiles().size(), equalTo(3));
|
||||||
|
|
||||||
assertNotNull(gist.getUpdatedAt());
|
assertNotNull(gist.getUpdatedAt());
|
||||||
assertNotNull(gist.getCommentsUrl());
|
assertNotNull(gist.getCommentsUrl());
|
||||||
@@ -28,7 +33,66 @@ public class GHGistTest extends AbstractGitHubWireMockTest {
|
|||||||
assertNotNull(gist.getGitPushUrl());
|
assertNotNull(gist.getGitPushUrl());
|
||||||
assertNotNull(gist.getHtmlUrl());
|
assertNotNull(gist.getHtmlUrl());
|
||||||
|
|
||||||
|
String id = gist.getGistId();
|
||||||
|
|
||||||
|
GHGist gistUpdate = gitHub.getGist(id);
|
||||||
|
assertThat(gistUpdate.getGistId(), equalTo(gist.getGistId()));
|
||||||
|
assertThat(gistUpdate.getDescription(), equalTo(gist.getDescription()));
|
||||||
|
assertThat(gistUpdate.getFiles().size(), equalTo(3));
|
||||||
|
|
||||||
|
gistUpdate = gistUpdate.update().description("Gist Test").addFile("jkl.txt", "jkl").update();
|
||||||
|
|
||||||
|
assertThat(gistUpdate.getGistId(), equalTo(gist.getGistId()));
|
||||||
|
assertThat(gistUpdate.getDescription(), equalTo("Gist Test"));
|
||||||
|
assertThat(gistUpdate.getFiles().size(), equalTo(4));
|
||||||
|
|
||||||
|
gistUpdate = gistUpdate.update()
|
||||||
|
.renameFile("abc.txt", "ab.txt")
|
||||||
|
.deleteFile("def.txt")
|
||||||
|
.updateFile("ghi.txt", "gh")
|
||||||
|
.updateFile("jkl.txt", "klm.txt", "nop")
|
||||||
|
.update();
|
||||||
|
|
||||||
|
assertThat(gistUpdate.getGistId(), equalTo(gist.getGistId()));
|
||||||
|
assertThat(gistUpdate.getDescription(), equalTo("Gist Test"));
|
||||||
|
assertThat(gistUpdate.getFiles().size(), equalTo(3));
|
||||||
|
|
||||||
|
// verify delete works
|
||||||
|
assertThat(gistUpdate.getFile("def.txt"), nullValue());
|
||||||
|
|
||||||
|
// verify rename
|
||||||
|
assertThat(gistUpdate.getFile("ab.txt").getContent(), equalTo("abc"));
|
||||||
|
|
||||||
|
// verify updates
|
||||||
|
assertThat(gistUpdate.getFile("ghi.txt").getContent(), equalTo("gh"));
|
||||||
|
assertThat(gistUpdate.getFile("klm.txt").getContent(), equalTo("nop"));
|
||||||
|
|
||||||
|
// rename and update on the same file in one update shoudl work.
|
||||||
|
gistUpdate = gistUpdate.update().renameFile("ab.txt", "a.txt").updateFile("ab.txt", "abcd").update();
|
||||||
|
|
||||||
|
assertThat(gistUpdate.getGistId(), equalTo(gist.getGistId()));
|
||||||
|
assertThat(gistUpdate.getFiles().size(), equalTo(3));
|
||||||
|
|
||||||
|
// verify rename and update
|
||||||
|
assertThat(gistUpdate.getFile("a.txt").getContent(), equalTo("abcd"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
gist.getId();
|
||||||
|
fail("Newly created gists do not have numeric ids.");
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
assertThat(e, notNullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(gist.getGistId(), notNullValue());
|
||||||
|
|
||||||
gist.delete();
|
gist.delete();
|
||||||
|
|
||||||
|
try {
|
||||||
|
gitHub.getGist(id);
|
||||||
|
fail("Gist should be deleted.");
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
assertThat(e, notNullValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -63,6 +127,8 @@ public class GHGistTest extends AbstractGitHubWireMockTest {
|
|||||||
GHGist gist = gitHub.getGist("9903708");
|
GHGist gist = gitHub.getGist("9903708");
|
||||||
|
|
||||||
assertTrue(gist.isPublic());
|
assertTrue(gist.isPublic());
|
||||||
|
assertThat(gist.getId(), equalTo(9903708L));
|
||||||
|
assertThat(gist.getGistId(), equalTo("9903708"));
|
||||||
|
|
||||||
assertEquals(1, gist.getFiles().size());
|
assertEquals(1, gist.getFiles().size());
|
||||||
GHGistFile f = gist.getFile("keybase.md");
|
GHGistFile f = gist.getFile("keybase.md");
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
|
|
||||||
|
public class GHIssueEventAttributeTest extends AbstractGitHubWireMockTest {
|
||||||
|
|
||||||
|
private enum Type implements Predicate<GHIssueEvent>, Consumer<GHIssueEvent> {
|
||||||
|
milestone(e -> assertNotNull(e.getMilestone()), "milestoned", "demilestoned"),
|
||||||
|
label(e -> assertNotNull(e.getLabel()), "labeled", "unlabeled"),
|
||||||
|
assignment(e -> assertNotNull(e.getAssignee()), "assigned", "unassigned");
|
||||||
|
|
||||||
|
private final Consumer<GHIssueEvent> assertion;
|
||||||
|
private final Set<String> subtypes;
|
||||||
|
|
||||||
|
Type(final Consumer<GHIssueEvent> assertion, final String... subtypes) {
|
||||||
|
this.assertion = assertion;
|
||||||
|
this.subtypes = new HashSet<>(asList(subtypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean test(final GHIssueEvent event) {
|
||||||
|
return this.subtypes.contains(event.getEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(final GHIssueEvent event) {
|
||||||
|
this.assertion.accept(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<GHIssueEvent> listEvents(final Type type) throws IOException {
|
||||||
|
return StreamSupport
|
||||||
|
.stream(gitHub.getRepository("chids/project-milestone-test").getIssue(1).listEvents().spliterator(),
|
||||||
|
false)
|
||||||
|
.filter(type)
|
||||||
|
.collect(toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEventSpecificAttributes() throws IOException {
|
||||||
|
for (Type type : Type.values()) {
|
||||||
|
final List<GHIssueEvent> events = listEvents(type);
|
||||||
|
assertThat(events, hasSize(2));
|
||||||
|
events.forEach(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,6 +56,6 @@ public class GHIssueEventTest extends AbstractGitHubWireMockTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private GHRepository getRepository(GitHub gitHub) throws IOException {
|
private GHRepository getRepository(GitHub gitHub) throws IOException {
|
||||||
return gitHub.getOrganization("github-api-test-org").getRepository("github-api");
|
return gitHub.getOrganization("hub4j-test-org").getRepository("github-api");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ import org.junit.Test;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Duncan Dickinson
|
* @author Duncan Dickinson
|
||||||
*/
|
*/
|
||||||
@@ -91,7 +93,7 @@ public class GHLicenseTest extends AbstractGitHubWireMockTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void checkRepositoryLicense() throws IOException {
|
public void checkRepositoryLicense() throws IOException {
|
||||||
GHRepository repo = gitHub.getRepository("github-api/github-api");
|
GHRepository repo = gitHub.getRepository("hub4j/github-api");
|
||||||
GHLicense license = repo.getLicense();
|
GHLicense license = repo.getLicense();
|
||||||
assertNotNull("The license is populated", license);
|
assertNotNull("The license is populated", license);
|
||||||
assertTrue("The key is correct", license.getKey().equals("mit"));
|
assertTrue("The key is correct", license.getKey().equals("mit"));
|
||||||
@@ -157,7 +159,7 @@ public class GHLicenseTest extends AbstractGitHubWireMockTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void checkRepositoryFullLicense() throws IOException {
|
public void checkRepositoryFullLicense() throws IOException {
|
||||||
GHRepository repo = gitHub.getRepository("github-api/github-api");
|
GHRepository repo = gitHub.getRepository("hub4j/github-api");
|
||||||
GHLicense license = repo.getLicense();
|
GHLicense license = repo.getLicense();
|
||||||
assertNotNull("The license is populated", license);
|
assertNotNull("The license is populated", license);
|
||||||
assertTrue("The key is correct", license.getKey().equals("mit"));
|
assertTrue("The key is correct", license.getKey().equals("mit"));
|
||||||
@@ -190,4 +192,21 @@ public class GHLicenseTest extends AbstractGitHubWireMockTest {
|
|||||||
fail("Expected the license to be Base64 encoded but instead it was " + content.getEncoding());
|
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();
|
||||||
|
assertNotNull("The license is populated", license);
|
||||||
|
assertThat(license.getKey(), equalTo("other"));
|
||||||
|
assertThat(license.getDescription(), is(nullValue()));
|
||||||
|
assertThat(license.getUrl(), is(nullValue()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,11 +70,31 @@ public class GHMilestoneTest extends AbstractGitHubWireMockTest {
|
|||||||
assertEquals(null, issue.getMilestone());
|
assertEquals(null, issue.getMilestone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnsetMilestoneFromPullRequest() throws IOException {
|
||||||
|
GHRepository repo = getRepository();
|
||||||
|
GHMilestone milestone = repo.createMilestone("Unset Test Milestone", "For testUnsetMilestone");
|
||||||
|
GHPullRequest p = repo.createPullRequest("testUnsetMilestoneFromPullRequest",
|
||||||
|
"test/stable",
|
||||||
|
"master",
|
||||||
|
"## test pull request");
|
||||||
|
|
||||||
|
// set the milestone
|
||||||
|
p.setMilestone(milestone);
|
||||||
|
p = repo.getPullRequest(p.getNumber()); // force reload
|
||||||
|
assertEquals(milestone.getNumber(), p.getMilestone().getNumber());
|
||||||
|
|
||||||
|
// remove the milestone
|
||||||
|
p.setMilestone(null);
|
||||||
|
p = repo.getPullRequest(p.getNumber()); // force reload
|
||||||
|
assertNull(p.getMilestone());
|
||||||
|
}
|
||||||
|
|
||||||
protected GHRepository getRepository() throws IOException {
|
protected GHRepository getRepository() throws IOException {
|
||||||
return getRepository(gitHub);
|
return getRepository(gitHub);
|
||||||
}
|
}
|
||||||
|
|
||||||
private GHRepository getRepository(GitHub gitHub) throws IOException {
|
private GHRepository getRepository(GitHub gitHub) throws IOException {
|
||||||
return gitHub.getOrganization("github-api-test-org").getRepository("github-api");
|
return gitHub.getOrganization("hub4j-test-org").getRepository("github-api");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public class GHObjectTest extends org.kohsuke.github.AbstractGitHubWireMockTest
|
|||||||
GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG);
|
GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG);
|
||||||
assertThat(org.toString(),
|
assertThat(org.toString(),
|
||||||
containsString(
|
containsString(
|
||||||
"login=github-api-test-org,location=<null>,blog=<null>,email=<null>,name=<null>,company=<null>,type=Organization,followers=0,following=0"));
|
"login=hub4j-test-org,location=<null>,blog=<null>,email=<null>,bio=<null>,name=<null>,company=<null>,type=Organization,followers=0,following=0,hireable=false"));
|
||||||
|
|
||||||
// getResponseHeaderFields is deprecated but we should not break it.
|
// getResponseHeaderFields is deprecated but we should not break it.
|
||||||
assertThat(org.getResponseHeaderFields(), notNullValue());
|
assertThat(org.getResponseHeaderFields(), notNullValue());
|
||||||
|
|||||||
@@ -9,9 +9,12 @@ import org.kohsuke.github.GHOrganization.Permission;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
public class GHOrganizationTest extends AbstractGitHubWireMockTest {
|
public class GHOrganizationTest extends AbstractGitHubWireMockTest {
|
||||||
|
|
||||||
public static final String GITHUB_API_TEST = "github-api-test";
|
public static final String GITHUB_API_TEST = "github-api-test";
|
||||||
|
public static final String GITHUB_API_TEMPLATE_TEST = "github-api-template-test";
|
||||||
public static final String TEAM_NAME_CREATE = "create-team-test";
|
public static final String TEAM_NAME_CREATE = "create-team-test";
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -56,6 +59,58 @@ public class GHOrganizationTest extends AbstractGitHubWireMockTest {
|
|||||||
Assert.assertNotNull(repository.getReadme());
|
Assert.assertNotNull(repository.getReadme());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateRepositoryWithParameterIsTemplate() throws IOException {
|
||||||
|
cleanupRepository(GITHUB_API_TEST_ORG + '/' + GITHUB_API_TEMPLATE_TEST);
|
||||||
|
|
||||||
|
GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG);
|
||||||
|
GHTeam team = org.getTeamByName("Core Developers");
|
||||||
|
|
||||||
|
int requestCount = mockGitHub.getRequestCount();
|
||||||
|
GHRepository repository = org.createRepository(GITHUB_API_TEMPLATE_TEST)
|
||||||
|
.description("a test template repository used to test kohsuke's github-api")
|
||||||
|
.homepage("http://github-api.kohsuke.org/")
|
||||||
|
.team(team)
|
||||||
|
.autoInit(true)
|
||||||
|
.templateRepository(true)
|
||||||
|
.create();
|
||||||
|
Assert.assertNotNull(repository);
|
||||||
|
assertThat(mockGitHub.getRequestCount(), equalTo(requestCount + 1));
|
||||||
|
|
||||||
|
Assert.assertNotNull(repository.getReadme());
|
||||||
|
assertThat(mockGitHub.getRequestCount(), equalTo(requestCount + 2));
|
||||||
|
|
||||||
|
// isTemplate() does not call populate() from create
|
||||||
|
assertThat(repository.isTemplate(), equalTo(true));
|
||||||
|
assertThat(mockGitHub.getRequestCount(), equalTo(requestCount + 2));
|
||||||
|
|
||||||
|
repository = org.getRepository(GITHUB_API_TEMPLATE_TEST);
|
||||||
|
|
||||||
|
// first isTemplate() calls populate()
|
||||||
|
assertThat(repository.isTemplate(), equalTo(true));
|
||||||
|
assertThat(mockGitHub.getRequestCount(), equalTo(requestCount + 4));
|
||||||
|
|
||||||
|
// second isTemplate() does not call populate()
|
||||||
|
assertThat(repository.isTemplate(), equalTo(true));
|
||||||
|
assertThat(mockGitHub.getRequestCount(), equalTo(requestCount + 4));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateRepositoryWithTemplate() throws IOException {
|
||||||
|
cleanupRepository(GITHUB_API_TEST_ORG + '/' + GITHUB_API_TEST);
|
||||||
|
|
||||||
|
GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG);
|
||||||
|
GHRepository repository = org.createRepository(GITHUB_API_TEST)
|
||||||
|
.fromTemplateRepository(GITHUB_API_TEST_ORG, GITHUB_API_TEMPLATE_TEST)
|
||||||
|
.owner(GITHUB_API_TEST_ORG)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
Assert.assertNotNull(repository);
|
||||||
|
Assert.assertNotNull(repository.getReadme());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInviteUser() throws IOException {
|
public void testInviteUser() throws IOException {
|
||||||
GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG);
|
GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG);
|
||||||
|
|||||||
@@ -28,6 +28,6 @@ public class GHPersonTest extends AbstractGitHubWireMockTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private GHRepository getRepository(GitHub gitHub) throws IOException {
|
private GHRepository getRepository(GitHub gitHub) throws IOException {
|
||||||
return gitHub.getOrganization("github-api-test-org").getRepository("github-api");
|
return gitHub.getOrganization("hub4j-test-org").getRepository("github-api");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ public class GHPullRequestTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
// Assert htmlUrl is not null
|
// Assert htmlUrl is not null
|
||||||
assertNotNull(comment.getHtmlUrl());
|
assertNotNull(comment.getHtmlUrl());
|
||||||
assertEquals(new URL("https://github.com/github-api-test-org/github-api/pull/266#discussion_r321995146"),
|
assertEquals(new URL("https://github.com/hub4j-test-org/github-api/pull/266#discussion_r321995146"),
|
||||||
comment.getHtmlUrl());
|
comment.getHtmlUrl());
|
||||||
|
|
||||||
comment.update("Updated review comment");
|
comment.update("Updated review comment");
|
||||||
@@ -156,7 +156,7 @@ public class GHPullRequestTest extends AbstractGitHubWireMockTest {
|
|||||||
// System.out.println(p.getUrl());
|
// System.out.println(p.getUrl());
|
||||||
assertTrue(p.getRequestedReviewers().isEmpty());
|
assertTrue(p.getRequestedReviewers().isEmpty());
|
||||||
|
|
||||||
GHOrganization testOrg = gitHub.getOrganization("github-api-test-org");
|
GHOrganization testOrg = gitHub.getOrganization("hub4j-test-org");
|
||||||
GHTeam testTeam = testOrg.getTeamBySlug("dummy-team");
|
GHTeam testTeam = testOrg.getTeamBySlug("dummy-team");
|
||||||
|
|
||||||
p.requestTeamReviewers(Collections.singletonList(testTeam));
|
p.requestTeamReviewers(Collections.singletonList(testTeam));
|
||||||
@@ -258,7 +258,7 @@ public class GHPullRequestTest extends AbstractGitHubWireMockTest {
|
|||||||
// Query by one of the heads and make sure we only get that branch's PR back.
|
// Query by one of the heads and make sure we only get that branch's PR back.
|
||||||
List<GHPullRequest> prs = repo.queryPullRequests()
|
List<GHPullRequest> prs = repo.queryPullRequests()
|
||||||
.state(GHIssueState.OPEN)
|
.state(GHIssueState.OPEN)
|
||||||
.head("github-api-test-org:test/stable")
|
.head("hub4j-test-org:test/stable")
|
||||||
.base("master")
|
.base("master")
|
||||||
.list()
|
.list()
|
||||||
.toList();
|
.toList();
|
||||||
@@ -329,6 +329,6 @@ public class GHPullRequestTest extends AbstractGitHubWireMockTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private GHRepository getRepository(GitHub gitHub) throws IOException {
|
private GHRepository getRepository(GitHub gitHub) throws IOException {
|
||||||
return gitHub.getOrganization("github-api-test-org").getRepository("github-api");
|
return gitHub.getOrganization("hub4j-test-org").getRepository("github-api");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ package org.kohsuke.github;
|
|||||||
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
|
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
|
||||||
import com.fasterxml.jackson.databind.exc.ValueInstantiationException;
|
import com.fasterxml.jackson.databind.exc.ValueInstantiationException;
|
||||||
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
|
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
|
||||||
import org.hamcrest.CoreMatchers;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
|
import static org.hamcrest.CoreMatchers.sameInstance;
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.hamcrest.core.IsInstanceOf.instanceOf;
|
import static org.hamcrest.core.IsInstanceOf.instanceOf;
|
||||||
|
|
||||||
@@ -51,13 +53,16 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
|
|||||||
public void testGitHubRateLimit() throws Exception {
|
public void testGitHubRateLimit() throws Exception {
|
||||||
// Customized response that templates the date to keep things working
|
// Customized response that templates the date to keep things working
|
||||||
snapshotNotAllowed();
|
snapshotNotAllowed();
|
||||||
|
GHRateLimit.UnknownLimitRecord.reset();
|
||||||
|
|
||||||
assertThat(mockGitHub.getRequestCount(), equalTo(0));
|
assertThat(mockGitHub.getRequestCount(), equalTo(0));
|
||||||
|
|
||||||
// 4897 is just the what the limit was when the snapshot was taken
|
// 4897 is just the what the limit was when the snapshot was taken
|
||||||
previousLimit = GHRateLimit.fromHeaderRecord(new GHRateLimit.Record(5000,
|
previousLimit = GHRateLimit.fromRecord(
|
||||||
4897,
|
new GHRateLimit.Record(5000,
|
||||||
(templating.testStartDate.getTime() + Duration.ofHours(1).toMillis()) / 1000L));
|
4897,
|
||||||
|
(templating.testStartDate.getTime() + Duration.ofHours(1).toMillis()) / 1000L),
|
||||||
|
RateLimitTarget.CORE);
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// /user gets response with rate limit information
|
// /user gets response with rate limit information
|
||||||
@@ -76,8 +81,8 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
|
|||||||
// Give this a moment
|
// Give this a moment
|
||||||
Thread.sleep(1500);
|
Thread.sleep(1500);
|
||||||
|
|
||||||
// ratelimit() uses headerRateLimit if available and headerRateLimit is not expired
|
// ratelimit() uses cached rate limit if available and not expired
|
||||||
assertThat(gitHub.rateLimit(), equalTo(headerRateLimit));
|
assertThat(gitHub.rateLimit(), sameInstance(headerRateLimit));
|
||||||
|
|
||||||
assertThat(mockGitHub.getRequestCount(), equalTo(1));
|
assertThat(mockGitHub.getRequestCount(), equalTo(1));
|
||||||
|
|
||||||
@@ -88,8 +93,13 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
|
|||||||
rateLimit = gitHub.getRateLimit();
|
rateLimit = gitHub.getRateLimit();
|
||||||
assertThat(mockGitHub.getRequestCount(), equalTo(2));
|
assertThat(mockGitHub.getRequestCount(), equalTo(2));
|
||||||
|
|
||||||
// Because remaining and reset date are unchanged, the header should be unchanged as well
|
// Because remaining and reset date are unchanged in core, the header should be unchanged as well
|
||||||
assertThat(gitHub.lastRateLimit(), sameInstance(headerRateLimit));
|
// 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
|
// rate limit request is free, remaining is unchanged
|
||||||
verifyRateLimitValues(previousLimit, previousLimit.getRemaining());
|
verifyRateLimitValues(previousLimit, previousLimit.getRemaining());
|
||||||
@@ -141,9 +151,8 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
|
|||||||
verifyRateLimitValues(previousLimit, previousLimit.getRemaining(), true);
|
verifyRateLimitValues(previousLimit, previousLimit.getRemaining(), true);
|
||||||
previousLimit = rateLimit;
|
previousLimit = rateLimit;
|
||||||
|
|
||||||
// When getRateLimit() succeeds, headerRateLimit updates as usual as well (if needed)
|
// When getRateLimit() succeeds, cached rate limit updates as usual as well (if needed)
|
||||||
// These are separate instances, but should be equal
|
assertThat(gitHub.rateLimit(), sameInstance(rateLimit));
|
||||||
assertThat(gitHub.rateLimit(), not(sameInstance(rateLimit)));
|
|
||||||
|
|
||||||
// Verify different record instances can be compared
|
// Verify different record instances can be compared
|
||||||
assertThat(gitHub.rateLimit().getCore(), equalTo(rateLimit.getCore()));
|
assertThat(gitHub.rateLimit().getCore(), equalTo(rateLimit.getCore()));
|
||||||
@@ -154,8 +163,43 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
assertThat(gitHub.rateLimit(), not(sameInstance(headerRateLimit)));
|
assertThat(gitHub.rateLimit(), not(sameInstance(headerRateLimit)));
|
||||||
assertThat(gitHub.rateLimit(), sameInstance(gitHub.lastRateLimit()));
|
assertThat(gitHub.rateLimit(), sameInstance(gitHub.lastRateLimit()));
|
||||||
|
headerRateLimit = gitHub.lastRateLimit();
|
||||||
|
|
||||||
assertThat(mockGitHub.getRequestCount(), equalTo(5));
|
assertThat(mockGitHub.getRequestCount(), equalTo(5));
|
||||||
|
|
||||||
|
// Verify the requesting a search url updates the search rate limit
|
||||||
|
assertThat(gitHub.lastRateLimit().getSearch().getRemaining(), equalTo(30));
|
||||||
|
|
||||||
|
HashMap<String, Object> searchResult = (HashMap<String, Object>) 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<GHRepository> 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) {
|
private void verifyRateLimitValues(GHRateLimit previousLimit, int remaining) {
|
||||||
@@ -194,6 +238,8 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
|
|||||||
public void testGitHubEnterpriseDoesNotHaveRateLimit() throws Exception {
|
public void testGitHubEnterpriseDoesNotHaveRateLimit() throws Exception {
|
||||||
// Customized response that results in file not found the same as GitHub Enterprise
|
// Customized response that results in file not found the same as GitHub Enterprise
|
||||||
snapshotNotAllowed();
|
snapshotNotAllowed();
|
||||||
|
GHRateLimit.UnknownLimitRecord.reset();
|
||||||
|
|
||||||
assertThat(mockGitHub.getRequestCount(), equalTo(0));
|
assertThat(mockGitHub.getRequestCount(), equalTo(0));
|
||||||
GHRateLimit rateLimit = null;
|
GHRateLimit rateLimit = null;
|
||||||
|
|
||||||
@@ -203,13 +249,14 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
|
|||||||
Thread.sleep(1500);
|
Thread.sleep(1500);
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// Before any queries, rate limit starts as null but may be requested
|
// Before any queries, rate limit starts as default but may be requested
|
||||||
gitHub = GitHub.connectToEnterprise(mockGitHub.apiServer().baseUrl(), "bogus", "bogus");
|
gitHub = GitHub.connectToEnterprise(mockGitHub.apiServer().baseUrl(), "bogus", "bogus");
|
||||||
assertThat(mockGitHub.getRequestCount(), equalTo(0));
|
assertThat(mockGitHub.getRequestCount(), equalTo(0));
|
||||||
|
|
||||||
assertThat(gitHub.lastRateLimit(), CoreMatchers.nullValue());
|
assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT));
|
||||||
|
|
||||||
rateLimit = gitHub.rateLimit();
|
rateLimit = gitHub.rateLimit();
|
||||||
|
assertThat(gitHub.lastRateLimit(), not(equalTo(GHRateLimit.DEFAULT)));
|
||||||
assertThat(rateLimit.getCore(), instanceOf(GHRateLimit.UnknownLimitRecord.class));
|
assertThat(rateLimit.getCore(), instanceOf(GHRateLimit.UnknownLimitRecord.class));
|
||||||
assertThat(rateLimit.getLimit(), equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit));
|
assertThat(rateLimit.getLimit(), equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit));
|
||||||
assertThat(rateLimit.getRemaining(), equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining));
|
assertThat(rateLimit.getRemaining(), equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining));
|
||||||
@@ -218,8 +265,8 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
assertThat(mockGitHub.getRequestCount(), equalTo(1));
|
assertThat(mockGitHub.getRequestCount(), equalTo(1));
|
||||||
|
|
||||||
// last is still null, because it actually means lastHeaderRateLimit
|
// lastRateLimit the same as rateLimit
|
||||||
assertThat(gitHub.lastRateLimit(), CoreMatchers.nullValue());
|
assertThat(gitHub.lastRateLimit(), sameInstance(rateLimit));
|
||||||
|
|
||||||
assertThat(mockGitHub.getRequestCount(), equalTo(1));
|
assertThat(mockGitHub.getRequestCount(), equalTo(1));
|
||||||
|
|
||||||
@@ -227,18 +274,20 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
|
|||||||
Thread.sleep(1500);
|
Thread.sleep(1500);
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// First call to /user gets response without rate limit information
|
// Some versions of GHE include header rate limit information, some do not
|
||||||
|
// This response mocks the behavior without header rate limit information
|
||||||
gitHub = GitHub.connectToEnterprise(mockGitHub.apiServer().baseUrl(), "bogus", "bogus");
|
gitHub = GitHub.connectToEnterprise(mockGitHub.apiServer().baseUrl(), "bogus", "bogus");
|
||||||
gitHub.getMyself();
|
gitHub.getMyself();
|
||||||
assertThat(mockGitHub.getRequestCount(), equalTo(2));
|
assertThat(mockGitHub.getRequestCount(), equalTo(2));
|
||||||
|
|
||||||
assertThat(gitHub.lastRateLimit(), CoreMatchers.nullValue());
|
assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT));
|
||||||
|
|
||||||
rateLimit = gitHub.rateLimit();
|
rateLimit = gitHub.rateLimit();
|
||||||
assertThat(rateLimit.getCore(), instanceOf(GHRateLimit.UnknownLimitRecord.class));
|
assertThat(rateLimit.getCore(), instanceOf(GHRateLimit.UnknownLimitRecord.class));
|
||||||
assertThat(rateLimit.getLimit(), equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit));
|
assertThat(rateLimit.getLimit(), equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit));
|
||||||
assertThat(rateLimit.getRemaining(), equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining));
|
assertThat(rateLimit.getRemaining(), equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining));
|
||||||
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(1));
|
// Same unknown instance is reused for a while
|
||||||
|
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(0));
|
||||||
lastReset = rateLimit.getResetDate();
|
lastReset = rateLimit.getResetDate();
|
||||||
|
|
||||||
assertThat(mockGitHub.getRequestCount(), equalTo(3));
|
assertThat(mockGitHub.getRequestCount(), equalTo(3));
|
||||||
@@ -246,6 +295,10 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
|
|||||||
// Give this a moment
|
// Give this a moment
|
||||||
Thread.sleep(1500);
|
Thread.sleep(1500);
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// GHE returns 404 from /rate_limit
|
||||||
|
// Some versions of GHE include header rate limit information, some do not
|
||||||
|
// This response mocks the behavior without header rate limit information
|
||||||
// Always requests new info
|
// Always requests new info
|
||||||
rateLimit = gitHub.getRateLimit();
|
rateLimit = gitHub.getRateLimit();
|
||||||
assertThat(mockGitHub.getRequestCount(), equalTo(4));
|
assertThat(mockGitHub.getRequestCount(), equalTo(4));
|
||||||
@@ -253,33 +306,38 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
|
|||||||
assertThat(rateLimit.getCore(), instanceOf(GHRateLimit.UnknownLimitRecord.class));
|
assertThat(rateLimit.getCore(), instanceOf(GHRateLimit.UnknownLimitRecord.class));
|
||||||
assertThat(rateLimit.getLimit(), equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit));
|
assertThat(rateLimit.getLimit(), equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit));
|
||||||
assertThat(rateLimit.getRemaining(), equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining));
|
assertThat(rateLimit.getRemaining(), equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining));
|
||||||
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(1));
|
// When not expired, unknowns do not replace each other so last reset remains unchanged
|
||||||
|
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(0));
|
||||||
|
|
||||||
// Give this a moment
|
// Give this a moment
|
||||||
Thread.sleep(1500);
|
Thread.sleep(1500);
|
||||||
|
|
||||||
// last is still null, because it actually means lastHeaderRateLimit
|
// lastRateLimit the same as rateLimit
|
||||||
assertThat(gitHub.lastRateLimit(), CoreMatchers.nullValue());
|
assertThat(gitHub.lastRateLimit(), sameInstance(rateLimit));
|
||||||
|
|
||||||
// ratelimit() tries not to make additional requests, uses queried rate limit since header not available
|
// ratelimit() tries not to make additional requests, uses queried rate limit since header not available
|
||||||
Thread.sleep(1500);
|
Thread.sleep(1500);
|
||||||
assertThat(gitHub.rateLimit(), sameInstance(rateLimit));
|
assertThat(gitHub.rateLimit(), sameInstance(rateLimit));
|
||||||
|
assertThat(mockGitHub.getRequestCount(), equalTo(4));
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// Second call to /user gets response with rate limit information
|
// Some versions of GHE include header rate limit information, some do not
|
||||||
|
// This response mocks the behavior with header rate limit information
|
||||||
gitHub = GitHub.connectToEnterprise(mockGitHub.apiServer().baseUrl(), "bogus", "bogus");
|
gitHub = GitHub.connectToEnterprise(mockGitHub.apiServer().baseUrl(), "bogus", "bogus");
|
||||||
gitHub.getMyself();
|
gitHub.getMyself();
|
||||||
assertThat(mockGitHub.getRequestCount(), equalTo(5));
|
assertThat(mockGitHub.getRequestCount(), equalTo(5));
|
||||||
|
|
||||||
// Since we already had rate limit info these don't request again
|
// Since just got rate limit from header, these don't request again
|
||||||
rateLimit = gitHub.lastRateLimit();
|
rateLimit = gitHub.lastRateLimit();
|
||||||
assertThat(rateLimit, notNullValue());
|
assertThat(rateLimit, notNullValue());
|
||||||
assertThat(rateLimit.getLimit(), equalTo(5000));
|
assertThat(rateLimit.getLimit(), equalTo(5000));
|
||||||
assertThat(rateLimit.getRemaining(), equalTo(4978));
|
assertThat(rateLimit.getRemaining(), equalTo(4978));
|
||||||
// The previous record was an "Unknown", so even though this records resets sooner we take it
|
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(1));
|
||||||
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(-1));
|
|
||||||
lastReset = rateLimit.getResetDate();
|
lastReset = rateLimit.getResetDate();
|
||||||
|
|
||||||
|
// When getting only header updates, the unknowns are also expired
|
||||||
|
assertThat(rateLimit.getSearch().isExpired(), is(true));
|
||||||
|
|
||||||
GHRateLimit headerRateLimit = rateLimit;
|
GHRateLimit headerRateLimit = rateLimit;
|
||||||
|
|
||||||
// Give this a moment
|
// Give this a moment
|
||||||
@@ -293,22 +351,70 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
|
|||||||
// Give this a moment
|
// Give this a moment
|
||||||
Thread.sleep(1500);
|
Thread.sleep(1500);
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// GHE returns 404 from /rate_limit
|
||||||
|
// Some versions of GHE include header rate limit information, some do not
|
||||||
|
// This response mocks the behavior with header rate limit information
|
||||||
|
// Mocks that other requests come in since previous call
|
||||||
// Always requests new info
|
// Always requests new info
|
||||||
rateLimit = gitHub.getRateLimit();
|
rateLimit = gitHub.getRateLimit();
|
||||||
assertThat(mockGitHub.getRequestCount(), equalTo(6));
|
assertThat(mockGitHub.getRequestCount(), equalTo(6));
|
||||||
|
|
||||||
assertThat(rateLimit.getCore(), instanceOf(GHRateLimit.UnknownLimitRecord.class));
|
// getRateLimit() uses headerRateLimit if /rate_limit returns a 404
|
||||||
assertThat(rateLimit.getLimit(), equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit));
|
// and headerRateLimit is available and not expired
|
||||||
assertThat(rateLimit.getRemaining(), equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining));
|
assertThat(rateLimit, notNullValue());
|
||||||
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(1));
|
assertThat(rateLimit.getLimit(), equalTo(5000));
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
// getRateLimit() uses headerRateLimit if /rate_limit returns a 404
|
||||||
|
// and headerRateLimit is available and not expired
|
||||||
|
assertThat(rateLimit, sameInstance(gitHub.lastRateLimit()));
|
||||||
|
headerRateLimit = rateLimit;
|
||||||
|
|
||||||
// ratelimit() should prefer headerRateLimit when getRateLimit fails and headerRateLimit is not expired
|
// ratelimit() should prefer headerRateLimit when getRateLimit fails and headerRateLimit is not expired
|
||||||
assertThat(gitHub.rateLimit(), equalTo(headerRateLimit));
|
assertThat(gitHub.rateLimit(), sameInstance(rateLimit));
|
||||||
|
|
||||||
assertThat(mockGitHub.getRequestCount(), equalTo(6));
|
assertThat(mockGitHub.getRequestCount(), equalTo(6));
|
||||||
|
|
||||||
// Wait for the header
|
// Verify the requesting a search url updates the search rate limit
|
||||||
Thread.sleep(1500);
|
// Core rate limit record should not change while search is updated.
|
||||||
|
assertThat(gitHub.lastRateLimit().getSearch(), instanceOf(GHRateLimit.UnknownLimitRecord.class));
|
||||||
|
assertThat(gitHub.lastRateLimit().getSearch().isExpired(), equalTo(true));
|
||||||
|
|
||||||
|
HashMap<String, Object> searchResult = (HashMap<String, Object>) gitHub.createRequest()
|
||||||
|
.rateLimit(RateLimitTarget.SEARCH)
|
||||||
|
.setRawUrlPath(mockGitHub.apiServer().baseUrl()
|
||||||
|
+ "/search/repositories?q=tetris+language%3Aassembly&sort=stars&order=desc")
|
||||||
|
.fetch(Object.class);
|
||||||
|
|
||||||
|
assertThat(searchResult.get("total_count"), 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(29));
|
||||||
|
|
||||||
|
PagedSearchIterable<GHRepository> searchResult2 = gitHub.searchRepositories()
|
||||||
|
.q("tetris")
|
||||||
|
.language("assembly")
|
||||||
|
.sort(GHRepositorySearchBuilder.Sort.STARS)
|
||||||
|
.order(GHDirection.DESC)
|
||||||
|
.list();
|
||||||
|
|
||||||
|
assertThat(searchResult2.getTotalCount(), equalTo(1918));
|
||||||
|
|
||||||
|
assertThat(mockGitHub.getRequestCount(), equalTo(8));
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -321,9 +427,8 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
|
|||||||
fail("Invalid rate limit missing some records should throw");
|
fail("Invalid rate limit missing some records should throw");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
assertThat(e, instanceOf(HttpException.class));
|
assertThat(e, instanceOf(HttpException.class));
|
||||||
assertThat(e.getCause(), instanceOf(IOException.class));
|
assertThat(e.getCause(), instanceOf(ValueInstantiationException.class));
|
||||||
assertThat(e.getCause().getCause(), instanceOf(ValueInstantiationException.class));
|
assertThat(e.getCause().getMessage(),
|
||||||
assertThat(e.getCause().getCause().getMessage(),
|
|
||||||
containsString(
|
containsString(
|
||||||
"Cannot construct instance of `org.kohsuke.github.GHRateLimit`, problem: `java.lang.NullPointerException`"));
|
"Cannot construct instance of `org.kohsuke.github.GHRateLimit`, problem: `java.lang.NullPointerException`"));
|
||||||
}
|
}
|
||||||
@@ -333,9 +438,8 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
|
|||||||
fail("Invalid rate limit record missing a value should throw");
|
fail("Invalid rate limit record missing a value should throw");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
assertThat(e, instanceOf(HttpException.class));
|
assertThat(e, instanceOf(HttpException.class));
|
||||||
assertThat(e.getCause(), instanceOf(IOException.class));
|
assertThat(e.getCause(), instanceOf(MismatchedInputException.class));
|
||||||
assertThat(e.getCause().getCause(), instanceOf(MismatchedInputException.class));
|
assertThat(e.getCause().getMessage(),
|
||||||
assertThat(e.getCause().getCause().getMessage(),
|
|
||||||
containsString("Missing required creator property 'reset' (index 2)"));
|
containsString("Missing required creator property 'reset' (index 2)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,6 +459,7 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
|
|||||||
private void executeExpirationTest() throws Exception {
|
private void executeExpirationTest() throws Exception {
|
||||||
// Customized response that templates the date to keep things working
|
// Customized response that templates the date to keep things working
|
||||||
snapshotNotAllowed();
|
snapshotNotAllowed();
|
||||||
|
GHRateLimit.UnknownLimitRecord.reset();
|
||||||
|
|
||||||
assertThat(mockGitHub.getRequestCount(), equalTo(0));
|
assertThat(mockGitHub.getRequestCount(), equalTo(0));
|
||||||
GHRateLimit rateLimit = null;
|
GHRateLimit rateLimit = null;
|
||||||
@@ -396,15 +501,15 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
assertThat("Header instance has expired", gitHub.lastRateLimit().isExpired(), is(true));
|
assertThat("Header instance has expired", gitHub.lastRateLimit().isExpired(), is(true));
|
||||||
|
|
||||||
assertThat("rateLimit() will ask server when header instance expires and it has not called getRateLimit() yet",
|
assertThat("rateLimit() will ask server when cached instance has expired",
|
||||||
gitHub.rateLimit(),
|
gitHub.rateLimit(),
|
||||||
not(sameInstance(rateLimit)));
|
not(sameInstance(rateLimit)));
|
||||||
|
|
||||||
assertThat("lastRateLimit() (header instance) is populated as part of internal call to getRateLimit()",
|
assertThat("lastRateLimit() is populated as part of internal call to getRateLimit()",
|
||||||
gitHub.lastRateLimit(),
|
gitHub.lastRateLimit(),
|
||||||
not(sameInstance(rateLimit)));
|
not(sameInstance(rateLimit)));
|
||||||
|
|
||||||
assertThat("After request, rateLimit() selects header instance since it has been refreshed",
|
assertThat("After request, rateLimit() selects cached since it has been refreshed",
|
||||||
gitHub.rateLimit(),
|
gitHub.rateLimit(),
|
||||||
sameInstance(gitHub.lastRateLimit()));
|
sameInstance(gitHub.lastRateLimit()));
|
||||||
|
|
||||||
@@ -412,27 +517,27 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
assertThat(mockGitHub.getRequestCount(), equalTo(2));
|
assertThat(mockGitHub.getRequestCount(), equalTo(2));
|
||||||
|
|
||||||
// This time, rateLimit() should find an expired header record, but a valid returned record
|
// During the previous call we returned expired header info but valid returned record
|
||||||
|
// Merging means this has already been merged into a valid cached instance
|
||||||
Thread.sleep(4000);
|
Thread.sleep(4000);
|
||||||
|
|
||||||
rateLimit = gitHub.rateLimit();
|
rateLimit = gitHub.rateLimit();
|
||||||
|
|
||||||
// Using custom data to have a header instance that expires before the queried instance
|
// Using custom data to have a header instance that expires before the queried instance
|
||||||
assertThat(
|
assertThat("if valid ratelimit() uses it without asking server",
|
||||||
"if header instance expires but queried instance is valid, ratelimit() uses it without asking server",
|
|
||||||
gitHub.rateLimit(),
|
gitHub.rateLimit(),
|
||||||
not(sameInstance(gitHub.lastRateLimit())));
|
sameInstance(gitHub.lastRateLimit()));
|
||||||
|
|
||||||
assertThat("ratelimit() should almost never return a return a GHRateLimit that is already expired",
|
assertThat("ratelimit() should almost never return a return a GHRateLimit that is already expired",
|
||||||
gitHub.rateLimit().isExpired(),
|
gitHub.rateLimit().isExpired(),
|
||||||
is(false));
|
is(false));
|
||||||
|
|
||||||
assertThat("Header instance hasn't been reloaded", gitHub.lastRateLimit(), sameInstance(headerRateLimit));
|
assertThat("Cached instance hasn't been reloaded", gitHub.lastRateLimit(), sameInstance(headerRateLimit));
|
||||||
assertThat("Header instance has expired", gitHub.lastRateLimit().isExpired(), is(true));
|
assertThat("Cached instance has not expired", gitHub.lastRateLimit().isExpired(), is(false));
|
||||||
|
|
||||||
assertThat(mockGitHub.getRequestCount(), equalTo(2));
|
assertThat(mockGitHub.getRequestCount(), equalTo(2));
|
||||||
|
|
||||||
// Finally they both expire and rateLimit() should find both expired and get a new record
|
// Finally the cached instance expires and rateLimit() should get a new record
|
||||||
Thread.sleep(2000);
|
Thread.sleep(2000);
|
||||||
|
|
||||||
headerRateLimit = gitHub.rateLimit();
|
headerRateLimit = gitHub.rateLimit();
|
||||||
@@ -452,7 +557,7 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static GHRepository getRepository(GitHub gitHub) throws IOException {
|
private static GHRepository getRepository(GitHub gitHub) throws IOException {
|
||||||
return gitHub.getOrganization("github-api-test-org").getRepository("github-api");
|
return gitHub.getOrganization("hub4j-test-org").getRepository("github-api");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.kohsuke.github;
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ public class GHRepositoryTest extends AbstractGitHubWireMockTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private GHRepository getRepository(GitHub gitHub) throws IOException {
|
private GHRepository getRepository(GitHub gitHub) throws IOException {
|
||||||
return gitHub.getOrganization("github-api-test-org").getRepository("github-api");
|
return gitHub.getOrganization("hub4j-test-org").getRepository("github-api");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -43,12 +44,12 @@ public class GHRepositoryTest extends AbstractGitHubWireMockTest {
|
|||||||
assertThat(r.isAllowRebaseMerge(), is(true));
|
assertThat(r.isAllowRebaseMerge(), is(true));
|
||||||
assertThat(r.isAllowSquashMerge(), is(true));
|
assertThat(r.isAllowSquashMerge(), is(true));
|
||||||
|
|
||||||
String httpTransport = "https://github.com/github-api-test-org/temp-testGetters.git";
|
String httpTransport = "https://github.com/hub4j-test-org/temp-testGetters.git";
|
||||||
assertThat(r.getHttpTransportUrl(), equalTo(httpTransport));
|
assertThat(r.getHttpTransportUrl(), equalTo(httpTransport));
|
||||||
assertThat(r.gitHttpTransportUrl(), equalTo(httpTransport));
|
assertThat(r.gitHttpTransportUrl(), equalTo(httpTransport));
|
||||||
|
|
||||||
assertThat(r.getName(), equalTo("temp-testGetters"));
|
assertThat(r.getName(), equalTo("temp-testGetters"));
|
||||||
assertThat(r.getFullName(), equalTo("github-api-test-org/temp-testGetters"));
|
assertThat(r.getFullName(), equalTo("hub4j-test-org/temp-testGetters"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -91,7 +92,7 @@ public class GHRepositoryTest extends AbstractGitHubWireMockTest {
|
|||||||
assertThat(e.getMessage(),
|
assertThat(e.getMessage(),
|
||||||
equalTo("Server returned HTTP response code: 200, message: '404 Not Found' for URL: "
|
equalTo("Server returned HTTP response code: 200, message: '404 Not Found' for URL: "
|
||||||
+ mockGitHub.apiServer().baseUrl()
|
+ mockGitHub.apiServer().baseUrl()
|
||||||
+ "/repos/github-api-test-org/github-api/branches/test/NonExistent"));
|
+ "/repos/hub4j-test-org/github-api/branches/test/NonExistent"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +128,7 @@ public class GHRepositoryTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void listContributors() throws IOException {
|
public void listContributors() throws IOException {
|
||||||
GHRepository r = gitHub.getOrganization("github-api").getRepository("github-api");
|
GHRepository r = gitHub.getOrganization("hub4j").getRepository("github-api");
|
||||||
int i = 0;
|
int i = 0;
|
||||||
boolean kohsuke = false;
|
boolean kohsuke = false;
|
||||||
|
|
||||||
@@ -147,7 +148,7 @@ public class GHRepositoryTest extends AbstractGitHubWireMockTest {
|
|||||||
@Test
|
@Test
|
||||||
public void getPermission() throws Exception {
|
public void getPermission() throws Exception {
|
||||||
kohsuke();
|
kohsuke();
|
||||||
GHRepository r = gitHub.getRepository("github-api-test-org/test-permission");
|
GHRepository r = gitHub.getRepository("hub4j-test-org/test-permission");
|
||||||
assertEquals(GHPermissionType.ADMIN, r.getPermission("kohsuke"));
|
assertEquals(GHPermissionType.ADMIN, r.getPermission("kohsuke"));
|
||||||
assertEquals(GHPermissionType.READ, r.getPermission("dude"));
|
assertEquals(GHPermissionType.READ, r.getPermission("dude"));
|
||||||
r = gitHub.getOrganization("apache").getRepository("groovy");
|
r = gitHub.getOrganization("apache").getRepository("groovy");
|
||||||
@@ -190,6 +191,7 @@ public class GHRepositoryTest extends AbstractGitHubWireMockTest {
|
|||||||
List<GHUser> users = new ArrayList<GHUser>();
|
List<GHUser> users = new ArrayList<GHUser>();
|
||||||
|
|
||||||
users.add(user);
|
users.add(user);
|
||||||
|
users.add(gitHub.getUser("jimmysombrero2"));
|
||||||
repo.addCollaborators(users, GHOrganization.Permission.PUSH);
|
repo.addCollaborators(users, GHOrganization.Permission.PUSH);
|
||||||
|
|
||||||
GHPersonSet<GHUser> collabs = repo.getCollaborators();
|
GHPersonSet<GHUser> collabs = repo.getCollaborators();
|
||||||
@@ -244,7 +246,7 @@ public class GHRepositoryTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void listLanguages() throws IOException {
|
public void listLanguages() throws IOException {
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
String mainLanguage = r.getLanguage();
|
String mainLanguage = r.getLanguage();
|
||||||
assertTrue(r.listLanguages().containsKey(mainLanguage));
|
assertTrue(r.listLanguages().containsKey(mainLanguage));
|
||||||
}
|
}
|
||||||
@@ -273,7 +275,7 @@ public class GHRepositoryTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test // issue #162
|
@Test // issue #162
|
||||||
public void testIssue162() throws Exception {
|
public void testIssue162() throws Exception {
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
List<GHContent> contents = r.getDirectoryContent("", "gh-pages");
|
List<GHContent> contents = r.getDirectoryContent("", "gh-pages");
|
||||||
for (GHContent content : contents) {
|
for (GHContent content : contents) {
|
||||||
if (content.isFile()) {
|
if (content.isFile()) {
|
||||||
@@ -289,11 +291,11 @@ public class GHRepositoryTest extends AbstractGitHubWireMockTest {
|
|||||||
public void markDown() throws Exception {
|
public void markDown() throws Exception {
|
||||||
assertEquals("<p><strong>Test日本語</strong></p>", IOUtils.toString(gitHub.renderMarkdown("**Test日本語**")).trim());
|
assertEquals("<p><strong>Test日本語</strong></p>", IOUtils.toString(gitHub.renderMarkdown("**Test日本語**")).trim());
|
||||||
|
|
||||||
String actual = IOUtils.toString(gitHub.getRepository("github-api/github-api")
|
String actual = IOUtils.toString(
|
||||||
.renderMarkdown("@kohsuke to fix issue #1", MarkdownMode.GFM));
|
gitHub.getRepository("hub4j/github-api").renderMarkdown("@kohsuke to fix issue #1", MarkdownMode.GFM));
|
||||||
// System.out.println(actual);
|
// System.out.println(actual);
|
||||||
assertTrue(actual.contains("href=\"https://github.com/kohsuke\""));
|
assertTrue(actual.contains("href=\"https://github.com/kohsuke\""));
|
||||||
assertTrue(actual.contains("href=\"https://github.com/github-api/github-api/pull/1\""));
|
assertTrue(actual.contains("href=\"https://github.com/hub4j/github-api/pull/1\""));
|
||||||
assertTrue(actual.contains("class=\"user-mention\""));
|
assertTrue(actual.contains("class=\"user-mention\""));
|
||||||
assertTrue(actual.contains("class=\"issue-link "));
|
assertTrue(actual.contains("class=\"issue-link "));
|
||||||
assertTrue(actual.contains("to fix issue"));
|
assertTrue(actual.contains("to fix issue"));
|
||||||
@@ -301,7 +303,7 @@ public class GHRepositoryTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setMergeOptions() throws IOException {
|
public void setMergeOptions() throws IOException {
|
||||||
// String repoName = "github-api-test-org/test-mergeoptions";
|
// String repoName = "hub4j-test-org/test-mergeoptions";
|
||||||
GHRepository r = getTempRepository();
|
GHRepository r = getTempRepository();
|
||||||
|
|
||||||
// at least one merge option must be selected
|
// at least one merge option must be selected
|
||||||
@@ -428,25 +430,93 @@ public class GHRepositoryTest extends AbstractGitHubWireMockTest {
|
|||||||
assertThat(e.getMessage(),
|
assertThat(e.getMessage(),
|
||||||
containsString(
|
containsString(
|
||||||
"{\"message\":\"Not Found\",\"documentation_url\":\"https://developer.github.com/v3/git/refs/#get-a-reference\"}"));
|
"{\"message\":\"Not Found\",\"documentation_url\":\"https://developer.github.com/v3/git/refs/#get-a-reference\"}"));
|
||||||
assertThat(e.getCause(), instanceOf(FileNotFoundException.class));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void listRefs() throws Exception {
|
public void listRefs() throws Exception {
|
||||||
GHRepository repo = getTempRepository();
|
GHRepository repo = getRepository();
|
||||||
List<GHRef> refs = repo.listRefs().toList();
|
|
||||||
assertThat(refs, notNullValue());
|
List<GHRef> ghRefs;
|
||||||
assertThat(refs.size(), equalTo(1));
|
|
||||||
assertThat(refs.get(0).getRef(), equalTo("refs/heads/master"));
|
// handle refs/*
|
||||||
|
ghRefs = repo.listRefs("heads").toList();
|
||||||
|
List<GHRef> ghRefsWithPrefix = repo.listRefs("refs/heads").toList();
|
||||||
|
|
||||||
|
assertThat(ghRefs, notNullValue());
|
||||||
|
assertThat(ghRefs.size(), greaterThan(3));
|
||||||
|
assertThat(ghRefs.get(0).getRef(), equalTo("refs/heads/changes"));
|
||||||
|
assertThat(ghRefsWithPrefix.size(), equalTo(ghRefs.size()));
|
||||||
|
assertThat(ghRefsWithPrefix.get(0).getRef(), equalTo(ghRefs.get(0).getRef()));
|
||||||
|
|
||||||
|
// git/refs/heads/gh-pages
|
||||||
|
// passing a specific ref to listRefs will fail to parse due to returning a single item not an array
|
||||||
|
try {
|
||||||
|
ghRefs = repo.listRefs("heads/gh-pages").toList();
|
||||||
|
fail();
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertThat(e, instanceOf(HttpException.class));
|
||||||
|
assertThat(e.getCause(), instanceOf(JsonMappingException.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
// git/refs/heads/gh
|
||||||
|
ghRefs = repo.listRefs("heads/gh").toList();
|
||||||
|
assertThat(ghRefs, notNullValue());
|
||||||
|
assertThat(ghRefs.size(), equalTo(1));
|
||||||
|
assertThat(ghRefs.get(0).getRef(), equalTo("refs/heads/gh-pages"));
|
||||||
|
|
||||||
|
// git/refs/headz
|
||||||
|
try {
|
||||||
|
ghRefs = repo.listRefs("headz").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\"}"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getRefWithPrefix() throws Exception {
|
public void getRef() throws Exception {
|
||||||
GHRepository repo = getTempRepository();
|
GHRepository repo = getRepository();
|
||||||
GHRef refWithoutPrefix = repo.getRef("heads/master");
|
|
||||||
GHRef refWithPrefix = repo.getRef("refs/heads/master");
|
GHRef ghRef;
|
||||||
assertThat(refWithoutPrefix.getRef(), equalTo(refWithPrefix.getRef()));
|
|
||||||
|
// 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()));
|
||||||
|
|
||||||
|
// 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\"}"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -466,8 +536,7 @@ public class GHRepositoryTest extends AbstractGitHubWireMockTest {
|
|||||||
fail();
|
fail();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
assertThat(e, instanceOf(GHFileNotFoundException.class));
|
assertThat(e, instanceOf(GHFileNotFoundException.class));
|
||||||
assertThat(e.getMessage(),
|
assertThat(e.getMessage(), containsString("/repos/hub4j-test-org/temp-listRefsEmptyTags/git/refs/tags"));
|
||||||
containsString("/repos/github-api-test-org/temp-listRefsEmptyTags/git/refs/tags"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -477,8 +546,7 @@ public class GHRepositoryTest extends AbstractGitHubWireMockTest {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
assertThat(e, instanceOf(GHException.class));
|
assertThat(e, instanceOf(GHException.class));
|
||||||
assertThat(e.getMessage(), containsString("Failed to retrieve "));
|
assertThat(e.getMessage(), containsString("Failed to retrieve "));
|
||||||
assertThat(e.getMessage(),
|
assertThat(e.getMessage(), containsString("/repos/hub4j-test-org/temp-listRefsEmptyTags/git/refs/tags"));
|
||||||
containsString("/repos/github-api-test-org/temp-listRefsEmptyTags/git/refs/tags"));
|
|
||||||
assertThat(e.getCause(), instanceOf(GHFileNotFoundException.class));
|
assertThat(e.getCause(), instanceOf(GHFileNotFoundException.class));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -526,7 +594,7 @@ public class GHRepositoryTest extends AbstractGitHubWireMockTest {
|
|||||||
public void getCheckRuns() throws Exception {
|
public void getCheckRuns() throws Exception {
|
||||||
final int expectedCount = 8;
|
final int expectedCount = 8;
|
||||||
// Use github-api repository as it has checks set up
|
// Use github-api repository as it has checks set up
|
||||||
PagedIterable<GHCheckRun> checkRuns = gitHub.getOrganization("github-api")
|
PagedIterable<GHCheckRun> checkRuns = gitHub.getOrganization("hub4j")
|
||||||
.getRepository("github-api")
|
.getRepository("github-api")
|
||||||
.getCheckRuns("78b9ff49d47daaa158eb373c4e2e040f739df8b9");
|
.getCheckRuns("78b9ff49d47daaa158eb373c4e2e040f739df8b9");
|
||||||
// Check if the paging works correctly
|
// Check if the paging works correctly
|
||||||
|
|||||||
@@ -60,6 +60,6 @@ public class GHTagTest extends AbstractGitHubWireMockTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private GHRepository getRepository(GitHub gitHub) throws IOException {
|
private GHRepository getRepository(GitHub gitHub) throws IOException {
|
||||||
return gitHub.getOrganization("github-api-test-org").getRepository("github-api");
|
return gitHub.getOrganization("hub4j-test-org").getRepository("github-api");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/test/java/org/kohsuke/github/GHTeamBuilderTest.java
Normal file
32
src/test/java/org/kohsuke/github/GHTeamBuilderTest.java
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class GHTeamBuilderTest extends AbstractGitHubWireMockTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateChildTeam() throws IOException {
|
||||||
|
String parentTeamSlug = "dummy-team";
|
||||||
|
String childTeamSlug = "dummy-team-child";
|
||||||
|
String description = "description";
|
||||||
|
|
||||||
|
// Get the parent team
|
||||||
|
GHTeam parentTeam = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(parentTeamSlug);
|
||||||
|
|
||||||
|
// Create a child team, using the parent team identifier
|
||||||
|
GHTeam childTeam = gitHub.getOrganization(GITHUB_API_TEST_ORG)
|
||||||
|
.createTeam(childTeamSlug)
|
||||||
|
.description(description)
|
||||||
|
.privacy(GHTeam.Privacy.CLOSED)
|
||||||
|
.parentTeamId(parentTeam.getId())
|
||||||
|
.create();
|
||||||
|
|
||||||
|
assertEquals(description, childTeam.getDescription());
|
||||||
|
assertEquals(childTeamSlug, childTeam.getName());
|
||||||
|
assertEquals(childTeamSlug, childTeam.getSlug());
|
||||||
|
assertEquals(GHTeam.Privacy.CLOSED, childTeam.getPrivacy());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import org.junit.Test;
|
|||||||
import org.kohsuke.github.GHTeam.Privacy;
|
import org.kohsuke.github.GHTeam.Privacy;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
@@ -56,4 +57,27 @@ public class GHTeamTest extends AbstractGitHubWireMockTest {
|
|||||||
assertEquals(privacy, team.getPrivacy());
|
assertEquals(privacy, team.getPrivacy());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFetchChildTeams() throws IOException {
|
||||||
|
String teamSlug = "dummy-team";
|
||||||
|
|
||||||
|
GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG);
|
||||||
|
GHTeam team = org.getTeamBySlug(teamSlug);
|
||||||
|
Set<GHTeam> result = team.listChildTeams().toSet();
|
||||||
|
|
||||||
|
assertEquals(1, result.size());
|
||||||
|
assertEquals("child-team-for-dummy", result.toArray(new GHTeam[]{})[0].getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFetchEmptyChildTeams() throws IOException {
|
||||||
|
String teamSlug = "simple-team";
|
||||||
|
|
||||||
|
GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG);
|
||||||
|
GHTeam team = org.getTeamBySlug(teamSlug);
|
||||||
|
Set<GHTeam> result = team.listChildTeams().toSet();
|
||||||
|
|
||||||
|
assertEquals(0, result.size());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import java.util.Arrays;
|
|||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
public class GHTreeBuilderTest extends AbstractGitHubWireMockTest {
|
public class GHTreeBuilderTest extends AbstractGitHubWireMockTest {
|
||||||
private static String REPO_NAME = "github-api-test-org/GHTreeBuilderTest";
|
private static String REPO_NAME = "hub4j-test-org/GHTreeBuilderTest";
|
||||||
|
|
||||||
private static String PATH_SCRIPT = "app/run.sh";
|
private static String PATH_SCRIPT = "app/run.sh";
|
||||||
private static String CONTENT_SCRIPT = "#!/bin/bash\necho Hello\n";
|
private static String CONTENT_SCRIPT = "#!/bin/bash\necho Hello\n";
|
||||||
|
|||||||
@@ -102,4 +102,12 @@ public class GHUserTest extends AbstractGitHubWireMockTest {
|
|||||||
repository.delete();
|
repository.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
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"));
|
||||||
|
assertTrue(u.isHireable());
|
||||||
|
assertNotNull(u.getTwitterUsername());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ public class GHVerificationReasonTest extends AbstractGitHubWireMockTest {
|
|||||||
// Issue 737
|
// Issue 737
|
||||||
@Test
|
@Test
|
||||||
public void testExpiredKey() throws Exception {
|
public void testExpiredKey() throws Exception {
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f01");
|
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f01");
|
||||||
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
||||||
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
||||||
@@ -18,7 +18,7 @@ public class GHVerificationReasonTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNotSigningKey() throws Exception {
|
public void testNotSigningKey() throws Exception {
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f02");
|
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f02");
|
||||||
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
||||||
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
||||||
@@ -27,7 +27,7 @@ public class GHVerificationReasonTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGpgverifyError() throws Exception {
|
public void testGpgverifyError() throws Exception {
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f03");
|
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f03");
|
||||||
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
||||||
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
||||||
@@ -36,7 +36,7 @@ public class GHVerificationReasonTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGpgverifyUnavailable() throws Exception {
|
public void testGpgverifyUnavailable() throws Exception {
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f04");
|
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f04");
|
||||||
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
||||||
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
||||||
@@ -46,7 +46,7 @@ public class GHVerificationReasonTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUnsigned() throws Exception {
|
public void testUnsigned() throws Exception {
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f05");
|
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f05");
|
||||||
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
||||||
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
||||||
@@ -55,7 +55,7 @@ public class GHVerificationReasonTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUnknownSignatureType() throws Exception {
|
public void testUnknownSignatureType() throws Exception {
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f06");
|
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f06");
|
||||||
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
||||||
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
||||||
@@ -65,7 +65,7 @@ public class GHVerificationReasonTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNoUser() throws Exception {
|
public void testNoUser() throws Exception {
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f07");
|
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f07");
|
||||||
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
||||||
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
||||||
@@ -74,7 +74,7 @@ public class GHVerificationReasonTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUnverifiedEmail() throws Exception {
|
public void testUnverifiedEmail() throws Exception {
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f08");
|
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f08");
|
||||||
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
||||||
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
||||||
@@ -83,7 +83,7 @@ public class GHVerificationReasonTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBadEmail() throws Exception {
|
public void testBadEmail() throws Exception {
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f09");
|
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f09");
|
||||||
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
||||||
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
||||||
@@ -92,7 +92,7 @@ public class GHVerificationReasonTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUnknownKey() throws Exception {
|
public void testUnknownKey() throws Exception {
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f10");
|
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f10");
|
||||||
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
||||||
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
||||||
@@ -101,7 +101,7 @@ public class GHVerificationReasonTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMalformedSignature() throws Exception {
|
public void testMalformedSignature() throws Exception {
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f11");
|
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f11");
|
||||||
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
||||||
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
||||||
@@ -111,7 +111,7 @@ public class GHVerificationReasonTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInvalid() throws Exception {
|
public void testInvalid() throws Exception {
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f12");
|
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f12");
|
||||||
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
||||||
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
||||||
@@ -120,7 +120,7 @@ public class GHVerificationReasonTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testValid() throws Exception {
|
public void testValid() throws Exception {
|
||||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||||
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f13");
|
GHCommit commit = r.getCommit("86a2e245aa6d71d54923655066049d9e21a15f13");
|
||||||
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Sourabh Parkala");
|
||||||
assertTrue(commit.getCommitShortInfo().getVerification().isVerified());
|
assertTrue(commit.getCommitShortInfo().getVerification().isVerified());
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import org.junit.Test;
|
|||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.*;
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
import static org.hamcrest.core.Is.is;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit test for {@link GitHub} static helpers.
|
* Unit test for {@link GitHub} static helpers.
|
||||||
@@ -20,25 +21,38 @@ public class GitHubStaticTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void timeRoundTrip() throws Exception {
|
public void timeRoundTrip() throws Exception {
|
||||||
Instant instantNow = Instant.now();
|
final long stableInstantEpochMilli = 1533721222255L;
|
||||||
|
Instant instantNow = Instant.ofEpochMilli(stableInstantEpochMilli);
|
||||||
|
|
||||||
Date instantSeconds = Date.from(instantNow.truncatedTo(ChronoUnit.SECONDS));
|
Date instantSeconds = Date.from(instantNow.truncatedTo(ChronoUnit.SECONDS));
|
||||||
Date instantMillis = Date.from(instantNow.truncatedTo(ChronoUnit.MILLIS));
|
Date instantMillis = Date.from(instantNow.truncatedTo(ChronoUnit.MILLIS));
|
||||||
|
|
||||||
// if we happen to land exactly on zero milliseconds, add 1 milli
|
String instantFormatSlash = formatZonedDate(instantMillis, "yyyy/MM/dd HH:mm:ss ZZZZ", "PST");
|
||||||
if (instantSeconds.equals(instantMillis)) {
|
assertThat(instantFormatSlash, equalTo("2018/08/08 02:40:22 -0700"));
|
||||||
instantMillis = Date.from(instantNow.plusMillis(1).truncatedTo(ChronoUnit.MILLIS));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: other formats
|
|
||||||
String instantFormatSlash = formatDate(instantMillis, "yyyy/MM/dd HH:mm:ss ZZZZ");
|
|
||||||
String instantFormatDash = formatDate(instantMillis, "yyyy-MM-dd'T'HH:mm:ss'Z'");
|
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'");
|
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'");
|
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'");
|
String instantBadFormat = formatDate(instantMillis, "yy-MM-dd'T'HH:mm'Z'");
|
||||||
|
assertThat(instantBadFormat, equalTo("18-08-08T09:40Z"));
|
||||||
|
|
||||||
assertThat(GitHubClient.parseDate(GitHubClient.printDate(instantSeconds)),
|
assertThat(GitHubClient.parseDate(GitHubClient.printDate(instantSeconds)),
|
||||||
equalTo(GitHubClient.parseDate(GitHubClient.printDate(instantMillis))));
|
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))));
|
assertThat(instantSeconds, equalTo(GitHubClient.parseDate(GitHubClient.printDate(instantSeconds))));
|
||||||
|
|
||||||
@@ -51,82 +65,153 @@ public class GitHubStaticTest extends AbstractGitHubWireMockTest {
|
|||||||
|
|
||||||
// This parser does not truncate to the nearest second, so it will be equal
|
// 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(instantFormatMillis)));
|
||||||
|
assertThat(instantMillis, equalTo(GitHubClient.parseDate(instantFormatMillisZoned)));
|
||||||
|
|
||||||
assertThat(instantSeconds, equalTo(GitHubClient.parseDate(instantSecondsFormatMillis)));
|
assertThat(instantSeconds, equalTo(GitHubClient.parseDate(instantSecondsFormatMillis)));
|
||||||
|
assertThat(instantSeconds, equalTo(GitHubClient.parseDate(instantSecondsFormatMillisZoned)));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
GitHubClient.parseDate(instantBadFormat);
|
GitHubClient.parseDate(instantBadFormat);
|
||||||
fail("Bad time format should throw.");
|
fail("Bad time format should throw.");
|
||||||
} catch (IllegalStateException e) {
|
} catch (DateTimeParseException e) {
|
||||||
assertThat(e.getMessage(), equalTo("Unable to parse the timestamp: " + instantBadFormat));
|
assertThat(e.getMessage(), equalTo("Text '" + instantBadFormat + "' could not be parsed at index 0"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGitHubRateLimitShouldReplaceRateLimit() throws Exception {
|
public void testGitHubRateLimitShouldReplaceRateLimit() throws Exception {
|
||||||
|
|
||||||
GHRateLimit.Record unknown0 = GHRateLimit.Unknown().getCore();
|
GHRateLimit.UnknownLimitRecord.reset();
|
||||||
GHRateLimit.Record unknown1 = GHRateLimit.Unknown().getCore();
|
GHRateLimit.UnknownLimitRecord.unknownLimitResetSeconds = 5;
|
||||||
|
|
||||||
GHRateLimit.Record record0 = new GHRateLimit.Record(10, 10, 10L);
|
GHRateLimit.Record unknown0 = GHRateLimit.UnknownLimitRecord.current();
|
||||||
GHRateLimit.Record record1 = new GHRateLimit.Record(10, 9, 10L);
|
|
||||||
GHRateLimit.Record record2 = new GHRateLimit.Record(10, 2, 10L);
|
|
||||||
GHRateLimit.Record record3 = new GHRateLimit.Record(10, 10, 20L);
|
|
||||||
GHRateLimit.Record record4 = new GHRateLimit.Record(10, 5, 20L);
|
|
||||||
|
|
||||||
Thread.sleep(2000);
|
Thread.sleep(1500);
|
||||||
|
GHRateLimit.UnknownLimitRecord.reset();
|
||||||
|
GHRateLimit.UnknownLimitRecord.unknownLimitResetSeconds = 5;
|
||||||
|
|
||||||
|
// For testing, we create an new unknown.
|
||||||
|
GHRateLimit.Record unknown1 = GHRateLimit.UnknownLimitRecord.current();
|
||||||
|
|
||||||
|
assertThat("Valid unknown should not replace an existing one, regardless of created or reset time",
|
||||||
|
unknown1.currentOrUpdated(unknown0),
|
||||||
|
sameInstance(unknown1));
|
||||||
|
assertThat("Valid unknown should not replace an existing one, regardless of created or reset time",
|
||||||
|
unknown0.currentOrUpdated(unknown1),
|
||||||
|
sameInstance(unknown0));
|
||||||
|
|
||||||
|
// Sleep to make different created time
|
||||||
|
Thread.sleep(1500);
|
||||||
|
|
||||||
|
// To reduce object creation: There is only one valid Unknown record at a time.
|
||||||
|
assertThat("Unknown current should should limit the creation of new unknown records",
|
||||||
|
unknown1,
|
||||||
|
sameInstance(GHRateLimit.UnknownLimitRecord.current()));
|
||||||
|
|
||||||
|
long epochSeconds = Instant.now().getEpochSecond();
|
||||||
|
|
||||||
|
GHRateLimit.Record record0 = new GHRateLimit.Record(10, 10, epochSeconds + 10L);
|
||||||
|
GHRateLimit.Record record1 = new GHRateLimit.Record(10, 9, epochSeconds + 10L);
|
||||||
|
GHRateLimit.Record record2 = new GHRateLimit.Record(10, 2, epochSeconds + 10L);
|
||||||
|
GHRateLimit.Record record3 = new GHRateLimit.Record(10, 10, epochSeconds + 20L);
|
||||||
|
GHRateLimit.Record record4 = new GHRateLimit.Record(10, 5, epochSeconds + 20L);
|
||||||
|
GHRateLimit.Record recordExpired0 = new GHRateLimit.Record(10, 10, epochSeconds - 1L);
|
||||||
|
GHRateLimit.Record recordExpired1 = new GHRateLimit.Record(10, 10, epochSeconds + 2L);
|
||||||
|
|
||||||
|
// Sleep to make expired and different created time
|
||||||
|
Thread.sleep(4000);
|
||||||
|
|
||||||
GHRateLimit.Record recordWorst = new GHRateLimit.Record(Integer.MAX_VALUE, Integer.MAX_VALUE, Long.MIN_VALUE);
|
GHRateLimit.Record recordWorst = new GHRateLimit.Record(Integer.MAX_VALUE, Integer.MAX_VALUE, Long.MIN_VALUE);
|
||||||
GHRateLimit.Record record00 = new GHRateLimit.Record(10, 10, 10L);
|
GHRateLimit.Record record00 = new GHRateLimit.Record(10, 10, epochSeconds + 10L);
|
||||||
GHRateLimit.Record unknown2 = GHRateLimit.Unknown().getCore();
|
|
||||||
|
GHRateLimit.Record unknownExpired0 = unknown0;
|
||||||
|
GHRateLimit.Record unknownExpired1 = unknown1;
|
||||||
|
unknown0 = GHRateLimit.UnknownLimitRecord.current();
|
||||||
|
|
||||||
// Rate-limit records maybe created and returned in different orders.
|
// Rate-limit records maybe created and returned in different orders.
|
||||||
// We should update to the regular records over unknowns.
|
// We should update to the unexpired regular records over unknowns.
|
||||||
// After that, we should update to the candidate if its limit is lower or its reset is later.
|
// After that, we should update to the candidate if its limit is lower or its reset is later.
|
||||||
|
|
||||||
assertThat("Equivalent unknown should not replace", GitHubClient.shouldReplace(unknown0, unknown1), is(false));
|
assertThat("Expired unknowns should not replace another expired one, regardless of created or reset time",
|
||||||
assertThat("Equivalent unknown should not replace", GitHubClient.shouldReplace(unknown1, unknown0), is(false));
|
unknownExpired0.currentOrUpdated(unknownExpired1),
|
||||||
|
sameInstance(unknownExpired0));
|
||||||
|
assertThat("Expired unknowns should not replace another expired one, regardless of created or reset time",
|
||||||
|
unknownExpired1.currentOrUpdated(unknownExpired0),
|
||||||
|
sameInstance(unknownExpired1));
|
||||||
|
|
||||||
assertThat("Later unknown should replace earlier", GitHubClient.shouldReplace(unknown2, unknown0), is(true));
|
assertThat("Expired unknown should not be replaced by expired earlier normal record",
|
||||||
assertThat("Earlier unknown should not replace later",
|
unknownExpired0.currentOrUpdated(recordExpired0),
|
||||||
GitHubClient.shouldReplace(unknown0, unknown2),
|
sameInstance(unknownExpired0));
|
||||||
is(false));
|
assertThat("Expired normal record should not be replaced an expired earlier unknown record",
|
||||||
|
recordExpired0.currentOrUpdated(unknownExpired0),
|
||||||
|
sameInstance(recordExpired0));
|
||||||
|
|
||||||
assertThat("Worst record should replace later unknown",
|
assertThat("Expired unknown should be replaced by expired later normal record",
|
||||||
GitHubClient.shouldReplace(recordWorst, unknown1),
|
unknownExpired0.currentOrUpdated(recordExpired1),
|
||||||
is(true));
|
sameInstance(recordExpired1));
|
||||||
assertThat("Unknown should not replace worst record",
|
assertThat(
|
||||||
GitHubClient.shouldReplace(unknown1, recordWorst),
|
"Expired later normal record should not be replaced an expired unknown record, regardless of created or reset time",
|
||||||
is(false));
|
recordExpired1.currentOrUpdated(unknownExpired0),
|
||||||
|
sameInstance(recordExpired1));
|
||||||
|
|
||||||
assertThat("Earlier record should replace later worst",
|
assertThat("Valid unknown should not be replaced by an expired unknown",
|
||||||
GitHubClient.shouldReplace(record0, recordWorst),
|
unknown0.currentOrUpdated(unknownExpired0),
|
||||||
is(true));
|
sameInstance(unknown0));
|
||||||
|
assertThat("Expired unknown should be replaced by valid unknown",
|
||||||
|
unknownExpired0.currentOrUpdated(unknown0),
|
||||||
|
sameInstance(unknown0));
|
||||||
|
|
||||||
|
assertThat("Valid unknown should replace an expired normal record",
|
||||||
|
recordExpired1.currentOrUpdated(unknown0),
|
||||||
|
sameInstance(unknown0));
|
||||||
|
assertThat("Valid unknown record should not be replaced by expired normal record",
|
||||||
|
unknown0.currentOrUpdated(recordExpired1),
|
||||||
|
sameInstance(unknown0));
|
||||||
|
|
||||||
|
// In normal comparision, expiration doesn't matter
|
||||||
|
assertThat("Expired normal should not be replaced by an earlier expired one",
|
||||||
|
recordExpired1.currentOrUpdated(recordExpired0),
|
||||||
|
sameInstance(recordExpired1));
|
||||||
|
assertThat("Expired normal should be replaced by a later expired one",
|
||||||
|
recordExpired0.currentOrUpdated(recordExpired1),
|
||||||
|
sameInstance(recordExpired1));
|
||||||
|
|
||||||
|
assertThat("Later worst record should be replaced by earlier record",
|
||||||
|
recordWorst.currentOrUpdated(record0),
|
||||||
|
sameInstance(record0));
|
||||||
assertThat("Later worst record should not replace earlier",
|
assertThat("Later worst record should not replace earlier",
|
||||||
GitHubClient.shouldReplace(recordWorst, record0),
|
record0.currentOrUpdated(recordWorst),
|
||||||
is(false));
|
sameInstance(record0));
|
||||||
|
|
||||||
assertThat("Equivalent record should not replace", GitHubClient.shouldReplace(record0, record00), is(false));
|
assertThat("Equivalent record should not replace other",
|
||||||
assertThat("Equivalent record should not replace", GitHubClient.shouldReplace(record00, record0), is(false));
|
record00.currentOrUpdated(record0),
|
||||||
|
sameInstance(record00));
|
||||||
|
assertThat("Equivalent record should not replace other",
|
||||||
|
record0.currentOrUpdated(record00),
|
||||||
|
sameInstance(record0));
|
||||||
|
|
||||||
assertThat("Lower limit record should replace higher", GitHubClient.shouldReplace(record1, record0), is(true));
|
assertThat("Higher limit record should be replaced by lower",
|
||||||
assertThat("Lower limit record should replace higher", GitHubClient.shouldReplace(record2, record1), is(true));
|
record0.currentOrUpdated(record1),
|
||||||
|
sameInstance(record1));
|
||||||
|
assertThat("Higher limit record should be replaced by lower",
|
||||||
|
record1.currentOrUpdated(record2),
|
||||||
|
sameInstance(record2));
|
||||||
|
|
||||||
assertThat("Higher limit record should not replace lower",
|
assertThat("Lower limit record should not be replaced higher",
|
||||||
GitHubClient.shouldReplace(record1, record2),
|
record2.currentOrUpdated(record1),
|
||||||
is(false));
|
sameInstance(record2));
|
||||||
|
|
||||||
assertThat("Higher limit record with later reset should replace lower",
|
assertThat("Lower limit record should be replaced by higher limit record with later reset",
|
||||||
GitHubClient.shouldReplace(record3, record2),
|
record2.currentOrUpdated(record3),
|
||||||
is(true));
|
sameInstance(record3));
|
||||||
|
|
||||||
assertThat("Lower limit record with later reset should replace higher",
|
assertThat("Higher limit record should be replaced by lower limit record with later reset",
|
||||||
GitHubClient.shouldReplace(record4, record1),
|
record1.currentOrUpdated(record4),
|
||||||
is(true));
|
sameInstance(record4));
|
||||||
|
|
||||||
assertThat("Lower limit record with earlier reset should not replace higher",
|
assertThat("Higher limit record should not be replaced by lower limit record with earlier reset",
|
||||||
GitHubClient.shouldReplace(record2, record4),
|
record4.currentOrUpdated(record2),
|
||||||
is(false));
|
sameInstance(record4));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,8 +242,12 @@ public class GitHubStaticTest extends AbstractGitHubWireMockTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static String formatDate(Date dt, String format) {
|
static String formatDate(Date dt, String format) {
|
||||||
|
return formatZonedDate(dt, format, "GMT");
|
||||||
|
}
|
||||||
|
|
||||||
|
static String formatZonedDate(Date dt, String format, String timeZone) {
|
||||||
SimpleDateFormat df = new SimpleDateFormat(format);
|
SimpleDateFormat df = new SimpleDateFormat(format);
|
||||||
df.setTimeZone(TimeZone.getTimeZone("GMT"));
|
df.setTimeZone(TimeZone.getTimeZone(timeZone));
|
||||||
return df.format(dt);
|
return df.format(dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
|
import java.io.StringReader;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Stephen Connolly
|
* @author Stephen Connolly
|
||||||
@@ -79,6 +83,11 @@ public class PayloadRule implements TestRule {
|
|||||||
return new InputStreamReader(asInputStream(), Charset.defaultCharset());
|
return new InputStreamReader(asInputStream(), Charset.defaultCharset());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Reader asReader(@Nonnull Function<String, String> transformer) throws IOException {
|
||||||
|
String payloadString = asString();
|
||||||
|
return new StringReader(transformer.apply(payloadString));
|
||||||
|
}
|
||||||
|
|
||||||
public Reader asReader(String encoding) throws IOException {
|
public Reader asReader(String encoding) throws IOException {
|
||||||
return new InputStreamReader(asInputStream(), encoding);
|
return new InputStreamReader(asInputStream(), encoding);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user