mirror of
https://github.com/jlengrand/github-api.git
synced 2026-03-11 00:11:25 +00:00
Compare commits
499 Commits
github-api
...
github-api
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e92f1321d4 | ||
|
|
208904b634 | ||
|
|
a433bcda2e | ||
|
|
77baafa643 | ||
|
|
224d8c7cb4 | ||
|
|
0feb520549 | ||
|
|
ca365b12f6 | ||
|
|
bde6ad9a06 | ||
|
|
4953f4500d | ||
|
|
4415ac8fd2 | ||
|
|
8c81e48a31 | ||
|
|
9ad0329c56 | ||
|
|
78f533bbfc | ||
|
|
79c7dd9ecf | ||
|
|
5d796d1f79 | ||
|
|
68a82be6c4 | ||
|
|
2676ef2b73 | ||
|
|
04b283c539 | ||
|
|
98b067937a | ||
|
|
8ababb60bf | ||
|
|
b51d655f77 | ||
|
|
74496d32da | ||
|
|
316e278be1 | ||
|
|
d881bf6504 | ||
|
|
c74fbbe1fd | ||
|
|
929d9fb7bd | ||
|
|
5d069d0531 | ||
|
|
dd9e6dc5d3 | ||
|
|
d22c77c41d | ||
|
|
3a11b7ccbf | ||
|
|
9d161b28bb | ||
|
|
9b16a1caa0 | ||
|
|
9a918e3bac | ||
|
|
d4c5c6a1e0 | ||
|
|
63fda3555c | ||
|
|
6a2381c06b | ||
|
|
e9c0a16c26 | ||
|
|
2101a67ac1 | ||
|
|
ddac568aaa | ||
|
|
262ae9f635 | ||
|
|
381502fb80 | ||
|
|
92fb441eb2 | ||
|
|
29e08037a8 | ||
|
|
84cc6d9315 | ||
|
|
b8d5a1c732 | ||
|
|
0197ab9661 | ||
|
|
b7915e61a6 | ||
|
|
586db99450 | ||
|
|
5377d0dd18 | ||
|
|
c5d3a7d573 | ||
|
|
8267050f06 | ||
|
|
c21bd5765a | ||
|
|
b78c37a695 | ||
|
|
2f151d45c3 | ||
|
|
3ebe3afdbd | ||
|
|
f4845df6c0 | ||
|
|
272b87f04d | ||
|
|
6e3f754366 | ||
|
|
6360112432 | ||
|
|
f1ca0b5417 | ||
|
|
0894c8007c | ||
|
|
0456f10709 | ||
|
|
b7d03f7463 | ||
|
|
07a392c2a7 | ||
|
|
5b69de770f | ||
|
|
bf67069768 | ||
|
|
91764c1c74 | ||
|
|
8b2a3e1221 | ||
|
|
def2f0b37d | ||
|
|
5d7479a3dd | ||
|
|
ceb2d35f9f | ||
|
|
fc38dba59a | ||
|
|
75b383d398 | ||
|
|
ee2d9491fb | ||
|
|
bf86a7c75a | ||
|
|
70f6d129e2 | ||
|
|
a4ac2aa99a | ||
|
|
ae3b6fbe6b | ||
|
|
e357fca963 | ||
|
|
c84cc89805 | ||
|
|
181238cd50 | ||
|
|
214c24c736 | ||
|
|
cf51ce8f26 | ||
|
|
2b7ed40d01 | ||
|
|
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 | ||
|
|
2321dc50c5 | ||
|
|
4efd2e8184 | ||
|
|
e30e153bfa | ||
|
|
2724211535 | ||
|
|
81068de0f1 | ||
|
|
7d842175f7 | ||
|
|
e0aee9f361 | ||
|
|
df576e2738 | ||
|
|
bb1356b25d | ||
|
|
1b67960da4 | ||
|
|
d76718e8b2 | ||
|
|
76c51922f1 | ||
|
|
f95e89a136 | ||
|
|
2dff60a23c | ||
|
|
95f83d1a29 | ||
|
|
b875ccecc1 | ||
|
|
e4c3802f16 | ||
|
|
081e485f4f | ||
|
|
4adf88da19 | ||
|
|
31e2b1b8d3 | ||
|
|
bd28abd343 | ||
|
|
955690b124 | ||
|
|
fa6f06ae15 | ||
|
|
263de140c5 | ||
|
|
ed85d06d69 | ||
|
|
4ff0870df8 | ||
|
|
410bac2040 | ||
|
|
38b1e367b1 | ||
|
|
3cddffa37f | ||
|
|
ea7a1a7175 | ||
|
|
36b5601588 | ||
|
|
7fc68f2969 | ||
|
|
c5ee07add4 | ||
|
|
32ff315b6b | ||
|
|
f919346f8f | ||
|
|
279df00404 | ||
|
|
bfd4b17fa0 | ||
|
|
5fe2817164 | ||
|
|
b337bb39bc | ||
|
|
65ae41c5f1 | ||
|
|
796c644c4a | ||
|
|
bfd9023a27 | ||
|
|
c9cdf5d03e | ||
|
|
f60bb41ad9 | ||
|
|
c333903b4a | ||
|
|
dd55e8a22c | ||
|
|
3ab9381d0a | ||
|
|
58f1fe0671 | ||
|
|
82b9c05d0f | ||
|
|
7c9397f7f6 | ||
|
|
6214b6a3ff | ||
|
|
883c8cc4c8 | ||
|
|
8d47c72913 | ||
|
|
89a6664e45 | ||
|
|
30d792d6e1 | ||
|
|
3745bf3157 | ||
|
|
a7fda3e50d | ||
|
|
7f07204fef | ||
|
|
8b51a44b7c | ||
|
|
c499c73dcc | ||
|
|
c01f3f5e8a | ||
|
|
2aef35655f | ||
|
|
7ddf1f5830 | ||
|
|
b2c513ea42 | ||
|
|
4c30f94355 | ||
|
|
e911e86c4c | ||
|
|
ca640b3f64 | ||
|
|
b4c4a05f3b | ||
|
|
fd3c36a259 | ||
|
|
d8274ac2d4 | ||
|
|
9c7479f953 | ||
|
|
b5dc3c4366 | ||
|
|
26b8082155 | ||
|
|
418df15f7b | ||
|
|
31ed0125b8 | ||
|
|
494318b879 | ||
|
|
f554ddc372 | ||
|
|
03de12c221 | ||
|
|
6c41f22b57 | ||
|
|
7ae96388e3 | ||
|
|
e8b4de00d2 | ||
|
|
cd7963b30d | ||
|
|
0dc44cffcf | ||
|
|
7a650132c5 | ||
|
|
c7fb390c38 | ||
|
|
572ff9df19 | ||
|
|
b715e0cef7 | ||
|
|
36ab2a889f | ||
|
|
a78d2f28d7 | ||
|
|
7d5a39ed89 | ||
|
|
772272ff36 | ||
|
|
2ab4eafee9 | ||
|
|
b15e0d4c45 | ||
|
|
b8180314d8 | ||
|
|
fcb8d03a0f | ||
|
|
09ec89bc2e | ||
|
|
863ad0f486 | ||
|
|
79a1bb3571 | ||
|
|
9f1d7323c7 | ||
|
|
64a82f4785 | ||
|
|
f37e4bd76e | ||
|
|
98ef2cc640 | ||
|
|
134222fd69 | ||
|
|
0cb2371517 | ||
|
|
b7de4359fd | ||
|
|
2607d6a107 | ||
|
|
db46b1ce13 | ||
|
|
d7b08d5207 | ||
|
|
29fbba832c | ||
|
|
fd621a442a | ||
|
|
a1a73568ae | ||
|
|
3daccbd6ec | ||
|
|
293deadb48 | ||
|
|
452b56c47b | ||
|
|
5cb6bfa633 | ||
|
|
0515cee6f3 | ||
|
|
4247112539 | ||
|
|
8d3374f574 | ||
|
|
26833e5f7c | ||
|
|
6752b46f67 | ||
|
|
b9429ffcaa | ||
|
|
10827c7e21 | ||
|
|
23cb4a34a4 | ||
|
|
adfd09565f | ||
|
|
78b9ff49d4 | ||
|
|
fca425d25e | ||
|
|
1a4238156c | ||
|
|
f6210cc014 | ||
|
|
6c8b466e59 | ||
|
|
2aebe97f9f | ||
|
|
157724bff8 | ||
|
|
6cbb1a0bee | ||
|
|
960a13dd38 | ||
|
|
9213f80435 | ||
|
|
bccae94c7a | ||
|
|
d71f77ce06 | ||
|
|
2787f3dc71 | ||
|
|
fb00baab5b | ||
|
|
9e22155d31 | ||
|
|
963373435d | ||
|
|
377987fa92 | ||
|
|
0b6980639e | ||
|
|
4f1cc9f94f | ||
|
|
6e5434a0ec | ||
|
|
3244f7c38f | ||
|
|
f27b676e89 | ||
|
|
4f2a80a4a3 | ||
|
|
a51bc27829 | ||
|
|
4fd321c93d | ||
|
|
bbd62bdef5 | ||
|
|
4bb1d78939 | ||
|
|
53c37ef413 | ||
|
|
a6511b6c5a | ||
|
|
829e96a2d0 | ||
|
|
2e25f37433 | ||
|
|
fbf6c73226 | ||
|
|
aab54e3f23 | ||
|
|
a6eff7fbfb |
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -7,4 +7,10 @@ We love getting PRs, but we hate asking people for the same basic changes every
|
||||
- [ ] Push your changes to a branch other than `master`. Create your PR from that branch.
|
||||
- [ ] Add JavaDocs and other comments
|
||||
- [ ] Write tests that run and pass in CI. See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to capture snapshot data.
|
||||
- [ ] Run `mvn -D enable-ci clean install site` locally. This may reformat your code, commit those changes. If this command doesn't succeed, your change will not pass CI.
|
||||
- [ ] 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.
|
||||
|
||||
# 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: "monthly"
|
||||
time: "02:00"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
time: "02:00"
|
||||
5
.github/release-drafter.yml
vendored
5
.github/release-drafter.yml
vendored
@@ -1,5 +1,6 @@
|
||||
name-template: 'v$NEXT_PATCH_VERSION 🌈'
|
||||
tag-template: 'v$NEXT_PATCH_VERSION'
|
||||
name-template: 'v$NEXT_MINOR_VERSION 🌈'
|
||||
tag-template: 'github-api-$NEXT_MINOR_VERSION'
|
||||
version-template: '$MAJOR.$MINOR'
|
||||
categories:
|
||||
- title: '🚀 Features'
|
||||
labels:
|
||||
|
||||
12
.github/workflows/maven-build.yml
vendored
12
.github/workflows/maven-build.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
java: [ 11 ]
|
||||
java: [ 13 ]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
with:
|
||||
java-version: ${{ matrix.java }}
|
||||
- name: Cached .m2
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
@@ -30,14 +30,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
java: [ 11 ]
|
||||
java: [ 8, 11 ]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: ${{ matrix.java }}
|
||||
- uses: actions/cache@v1
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
@@ -51,14 +51,14 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu, windows ]
|
||||
java: [ 8, 11, 13 ]
|
||||
java: [ 8, 11, 13, 15-ea ]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: ${{ matrix.java }}
|
||||
- uses: actions/cache@v1
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
|
||||
1326
CHANGELOG.md
1326
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,9 @@
|
||||
# Java API for GitHub
|
||||
|
||||
[](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)
|
||||

|
||||
|
||||
|
||||
|
||||
See https://github-api.kohsuke.org/ for more details
|
||||
|
||||
116
pom.xml
116
pom.xml
@@ -2,16 +2,16 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.kohsuke</groupId>
|
||||
<artifactId>github-api</artifactId>
|
||||
<version>1.108</version>
|
||||
<version>1.117</version>
|
||||
<name>GitHub API for Java</name>
|
||||
<url>https://github-api.kohsuke.org/</url>
|
||||
<description>GitHub API for Java</description>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:git@github.com/github-api/${project.artifactId}.git</connection>
|
||||
<developerConnection>scm:git:ssh://git@github.com/github-api/${project.artifactId}.git</developerConnection>
|
||||
<url>https://github.com/github-api/github-api/</url>
|
||||
<tag>github-api-1.108</tag>
|
||||
<connection>scm:git:git@github.com/hub4j/${project.artifactId}.git</connection>
|
||||
<developerConnection>scm:git:ssh://git@github.com/hub4j/${project.artifactId}.git</developerConnection>
|
||||
<url>https://github.com/hub4j/github-api/</url>
|
||||
<tag>github-api-1.117</tag>
|
||||
</scm>
|
||||
|
||||
<distributionManagement>
|
||||
@@ -27,18 +27,18 @@
|
||||
</repository>
|
||||
<site>
|
||||
<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>
|
||||
</distributionManagement>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<spotbugs-maven-plugin.version>3.1.12.2</spotbugs-maven-plugin.version>
|
||||
<spotbugs.version>4.0.0</spotbugs.version>
|
||||
<spotbugs-maven-plugin.version>4.0.4</spotbugs-maven-plugin.version>
|
||||
<spotbugs.version>4.1.2</spotbugs.version>
|
||||
<spotbugs-maven-plugin.failOnError>true</spotbugs-maven-plugin.failOnError>
|
||||
<hamcrest.version>2.2</hamcrest.version>
|
||||
<okhttp3.version>4.4.0</okhttp3.version>
|
||||
<okio.version>2.4.3</okio.version>
|
||||
<okhttp3.version>4.4.1</okhttp3.version>
|
||||
<okio.version>2.5.0</okio.version>
|
||||
<formatter-maven-plugin.goal>format</formatter-maven-plugin.goal>
|
||||
<impsort-maven-plugin.goal>sort</impsort-maven-plugin.goal>
|
||||
<!-- Using this as the minimum bar for code coverage. Adding methods without covering them will fail this. -->
|
||||
@@ -79,6 +79,14 @@
|
||||
</testResources>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
<configuration>
|
||||
<!-- SUREFIRE-1226 workaround -->
|
||||
<trimStackTrace>false</trimStackTrace>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
@@ -92,7 +100,7 @@
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.8.5</version>
|
||||
<version>0.8.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
@@ -209,7 +217,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<version>3.2.0</version>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<failOnWarnings>true</failOnWarnings>
|
||||
@@ -233,7 +241,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-site-plugin</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.9.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@@ -253,12 +261,12 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-project-info-reports-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<version>3.1.0</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.bcel</groupId>
|
||||
<artifactId>bcel</artifactId>
|
||||
<version>6.4.1</version>
|
||||
<version>6.5.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
@@ -280,12 +288,31 @@
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-test</id>
|
||||
<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>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>animal-sniffer-maven-plugin</artifactId>
|
||||
<version>1.18</version>
|
||||
<version>1.19</version>
|
||||
<configuration>
|
||||
<signature>
|
||||
<groupId>org.codehaus.mojo.signature</groupId>
|
||||
@@ -318,7 +345,7 @@
|
||||
<plugin>
|
||||
<groupId>net.revelc.code.formatter</groupId>
|
||||
<artifactId>formatter-maven-plugin</artifactId>
|
||||
<version>2.11.0</version>
|
||||
<version>2.12.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
@@ -326,6 +353,7 @@
|
||||
</goals>
|
||||
<configuration>
|
||||
<configFile>src/main/resources/eclipse/formatter.xml</configFile>
|
||||
<cachedir>${project.build.directory}/.cache</cachedir>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
@@ -333,7 +361,7 @@
|
||||
<plugin>
|
||||
<groupId>net.revelc.code</groupId>
|
||||
<artifactId>impsort-maven-plugin</artifactId>
|
||||
<version>1.3.2</version>
|
||||
<version>1.4.1</version>
|
||||
<configuration>
|
||||
<groups>*,java.,javax.</groups>
|
||||
<removeUnused>true</removeUnused>
|
||||
@@ -404,7 +432,7 @@
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13</version>
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -441,7 +469,7 @@
|
||||
<dependency>
|
||||
<groupId>org.kohsuke.stapler</groupId>
|
||||
<artifactId>stapler</artifactId>
|
||||
<version>1.259</version>
|
||||
<version>1.260</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -453,7 +481,25 @@
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jgit</groupId>
|
||||
<artifactId>org.eclipse.jgit</artifactId>
|
||||
<version>5.6.1.202002131546-r</version>
|
||||
<version>5.9.0.202009080501-r</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>0.11.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>0.11.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>0.11.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -491,7 +537,7 @@
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>3.3.1</version>
|
||||
<version>3.6.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -503,7 +549,7 @@
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock-jre8-standalone</artifactId>
|
||||
<version>2.26.2</version>
|
||||
<version>2.27.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -512,6 +558,12 @@
|
||||
<version>2.8.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>1.7.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<repositories>
|
||||
<repository>
|
||||
@@ -568,6 +620,10 @@
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
@@ -617,6 +673,18 @@
|
||||
</profiles>
|
||||
<reporting>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<reportSets>
|
||||
<reportSet>
|
||||
<reports>
|
||||
<!-- select non-aggregate reports -->
|
||||
<report>report</report>
|
||||
</reports>
|
||||
</reportSet>
|
||||
</reportSets>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
|
||||
165
src/main/java/org/kohsuke/github/AbstractBuilder.java
Normal file
165
src/main/java/org/kohsuke/github/AbstractBuilder.java
Normal file
@@ -0,0 +1,165 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.annotation.CheckForNull;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* An abstract data object builder/updater.
|
||||
*
|
||||
* This class can be use to make a Builder that supports both batch and single property changes.
|
||||
* <p>
|
||||
* Batching looks like this:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* update().someName(value).otherName(value).done()
|
||||
* </pre>
|
||||
* <p>
|
||||
* Single changes look like this:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* set().someName(value);
|
||||
* set().otherName(value);
|
||||
* </pre>
|
||||
* <p>
|
||||
* If {@link S} is the same as {@link R}, {@link #with(String, Object)} will commit changes after the first value change
|
||||
* and return a {@link R} from {@link #done()}.
|
||||
* </p>
|
||||
* <p>
|
||||
* If {@link S} is not the same as {@link R}, {@link #with(String, Object)} will batch together multiple changes and let
|
||||
* the user call {@link #done()} when they are ready.
|
||||
*
|
||||
* @param <R>
|
||||
* Final return type built by this builder returned when {@link #done()}} is called.
|
||||
* @param <S>
|
||||
* Intermediate return type for this builder returned by calls to {@link #with(String, Object)}. If {@link S}
|
||||
* the same as {@link R}, this builder will commit changes after each call to {@link #with(String, Object)}.
|
||||
*/
|
||||
abstract class AbstractBuilder<R, S> {
|
||||
|
||||
@Nonnull
|
||||
private final Class<R> returnType;
|
||||
|
||||
private final boolean commitChangesImmediately;
|
||||
|
||||
@CheckForNull
|
||||
private final R baseInstance;
|
||||
|
||||
@Nonnull
|
||||
protected final Requester requester;
|
||||
|
||||
// TODO: Not sure how update-in-place behavior should be controlled
|
||||
// However, it certainly can be controlled dynamically down to the instance level or inherited for all children of
|
||||
// some
|
||||
// connection.
|
||||
protected boolean updateInPlace;
|
||||
|
||||
/**
|
||||
* Creates a builder.
|
||||
*
|
||||
* @param root
|
||||
* the GitHub instance to connect to.
|
||||
* @param intermediateReturnType
|
||||
* the intermediate return type of type {@link S} returned by calls to {@link #with(String, Object)}.
|
||||
* Must either be equal to {@code builtReturnType} or this instance must be castable to this class. If
|
||||
* not, the constructor will throw {@link IllegalArgumentException}.
|
||||
* @param finalReturnType
|
||||
* the final return type for built by this builder returned when {@link #done()}} is called.
|
||||
* @param baseInstance
|
||||
* optional instance on which to base this builder.
|
||||
*/
|
||||
protected AbstractBuilder(@Nonnull Class<R> finalReturnType,
|
||||
@Nonnull Class<S> intermediateReturnType,
|
||||
@Nonnull GitHub root,
|
||||
@CheckForNull R baseInstance) {
|
||||
this.requester = root.createRequest();
|
||||
this.returnType = finalReturnType;
|
||||
this.commitChangesImmediately = returnType.equals(intermediateReturnType);
|
||||
if (!commitChangesImmediately && !intermediateReturnType.isInstance(this)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Argument \"intermediateReturnType\": This instance must be castable to intermediateReturnType or finalReturnType must be equal to intermediateReturnType.");
|
||||
}
|
||||
|
||||
this.baseInstance = baseInstance;
|
||||
this.updateInPlace = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes an update, committing changes.
|
||||
*
|
||||
* This method may update-in-place or not. Either way it returns the resulting instance.
|
||||
*
|
||||
* @return an instance with updated current data
|
||||
* @throws IOException
|
||||
* if there is an I/O Exception
|
||||
*/
|
||||
@Nonnull
|
||||
@Preview
|
||||
@Deprecated
|
||||
public R done() throws IOException {
|
||||
R result;
|
||||
if (updateInPlace && baseInstance != null) {
|
||||
result = requester.fetchInto(baseInstance);
|
||||
} else {
|
||||
result = requester.fetch(returnType);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a value to a name for this builder.
|
||||
*
|
||||
* If {@link S} is the same as {@link R}, this method will commit changes after the first value change and return a
|
||||
* {@link R} from {@link #done()}.
|
||||
*
|
||||
* If {@link S} is not the same as {@link R}, this method will return an {@link S} and letting the caller batch
|
||||
* together multiple changes and call {@link #done()} when they are ready.
|
||||
*
|
||||
* @param name
|
||||
* the name of the field
|
||||
* @param value
|
||||
* the value of the field
|
||||
* @return either a continuing builder or an updated data record
|
||||
* @throws IOException
|
||||
* if an I/O error occurs
|
||||
*/
|
||||
@Nonnull
|
||||
@Preview
|
||||
@Deprecated
|
||||
protected S with(@Nonnull String name, Object value) throws IOException {
|
||||
requester.with(name, value);
|
||||
return continueOrDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Chooses whether to return a continuing builder or an updated data record
|
||||
*
|
||||
* If {@link S} is the same as {@link R}, this method will commit changes after the first value change and return a
|
||||
* {@link R} from {@link #done()}.
|
||||
*
|
||||
* If {@link S} is not the same as {@link R}, this method will return an {@link S} and letting the caller batch
|
||||
* together multiple changes and call {@link #done()} when they are ready.
|
||||
*
|
||||
* @return either a continuing builder or an updated data record
|
||||
* @throws IOException
|
||||
* if an I/O error occurs
|
||||
*/
|
||||
@Nonnull
|
||||
@Preview
|
||||
@Deprecated
|
||||
protected S continueOrDone() throws IOException {
|
||||
// This little bit of roughness in this base class means all inheriting builders get to create Updater and
|
||||
// Setter classes from almost identical code. Creator can often be implemented with significant code reuse as
|
||||
// well.
|
||||
if (commitChangesImmediately) {
|
||||
// These casts look strange and risky, but they they're actually guaranteed safe due to the return path
|
||||
// being based on the previous comparison of class instances passed to the constructor.
|
||||
return (S) done();
|
||||
} else {
|
||||
return (S) this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,9 @@ public class GHApp extends GHObject {
|
||||
*
|
||||
* @param 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) {
|
||||
this.owner = owner;
|
||||
}
|
||||
@@ -58,7 +60,9 @@ public class GHApp extends GHObject {
|
||||
*
|
||||
* @param 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) {
|
||||
this.name = name;
|
||||
}
|
||||
@@ -77,7 +81,9 @@ public class GHApp extends GHObject {
|
||||
*
|
||||
* @param 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) {
|
||||
this.description = description;
|
||||
}
|
||||
@@ -96,7 +102,9 @@ public class GHApp extends GHObject {
|
||||
*
|
||||
* @param externalUrl
|
||||
* 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) {
|
||||
this.externalUrl = externalUrl;
|
||||
}
|
||||
@@ -115,7 +123,9 @@ public class GHApp extends GHObject {
|
||||
*
|
||||
* @param 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) {
|
||||
this.events = events;
|
||||
}
|
||||
@@ -134,7 +144,9 @@ public class GHApp extends GHObject {
|
||||
*
|
||||
* @param installationsCount
|
||||
* 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) {
|
||||
this.installationsCount = installationsCount;
|
||||
}
|
||||
@@ -157,7 +169,9 @@ public class GHApp extends GHObject {
|
||||
*
|
||||
* @param 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) {
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ package org.kohsuke.github;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.kohsuke.github.Previews.GAMBIT;
|
||||
import static org.kohsuke.github.Previews.MACHINE_MAN;
|
||||
|
||||
/**
|
||||
* A Github App Installation.
|
||||
@@ -59,7 +61,9 @@ public class GHAppInstallation extends GHObject {
|
||||
*
|
||||
* @param 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) {
|
||||
this.root = root;
|
||||
}
|
||||
@@ -78,7 +82,9 @@ public class GHAppInstallation extends GHObject {
|
||||
*
|
||||
* @param 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) {
|
||||
this.account = account;
|
||||
}
|
||||
@@ -97,7 +103,9 @@ public class GHAppInstallation extends GHObject {
|
||||
*
|
||||
* @param accessTokenUrl
|
||||
* 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) {
|
||||
this.accessTokenUrl = accessTokenUrl;
|
||||
}
|
||||
@@ -111,12 +119,44 @@ public class GHAppInstallation extends GHObject {
|
||||
return repositoriesUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* List repositories that this app installation can access.
|
||||
*
|
||||
* @return the paged iterable
|
||||
*/
|
||||
@Preview
|
||||
@Deprecated
|
||||
public PagedSearchIterable<GHRepository> listRepositories() {
|
||||
GitHubRequest request;
|
||||
|
||||
try {
|
||||
request = root.createRequest().withPreview(MACHINE_MAN).withUrlPath("/installation/repositories").build();
|
||||
} catch (MalformedURLException e) {
|
||||
throw new GHException("", e);
|
||||
}
|
||||
|
||||
return new PagedSearchIterable<>(root, request, GHAppInstallationRepositoryResult.class);
|
||||
}
|
||||
|
||||
private static class GHAppInstallationRepositoryResult extends SearchResult<GHRepository> {
|
||||
private GHRepository[] repositories;
|
||||
|
||||
@Override
|
||||
GHRepository[] getItems(GitHub root) {
|
||||
for (GHRepository item : repositories)
|
||||
item.wrap(root);
|
||||
return repositories;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets repositories url.
|
||||
*
|
||||
* @param repositoriesUrl
|
||||
* 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) {
|
||||
this.repositoriesUrl = repositoriesUrl;
|
||||
}
|
||||
@@ -135,7 +175,9 @@ public class GHAppInstallation extends GHObject {
|
||||
*
|
||||
* @param appId
|
||||
* 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) {
|
||||
this.appId = appId;
|
||||
}
|
||||
@@ -154,7 +196,9 @@ public class GHAppInstallation extends GHObject {
|
||||
*
|
||||
* @param targetId
|
||||
* 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) {
|
||||
this.targetId = targetId;
|
||||
}
|
||||
@@ -173,7 +217,9 @@ public class GHAppInstallation extends GHObject {
|
||||
*
|
||||
* @param targetType
|
||||
* 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) {
|
||||
this.targetType = targetType;
|
||||
}
|
||||
@@ -192,7 +238,9 @@ public class GHAppInstallation extends GHObject {
|
||||
*
|
||||
* @param 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) {
|
||||
this.permissions = permissions;
|
||||
}
|
||||
@@ -211,7 +259,9 @@ public class GHAppInstallation extends GHObject {
|
||||
*
|
||||
* @param 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) {
|
||||
this.events = events;
|
||||
}
|
||||
@@ -230,7 +280,9 @@ public class GHAppInstallation extends GHObject {
|
||||
*
|
||||
* @param singleFileName
|
||||
* 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) {
|
||||
this.singleFileName = singleFileName;
|
||||
}
|
||||
@@ -249,7 +301,9 @@ public class GHAppInstallation extends GHObject {
|
||||
*
|
||||
* @param repositorySelection
|
||||
* 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) {
|
||||
this.repositorySelection = repositorySelection;
|
||||
}
|
||||
@@ -274,7 +328,7 @@ public class GHAppInstallation extends GHObject {
|
||||
root.createRequest()
|
||||
.method("DELETE")
|
||||
.withPreview(GAMBIT)
|
||||
.withUrlPath(String.format("/app/installations/%d", id))
|
||||
.withUrlPath(String.format("/app/installations/%d", getId()))
|
||||
.send();
|
||||
}
|
||||
|
||||
@@ -293,7 +347,9 @@ public class GHAppInstallation extends GHObject {
|
||||
@Preview
|
||||
@Deprecated
|
||||
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 +364,6 @@ public class GHAppInstallation extends GHObject {
|
||||
@Preview
|
||||
@Deprecated
|
||||
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
|
||||
* the root
|
||||
* @deprecated Do not use this method. It was added due to incomplete understanding of Jackson binding.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setRoot(GitHub root) {
|
||||
this.root = root;
|
||||
}
|
||||
@@ -56,7 +58,9 @@ public class GHAppInstallationToken {
|
||||
*
|
||||
* @param 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) {
|
||||
this.permissions = permissions;
|
||||
}
|
||||
@@ -75,7 +79,9 @@ public class GHAppInstallationToken {
|
||||
*
|
||||
* @param 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) {
|
||||
this.token = token;
|
||||
}
|
||||
@@ -94,7 +100,9 @@ public class GHAppInstallationToken {
|
||||
*
|
||||
* @param 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) {
|
||||
this.repositories = repositories;
|
||||
}
|
||||
@@ -113,7 +121,9 @@ public class GHAppInstallationToken {
|
||||
*
|
||||
* @param repositorySelection
|
||||
* 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) {
|
||||
this.repositorySelection = repositorySelection;
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ public class GHAsset extends GHObject {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -112,10 +112,12 @@ public class GHAuthorization extends GHObject {
|
||||
* Gets 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")
|
||||
public URL getApiURL() {
|
||||
return GitHubClient.parseURL(url);
|
||||
return getUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,8 @@ import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.CheckForNull;
|
||||
|
||||
/**
|
||||
* A branch in a repository.
|
||||
*
|
||||
@@ -101,7 +103,11 @@ public class GHBranch {
|
||||
* the io exception
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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() {
|
||||
return owner.getApiTailUrl("/branches/" + name);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import static org.kohsuke.github.Previews.ZZZAX;
|
||||
|
||||
/**
|
||||
* The type GHBranchProtection.
|
||||
*
|
||||
* @see <a href="https://docs.github.com/en/rest/reference/repos#get-branch-protection">GitHub Branch Protection</a>
|
||||
*/
|
||||
@SuppressFBWarnings(
|
||||
value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD",
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a check run.
|
||||
@@ -14,6 +19,8 @@ import java.util.Date;
|
||||
@SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD", "URF_UNREAD_FIELD" },
|
||||
justification = "JSON API")
|
||||
public class GHCheckRun extends GHObject {
|
||||
|
||||
@JsonProperty("repository")
|
||||
GHRepository owner;
|
||||
GitHub root;
|
||||
|
||||
@@ -34,7 +41,7 @@ public class GHCheckRun extends GHObject {
|
||||
|
||||
GHCheckRun wrap(GHRepository owner) {
|
||||
this.owner = owner;
|
||||
this.root = owner.root;
|
||||
wrap(owner.root);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -42,7 +49,24 @@ public class GHCheckRun extends GHObject {
|
||||
this.root = root;
|
||||
if (owner != null) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -51,24 +75,39 @@ public class GHCheckRun extends GHObject {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets status of the check run. It can be one of "queue", "in_progress", or "completed"
|
||||
* Gets status of the check run.
|
||||
*
|
||||
* @return Status of the check run
|
||||
* @see Status
|
||||
*/
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public static enum Status {
|
||||
QUEUED, IN_PROGRESS, COMPLETED
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets conclusion of a completed check run. It can be one of "success", "failure", "neutral", "cancelled",
|
||||
* "time_out", or "action_required".
|
||||
* Gets conclusion of a completed check run.
|
||||
*
|
||||
* @return Status of the check run
|
||||
* @see Conclusion
|
||||
*/
|
||||
public String getConclusion() {
|
||||
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 {
|
||||
SUCCESS, FAILURE, NEUTRAL, CANCELLED, TIMED_OUT, ACTION_REQUIRED, SKIPPED
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the custom name of this check run.
|
||||
*
|
||||
@@ -90,15 +129,22 @@ public class GHCheckRun extends GHObject {
|
||||
/**
|
||||
* 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) {
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,4 +289,19 @@ public class GHCheckRun extends GHObject {
|
||||
}
|
||||
}
|
||||
|
||||
public static enum AnnotationLevel {
|
||||
NOTICE, WARNING, FAILURE
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates this check run.
|
||||
*
|
||||
* @return a builder which you should customize, then call {@link GHCheckRunBuilder#create}
|
||||
*/
|
||||
@Preview
|
||||
@Deprecated
|
||||
public @NonNull GHCheckRunBuilder update() {
|
||||
return new GHCheckRunBuilder(owner, getId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
306
src/main/java/org/kohsuke/github/GHCheckRunBuilder.java
Normal file
306
src/main/java/org/kohsuke/github/GHCheckRunBuilder.java
Normal file
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright 2020 CloudBees, Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.kohsuke.github;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Drafts or updates a check run.
|
||||
*
|
||||
* @see GHCheckRun
|
||||
* @see GHRepository#createCheckRun
|
||||
* @see <a href="https://developer.github.com/v3/checks/runs/#create-a-check-run">documentation</a>
|
||||
* @see GHCheckRun#update()
|
||||
* @see <a href="https://developer.github.com/v3/checks/runs/#update-a-check-run">documentation</a>
|
||||
*/
|
||||
@SuppressFBWarnings(value = "URF_UNREAD_FIELD", justification = "Jackson serializes these even without a getter")
|
||||
@Preview
|
||||
@Deprecated
|
||||
public final class GHCheckRunBuilder {
|
||||
|
||||
protected final GHRepository repo;
|
||||
protected final Requester requester;
|
||||
private Output output;
|
||||
private List<Action> actions;
|
||||
|
||||
private GHCheckRunBuilder(GHRepository repo, Requester requester) {
|
||||
this.repo = repo;
|
||||
this.requester = requester;
|
||||
}
|
||||
|
||||
GHCheckRunBuilder(GHRepository repo, String name, String headSHA) {
|
||||
this(repo,
|
||||
repo.root.createRequest()
|
||||
.withPreview(Previews.ANTIOPE)
|
||||
.method("POST")
|
||||
.with("name", name)
|
||||
.with("head_sha", headSHA)
|
||||
.withUrlPath(repo.getApiTailUrl("check-runs")));
|
||||
}
|
||||
|
||||
GHCheckRunBuilder(GHRepository repo, long checkId) {
|
||||
this(repo,
|
||||
repo.root.createRequest()
|
||||
.withPreview(Previews.ANTIOPE)
|
||||
.method("PATCH")
|
||||
.withUrlPath(repo.getApiTailUrl("check-runs/" + checkId)));
|
||||
}
|
||||
|
||||
public @NonNull GHCheckRunBuilder withDetailsURL(@CheckForNull String detailsURL) {
|
||||
requester.with("details_url", detailsURL);
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull GHCheckRunBuilder withExternalID(@CheckForNull String externalID) {
|
||||
requester.with("external_id", externalID);
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull GHCheckRunBuilder withStatus(@CheckForNull GHCheckRun.Status status) {
|
||||
if (status != null) {
|
||||
// Do *not* use the overload taking Enum, as that s/_/-/g which would be wrong here.
|
||||
requester.with("status", status.toString().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull GHCheckRunBuilder withConclusion(@CheckForNull GHCheckRun.Conclusion conclusion) {
|
||||
if (conclusion != null) {
|
||||
requester.with("conclusion", conclusion.toString().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull GHCheckRunBuilder withStartedAt(@CheckForNull Date startedAt) {
|
||||
if (startedAt != null) {
|
||||
requester.with("started_at", GitHubClient.printDate(startedAt));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull GHCheckRunBuilder withCompletedAt(@CheckForNull Date completedAt) {
|
||||
if (completedAt != null) {
|
||||
requester.with("completed_at", GitHubClient.printDate(completedAt));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull GHCheckRunBuilder add(@NonNull Output output) {
|
||||
if (this.output != null) {
|
||||
throw new IllegalStateException("cannot add Output twice");
|
||||
}
|
||||
this.output = output;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull GHCheckRunBuilder add(@NonNull Action action) {
|
||||
if (actions == null) {
|
||||
actions = new LinkedList<>();
|
||||
}
|
||||
actions.add(action);
|
||||
return this;
|
||||
}
|
||||
|
||||
private static final int MAX_ANNOTATIONS = 50;
|
||||
/**
|
||||
* Actually creates the check run. (If more than fifty annotations were requested, this is done in batches.)
|
||||
*
|
||||
* @return the resulting run
|
||||
* @throws IOException
|
||||
* for the usual reasons
|
||||
*/
|
||||
public @NonNull GHCheckRun create() throws IOException {
|
||||
List<Annotation> extraAnnotations;
|
||||
if (output != null && output.annotations != null && output.annotations.size() > MAX_ANNOTATIONS) {
|
||||
extraAnnotations = output.annotations.subList(MAX_ANNOTATIONS, output.annotations.size());
|
||||
output.annotations = output.annotations.subList(0, MAX_ANNOTATIONS);
|
||||
} else {
|
||||
extraAnnotations = Collections.emptyList();
|
||||
}
|
||||
GHCheckRun run = requester.with("output", output).with("actions", actions).fetch(GHCheckRun.class).wrap(repo);
|
||||
while (!extraAnnotations.isEmpty()) {
|
||||
Output output2 = new Output(output.title, output.summary);
|
||||
int i = Math.min(extraAnnotations.size(), MAX_ANNOTATIONS);
|
||||
output2.annotations = extraAnnotations.subList(0, i);
|
||||
extraAnnotations = extraAnnotations.subList(i, extraAnnotations.size());
|
||||
run = repo.root.createRequest()
|
||||
.withPreview(Previews.ANTIOPE)
|
||||
.method("PATCH")
|
||||
.with("output", output2)
|
||||
.withUrlPath(repo.getApiTailUrl("check-runs/" + run.getId()))
|
||||
.fetch(GHCheckRun.class)
|
||||
.wrap(repo);
|
||||
}
|
||||
return run;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see <a href="https://developer.github.com/v3/checks/runs/#output-object">documentation</a>
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public static final class Output {
|
||||
|
||||
private final String title;
|
||||
private final String summary;
|
||||
private String text;
|
||||
private List<Annotation> annotations;
|
||||
private List<Image> images;
|
||||
|
||||
public Output(@NonNull String title, @NonNull String summary) {
|
||||
this.title = title;
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
public @NonNull Output withText(@CheckForNull String text) {
|
||||
this.text = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull Output add(@NonNull Annotation annotation) {
|
||||
if (annotations == null) {
|
||||
annotations = new LinkedList<>();
|
||||
}
|
||||
annotations.add(annotation);
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull Output add(@NonNull Image image) {
|
||||
if (images == null) {
|
||||
images = new LinkedList<>();
|
||||
}
|
||||
images.add(image);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @see <a href="https://developer.github.com/v3/checks/runs/#annotations-object">documentation</a>
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public static final class Annotation {
|
||||
|
||||
private final String path;
|
||||
private final int start_line;
|
||||
private final int end_line;
|
||||
private final String annotation_level;
|
||||
private final String message;
|
||||
private Integer start_column;
|
||||
private Integer end_column;
|
||||
private String title;
|
||||
private String raw_details;
|
||||
|
||||
public Annotation(@NonNull String path,
|
||||
int line,
|
||||
@NonNull GHCheckRun.AnnotationLevel annotationLevel,
|
||||
@NonNull String message) {
|
||||
this(path, line, line, annotationLevel, message);
|
||||
}
|
||||
|
||||
public Annotation(@NonNull String path,
|
||||
int startLine,
|
||||
int endLine,
|
||||
@NonNull GHCheckRun.AnnotationLevel annotationLevel,
|
||||
@NonNull String message) {
|
||||
this.path = path;
|
||||
start_line = startLine;
|
||||
end_line = endLine;
|
||||
annotation_level = annotationLevel.toString().toLowerCase(Locale.ROOT);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public @NonNull Annotation withStartColumn(@CheckForNull Integer startColumn) {
|
||||
start_column = startColumn;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull Annotation withEndColumn(@CheckForNull Integer endColumn) {
|
||||
end_column = endColumn;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull Annotation withTitle(@CheckForNull String title) {
|
||||
this.title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull Annotation withRawDetails(@CheckForNull String rawDetails) {
|
||||
raw_details = rawDetails;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @see <a href="https://developer.github.com/v3/checks/runs/#images-object">documentation</a>
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public static final class Image {
|
||||
|
||||
private final String alt;
|
||||
private final String image_url;
|
||||
private String caption;
|
||||
|
||||
public Image(@NonNull String alt, @NonNull String imageURL) {
|
||||
this.alt = alt;
|
||||
image_url = imageURL;
|
||||
}
|
||||
|
||||
public @NonNull Image withCaption(@CheckForNull String caption) {
|
||||
this.caption = caption;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @see <a href="https://developer.github.com/v3/checks/runs/#actions-object">documentation</a>
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public static final class Action {
|
||||
|
||||
private final String label;
|
||||
private final String description;
|
||||
private final String identifier;
|
||||
|
||||
public Action(@NonNull String label, @NonNull String description, @NonNull String identifier) {
|
||||
this.label = label;
|
||||
this.description = description;
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
44
src/main/java/org/kohsuke/github/GHCheckRunsIterable.java
Normal file
44
src/main/java/org/kohsuke/github/GHCheckRunsIterable.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Iterable for check-runs listing.
|
||||
*/
|
||||
class GHCheckRunsIterable extends PagedIterable<GHCheckRun> {
|
||||
private GitHub root;
|
||||
private final GitHubRequest request;
|
||||
|
||||
private GHCheckRunsPage result;
|
||||
|
||||
public GHCheckRunsIterable(GitHub root, GitHubRequest request) {
|
||||
this.root = root;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public PagedIterator<GHCheckRun> _iterator(int pageSize) {
|
||||
return new PagedIterator<>(
|
||||
adapt(GitHubPageIterator.create(root.getClient(), GHCheckRunsPage.class, request, pageSize)),
|
||||
null);
|
||||
}
|
||||
|
||||
protected Iterator<GHCheckRun[]> adapt(final Iterator<GHCheckRunsPage> base) {
|
||||
return new Iterator<GHCheckRun[]>() {
|
||||
public boolean hasNext() {
|
||||
return base.hasNext();
|
||||
}
|
||||
|
||||
public GHCheckRun[] next() {
|
||||
GHCheckRunsPage v = base.next();
|
||||
if (result == null) {
|
||||
result = v;
|
||||
}
|
||||
return v.getCheckRuns(root);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
20
src/main/java/org/kohsuke/github/GHCheckRunsPage.java
Normal file
20
src/main/java/org/kohsuke/github/GHCheckRunsPage.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
/**
|
||||
* Represents the one page of check-runs result when listing check-runs.
|
||||
*/
|
||||
class GHCheckRunsPage {
|
||||
private int total_count;
|
||||
private GHCheckRun[] check_runs;
|
||||
|
||||
public int getTotalCount() {
|
||||
return total_count;
|
||||
}
|
||||
|
||||
GHCheckRun[] getCheckRuns(GitHub root) {
|
||||
for (GHCheckRun check_run : check_runs) {
|
||||
check_run.wrap(root);
|
||||
}
|
||||
return check_runs;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a check suite.
|
||||
@@ -14,6 +18,8 @@ import java.util.Date;
|
||||
@SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD", "URF_UNREAD_FIELD" },
|
||||
justification = "JSON API")
|
||||
public class GHCheckSuite extends GHObject {
|
||||
|
||||
@JsonProperty("repository")
|
||||
GHRepository owner;
|
||||
GitHub root;
|
||||
|
||||
@@ -32,7 +38,7 @@ public class GHCheckSuite extends GHObject {
|
||||
|
||||
GHCheckSuite wrap(GHRepository owner) {
|
||||
this.owner = owner;
|
||||
this.root = owner.root;
|
||||
this.wrap(owner.root);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -40,6 +46,14 @@ public class GHCheckSuite extends GHObject {
|
||||
this.root = root;
|
||||
if (owner != null) {
|
||||
owner.wrap(root);
|
||||
if (pullRequests != null && pullRequests.length != 0) {
|
||||
for (GHPullRequest singlePull : pullRequests) {
|
||||
singlePull.wrap(owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (app != null) {
|
||||
app.wrapUp(root);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -153,15 +167,22 @@ public class GHCheckSuite extends GHObject {
|
||||
/**
|
||||
* 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) {
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,8 @@ import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static org.kohsuke.github.Previews.GROOT;
|
||||
|
||||
/**
|
||||
* A commit in a repository.
|
||||
*
|
||||
@@ -39,6 +41,8 @@ public class GHCommit {
|
||||
|
||||
private int comment_count;
|
||||
|
||||
private GHVerification verification;
|
||||
|
||||
static class Tree {
|
||||
String sha;
|
||||
}
|
||||
@@ -100,6 +104,15 @@ public class GHCommit {
|
||||
public int getCommentCount() {
|
||||
return comment_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Verification Status.
|
||||
*
|
||||
* @return the Verification status
|
||||
*/
|
||||
public GHVerification getVerification() {
|
||||
return verification;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -435,6 +448,39 @@ public class GHCommit {
|
||||
return owner.root.getUser(author.login);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of pull requests which contain this commit.
|
||||
*
|
||||
* @return {@link PagedIterable} with the pull requests which contain this commit
|
||||
*/
|
||||
@Preview
|
||||
@Deprecated
|
||||
public PagedIterable<GHPullRequest> listPullRequests() {
|
||||
return owner.root.createRequest()
|
||||
.withPreview(GROOT)
|
||||
.withUrlPath(String.format("/repos/%s/%s/commits/%s/pulls", owner.getOwnerName(), owner.getName(), sha))
|
||||
.toIterable(GHPullRequest[].class, item -> item.wrapUp(owner));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of branches where this commit is the head commit.
|
||||
*
|
||||
* @return {@link PagedIterable} with the branches where the commit is the head commit
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
*/
|
||||
@Preview
|
||||
@Deprecated
|
||||
public PagedIterable<GHBranch> listBranchesWhereHead() throws IOException {
|
||||
return owner.root.createRequest()
|
||||
.withPreview(GROOT)
|
||||
.withUrlPath(String.format("/repos/%s/%s/commits/%s/branches-where-head",
|
||||
owner.getOwnerName(),
|
||||
owner.getName(),
|
||||
sha))
|
||||
.toIterable(GHBranch[].class, item -> item.wrap(owner));
|
||||
}
|
||||
|
||||
/**
|
||||
* List comments paged iterable.
|
||||
*
|
||||
@@ -512,6 +558,19 @@ public class GHCommit {
|
||||
return owner.getLastCommitStatus(sha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets check-runs for given sha.
|
||||
*
|
||||
* @return check runs for given sha.
|
||||
* @throws IOException
|
||||
* on error
|
||||
*/
|
||||
@Preview
|
||||
@Deprecated
|
||||
public PagedIterable<GHCheckRun> getCheckRuns() throws IOException {
|
||||
return owner.getCheckRuns(sha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Some of the fields are not always filled in when this object is retrieved as a part of another API call.
|
||||
*
|
||||
|
||||
@@ -89,6 +89,19 @@ public class GHCommitBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the PGP signature of this commit.
|
||||
*
|
||||
* @param signature
|
||||
* the signature calculated from the commit
|
||||
*
|
||||
* @return the gh commit builder
|
||||
*/
|
||||
public GHCommitBuilder withSignature(String signature) {
|
||||
req.with("signature", signature);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the committer of this commit.
|
||||
*
|
||||
|
||||
@@ -153,7 +153,7 @@ public class GHCommitComment extends GHObject implements Reactable {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -30,6 +30,7 @@ public class GHContent implements Refreshable {
|
||||
private String sha;
|
||||
private String name;
|
||||
private String path;
|
||||
private String target;
|
||||
private String content;
|
||||
private String url; // this is the API url
|
||||
private String git_url; // this is the Blob url
|
||||
@@ -99,6 +100,15 @@ public class GHContent implements Refreshable {
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -3,6 +3,8 @@ package org.kohsuke.github;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import static org.kohsuke.github.Previews.BAPTISE;
|
||||
|
||||
/**
|
||||
* Creates a repository
|
||||
*
|
||||
@@ -11,7 +13,7 @@ import java.net.URL;
|
||||
public class GHCreateRepositoryBuilder {
|
||||
private final GitHub root;
|
||||
protected final Requester builder;
|
||||
private final String apiUrlTail;
|
||||
private String apiUrlTail;
|
||||
|
||||
GHCreateRepositoryBuilder(GitHub root, String apiUrlTail, String name) {
|
||||
this.root = root;
|
||||
@@ -79,6 +81,18 @@ public class GHCreateRepositoryBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables projects
|
||||
*
|
||||
* @param enabled
|
||||
* true if enabled
|
||||
* @return a builder to continue with building
|
||||
*/
|
||||
public GHCreateRepositoryBuilder projects(boolean enabled) {
|
||||
this.builder.with("has_projects", enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables wiki
|
||||
*
|
||||
@@ -188,6 +202,51 @@ public class GHCreateRepositoryBuilder {
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@@ -68,6 +70,25 @@ public class GHDeployment extends GHObject {
|
||||
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.
|
||||
*
|
||||
@@ -122,7 +143,7 @@ public class GHDeployment extends GHObject {
|
||||
* @return the gh deployment status builder
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@ public enum GHEvent {
|
||||
PULL_REQUEST_REVIEW,
|
||||
PULL_REQUEST_REVIEW_COMMENT,
|
||||
PUSH,
|
||||
REGISTRY_PACKAGE,
|
||||
RELEASE,
|
||||
REPOSITORY_DISPATCH, // only valid for org hooks
|
||||
REPOSITORY,
|
||||
@@ -61,6 +62,8 @@ public enum GHEvent {
|
||||
TEAM,
|
||||
TEAM_ADD,
|
||||
WATCH,
|
||||
WORKFLOW_DISPATCH,
|
||||
WORKFLOW_RUN,
|
||||
|
||||
/**
|
||||
* Special event type that means "every possible event"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -50,6 +50,30 @@ public class GHGist extends GHObject {
|
||||
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.
|
||||
*
|
||||
@@ -97,6 +121,11 @@ public class GHGist extends GHObject {
|
||||
return git_push_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the html url.
|
||||
*
|
||||
* @return the github html url
|
||||
*/
|
||||
public URL getHtmlUrl() {
|
||||
return GitHubClient.parseURL(html_url);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Builder pattern for creating a new Gist.
|
||||
*
|
||||
@@ -59,7 +61,7 @@ public class GHGistBuilder {
|
||||
* the content
|
||||
* @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));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Builder pattern for updating a Gist.
|
||||
@@ -12,7 +15,7 @@ import java.util.LinkedHashMap;
|
||||
public class GHGistUpdater {
|
||||
private final GHGist base;
|
||||
private final Requester builder;
|
||||
LinkedHashMap<String, Object> files;
|
||||
LinkedHashMap<String, Map<String, String>> files;
|
||||
|
||||
GHGistUpdater(GHGist base) {
|
||||
this.base = base;
|
||||
@@ -32,16 +35,15 @@ public class GHGistUpdater {
|
||||
* @throws IOException
|
||||
* 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);
|
||||
return this;
|
||||
}
|
||||
|
||||
// // This method does not work.
|
||||
// public GHGistUpdater deleteFile(String fileName) throws IOException {
|
||||
// files.put(fileName, Collections.singletonMap("filename", null));
|
||||
// return this;
|
||||
// }
|
||||
public GHGistUpdater deleteFile(@Nonnull String fileName) throws IOException {
|
||||
files.put(fileName, null);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename file gh gist updater.
|
||||
@@ -54,8 +56,9 @@ public class GHGistUpdater {
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
*/
|
||||
public GHGistUpdater renameFile(String fileName, String newFileName) throws IOException {
|
||||
files.put(fileName, Collections.singletonMap("filename", newFileName));
|
||||
public GHGistUpdater renameFile(@Nonnull String fileName, @Nonnull String newFileName) throws IOException {
|
||||
Map<String, String> file = files.computeIfAbsent(fileName, d -> new HashMap<>());
|
||||
file.put("filename", newFileName);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -70,8 +73,31 @@ public class GHGistUpdater {
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
*/
|
||||
public GHGistUpdater updateFile(String fileName, String content) throws IOException {
|
||||
files.put(fileName, Collections.singletonMap("content", content));
|
||||
public GHGistUpdater updateFile(@Nonnull String fileName, @Nonnull String content) throws IOException {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.kohsuke.github.Previews.SQUIRREL_GIRL;
|
||||
|
||||
@@ -157,10 +158,8 @@ public class GHIssue extends GHObject implements Reactable {
|
||||
* Gets labels.
|
||||
*
|
||||
* @return the labels
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
*/
|
||||
public Collection<GHLabel> getLabels() throws IOException {
|
||||
public Collection<GHLabel> getLabels() {
|
||||
if (labels == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@@ -179,10 +178,12 @@ public class GHIssue extends GHObject implements Reactable {
|
||||
/**
|
||||
* Gets api url.
|
||||
*
|
||||
* @return the api url
|
||||
* @return API URL of this object.
|
||||
* @deprecated use {@link #getUrl()}
|
||||
*/
|
||||
@Deprecated
|
||||
public URL getApiURL() {
|
||||
return GitHubClient.parseURL(url);
|
||||
return getUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,8 +229,15 @@ public class GHIssue extends GHObject implements Reactable {
|
||||
root.createRequest().with(key, value).method("PATCH").withUrlPath(getApiRoute()).send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Identical to edit(), but allows null for the value.
|
||||
*/
|
||||
private void editNullable(String key, Object value) throws IOException {
|
||||
root.createRequest().withNullable(key, value).method("PATCH").withUrlPath(getApiRoute()).send();
|
||||
}
|
||||
|
||||
private void editIssue(String key, Object value) throws IOException {
|
||||
root.createRequest().with(key, value).method("PATCH").withUrlPath(getIssuesApiRoute()).send();
|
||||
root.createRequest().withNullable(key, value).method("PATCH").withUrlPath(getIssuesApiRoute()).send();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,15 +285,19 @@ public class GHIssue extends GHObject implements Reactable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets milestone.
|
||||
* Sets the milestone for this issue.
|
||||
*
|
||||
* @param milestone
|
||||
* the milestone
|
||||
* The milestone to assign this issue to. Use null to remove the milestone for this issue.
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
* The io exception
|
||||
*/
|
||||
public void setMilestone(GHMilestone milestone) throws IOException {
|
||||
edit("milestone", milestone.getNumber());
|
||||
if (milestone == null) {
|
||||
editIssue("milestone", null);
|
||||
} else {
|
||||
editIssue("milestone", milestone.getNumber());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -559,7 +571,8 @@ public class GHIssue extends GHObject implements Reactable {
|
||||
protected String getIssuesApiRoute() {
|
||||
if (owner == null) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -149,6 +149,6 @@ public class GHIssueComment extends GHObject implements Reactable {
|
||||
|
||||
private String getApiRoute() {
|
||||
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.
|
||||
*
|
||||
* @see <a href="https://developer.github.com/v3/issues/events/">Github documentation for issue events</a>
|
||||
*
|
||||
* @author Martin van Zijl
|
||||
*/
|
||||
public class GHIssueEvent {
|
||||
@@ -18,6 +20,9 @@ public class GHIssueEvent {
|
||||
private String commit_id;
|
||||
private String commit_url;
|
||||
private String created_at;
|
||||
private GHMilestone milestone;
|
||||
private GHLabel label;
|
||||
private GHUser assignee;
|
||||
|
||||
private GHIssue issue;
|
||||
|
||||
@@ -111,6 +116,36 @@ public class GHIssueEvent {
|
||||
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) {
|
||||
this.root = root;
|
||||
return this;
|
||||
|
||||
@@ -1,27 +1,56 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.CheckForNull;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* The type GHLabel.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see <a href="https://developer.github.com/v3/issues/labels/">Labels</a>
|
||||
* @see GHIssue#getLabels() GHIssue#getLabels()
|
||||
* @see GHRepository#listLabels() GHRepository#listLabels()
|
||||
*/
|
||||
public class GHLabel {
|
||||
private String url, name, color, description;
|
||||
private GHRepository repo;
|
||||
|
||||
@Nonnull
|
||||
private String url, name, color;
|
||||
|
||||
@CheckForNull
|
||||
private String description;
|
||||
|
||||
@Nonnull
|
||||
private final GitHub root;
|
||||
|
||||
@JsonCreator
|
||||
private GHLabel(@JacksonInject @Nonnull GitHub root) {
|
||||
this.root = root;
|
||||
url = "";
|
||||
name = "";
|
||||
color = "";
|
||||
description = null;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
GitHub getApiRoot() {
|
||||
return Objects.requireNonNull(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets url.
|
||||
*
|
||||
* @return the url
|
||||
*/
|
||||
@Nonnull
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
@@ -31,6 +60,7 @@ public class GHLabel {
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
@Nonnull
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
@@ -40,6 +70,7 @@ public class GHLabel {
|
||||
*
|
||||
* @return the color
|
||||
*/
|
||||
@Nonnull
|
||||
public String getColor() {
|
||||
return color;
|
||||
}
|
||||
@@ -49,25 +80,11 @@ public class GHLabel {
|
||||
*
|
||||
* @return the description
|
||||
*/
|
||||
@CheckForNull
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
GHLabel wrapUp(GHRepository repo) {
|
||||
this.repo = repo;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete.
|
||||
*
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
repo.root.createRequest().method("DELETE").setRawUrlPath(url).send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets color.
|
||||
*
|
||||
@@ -75,15 +92,11 @@ public class GHLabel {
|
||||
* 6-letter hex color code, like "f29513"
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
* @deprecated use {@link #set()} or {@link #update()} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public void setColor(String newColor) throws IOException {
|
||||
repo.root.createRequest()
|
||||
.method("PATCH")
|
||||
.with("name", name)
|
||||
.with("color", newColor)
|
||||
.with("description", description)
|
||||
.setRawUrlPath(url)
|
||||
.send();
|
||||
set().color(newColor);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,25 +106,106 @@ public class GHLabel {
|
||||
* Description of label
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
* @deprecated use {@link #set()} or {@link #update()} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public void setDescription(String newDescription) throws IOException {
|
||||
repo.root.createRequest()
|
||||
.method("PATCH")
|
||||
.with("name", name)
|
||||
.with("color", color)
|
||||
.with("description", newDescription)
|
||||
.setRawUrlPath(url)
|
||||
.send();
|
||||
set().description(newDescription);
|
||||
}
|
||||
|
||||
static Collection<String> toNames(Collection<GHLabel> labels) {
|
||||
List<String> r = new ArrayList<String>();
|
||||
List<String> r = new ArrayList<>();
|
||||
for (GHLabel l : labels) {
|
||||
r.add(l.getName());
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begins the creation of a new instance.
|
||||
*
|
||||
* Consumer must call {@link Creator#done()} to commit changes.
|
||||
*
|
||||
* @param repository
|
||||
* the repository in which the label will be created.
|
||||
* @return a {@link Creator}
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
*/
|
||||
@Preview
|
||||
@Deprecated
|
||||
static Creator create(GHRepository repository) throws IOException {
|
||||
return new Creator(repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a label from a repository.
|
||||
*
|
||||
* @param repository
|
||||
* the repository to read from
|
||||
* @param name
|
||||
* the name of the label
|
||||
* @return a label
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
*/
|
||||
static GHLabel read(@Nonnull GHRepository repository, @Nonnull String name) throws IOException {
|
||||
return repository.root.createRequest()
|
||||
.withUrlPath(repository.getApiTailUrl("labels"), name)
|
||||
.fetch(GHLabel.class);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all labels from a repository.
|
||||
*
|
||||
* @param repository
|
||||
* the repository to read from
|
||||
* @return iterable of all labels
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
*/
|
||||
static PagedIterable<GHLabel> readAll(@Nonnull final GHRepository repository) throws IOException {
|
||||
return repository.root.createRequest()
|
||||
.withUrlPath(repository.getApiTailUrl("labels"))
|
||||
.toIterable(GHLabel[].class, null);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Begins a batch update
|
||||
*
|
||||
* Consumer must call {@link Updater#done()} to commit changes.
|
||||
*
|
||||
* @return a {@link Updater}
|
||||
*/
|
||||
@Preview
|
||||
@Deprecated
|
||||
public Updater update() {
|
||||
return new Updater(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begins a single property update.
|
||||
*
|
||||
* @return a {@link Setter}
|
||||
*/
|
||||
@Preview
|
||||
@Deprecated
|
||||
public Setter set() {
|
||||
return new Setter(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete this label from the repository.
|
||||
*
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
root.createRequest().method("DELETE").setRawUrlPath(getUrl()).send();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o)
|
||||
@@ -120,11 +214,54 @@ public class GHLabel {
|
||||
return false;
|
||||
final GHLabel ghLabel = (GHLabel) o;
|
||||
return Objects.equals(url, ghLabel.url) && Objects.equals(name, ghLabel.name)
|
||||
&& Objects.equals(color, ghLabel.color) && Objects.equals(repo, ghLabel.repo);
|
||||
&& Objects.equals(color, ghLabel.color) && Objects.equals(description, ghLabel.description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(url, name, color, repo);
|
||||
return Objects.hash(url, name, color, description);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link GHLabelBuilder} that updates a single property per request
|
||||
*
|
||||
* {@link #done()} is called automatically after the property is set.
|
||||
*/
|
||||
@Preview
|
||||
@Deprecated
|
||||
public static class Setter extends GHLabelBuilder<GHLabel> {
|
||||
private Setter(@Nonnull GHLabel base) {
|
||||
super(GHLabel.class, base.getApiRoot(), base);
|
||||
requester.method("PATCH").setRawUrlPath(base.getUrl());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link GHLabelBuilder} that allows multiple properties to be updated per request.
|
||||
*
|
||||
* Consumer must call {@link #done()} to commit changes.
|
||||
*/
|
||||
@Preview
|
||||
@Deprecated
|
||||
public static class Updater extends GHLabelBuilder<Updater> {
|
||||
private Updater(@Nonnull GHLabel base) {
|
||||
super(Updater.class, base.getApiRoot(), base);
|
||||
requester.method("PATCH").setRawUrlPath(base.getUrl());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link GHLabelBuilder} that creates a new {@link GHLabel}
|
||||
*
|
||||
* Consumer must call {@link #done()} to create the new instance.
|
||||
*/
|
||||
@Preview
|
||||
@Deprecated
|
||||
public static class Creator extends GHLabelBuilder<Creator> {
|
||||
private Creator(@Nonnull GHRepository repository) {
|
||||
super(Creator.class, repository.root, null);
|
||||
requester.method("POST").withUrlPath(repository.getApiTailUrl("labels"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
60
src/main/java/org/kohsuke/github/GHLabelBuilder.java
Normal file
60
src/main/java/org/kohsuke/github/GHLabelBuilder.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.annotation.CheckForNull;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
*
|
||||
* @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 GHLabelBuilder<S> extends AbstractBuilder<GHLabel, S> {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param intermediateReturnType
|
||||
* 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)}.
|
||||
* @param root
|
||||
* the GitHub instance to which updates will be sent
|
||||
* @param baseInstance
|
||||
* instance on which to base this builder. If {@code null} a new instance will be created.
|
||||
*/
|
||||
protected GHLabelBuilder(@Nonnull Class<S> intermediateReturnType,
|
||||
@Nonnull GitHub root,
|
||||
@CheckForNull GHLabel baseInstance) {
|
||||
super(GHLabel.class, intermediateReturnType, root, baseInstance);
|
||||
|
||||
if (baseInstance != null) {
|
||||
requester.with("name", baseInstance.getName());
|
||||
requester.with("color", baseInstance.getColor());
|
||||
requester.with("description", baseInstance.getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Preview
|
||||
@Deprecated
|
||||
public S name(String value) throws IOException {
|
||||
return with("name", value);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Preview
|
||||
@Deprecated
|
||||
public S color(String value) throws IOException {
|
||||
return with("color", value);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Preview
|
||||
@Deprecated
|
||||
public S description(String value) throws IOException {
|
||||
return with("description", value);
|
||||
}
|
||||
}
|
||||
@@ -24,13 +24,13 @@
|
||||
|
||||
package org.kohsuke.github;
|
||||
|
||||
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* The GitHub Preview API's license information
|
||||
@@ -78,14 +78,6 @@ public class GHLicense extends GHObject {
|
||||
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
|
||||
*
|
||||
@@ -199,7 +191,14 @@ public class GHLicense extends GHObject {
|
||||
if (description != null)
|
||||
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
|
||||
@@ -210,12 +209,12 @@ public class GHLicense extends GHObject {
|
||||
return false;
|
||||
|
||||
GHLicense that = (GHLicense) o;
|
||||
return this.url.equals(that.url);
|
||||
return Objects.equals(getUrl(), that.getUrl());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return url.hashCode();
|
||||
return Objects.hashCode(getUrl());
|
||||
}
|
||||
|
||||
GHLicense wrap(GitHub root) {
|
||||
|
||||
@@ -24,12 +24,14 @@ public abstract class GHObject {
|
||||
/**
|
||||
* Capture response HTTP headers on the state object.
|
||||
*/
|
||||
protected Map<String, List<String>> responseHeaderFields;
|
||||
protected transient Map<String, List<String>> responseHeaderFields;
|
||||
|
||||
protected String url;
|
||||
protected long id;
|
||||
protected String created_at;
|
||||
protected String updated_at;
|
||||
private String url;
|
||||
|
||||
private long id;
|
||||
private String nodeId;
|
||||
private String createdAt;
|
||||
private String updatedAt;
|
||||
|
||||
GHObject() {
|
||||
}
|
||||
@@ -74,12 +76,12 @@ public abstract class GHObject {
|
||||
*/
|
||||
@WithBridgeMethods(value = String.class, adapterMethod = "createdAtStr")
|
||||
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")
|
||||
private Object createdAtStr(Date id, Class type) {
|
||||
return created_at;
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,7 +112,18 @@ public abstract class GHObject {
|
||||
* on error
|
||||
*/
|
||||
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
|
||||
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,40 @@ public class GHOrganization extends GHPerson {
|
||||
.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.
|
||||
*
|
||||
* @param teamId
|
||||
* id of the team that we want to query for
|
||||
* @return the team
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
*
|
||||
* @see <a href= "https://developer.github.com/v3/teams/#get-team-by-name">documentation</a>
|
||||
*/
|
||||
public GHTeam getTeam(long teamId) throws IOException {
|
||||
return root.createRequest()
|
||||
.withUrlPath(String.format("/organizations/%d/team/%d", getId(), teamId))
|
||||
.fetch(GHTeam.class)
|
||||
.wrapUp(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a team that has the given name in its {@link GHTeam#getName()}
|
||||
*
|
||||
@@ -147,19 +181,19 @@ public class GHOrganization extends GHPerson {
|
||||
|
||||
/**
|
||||
* Finds a team that has the given slug in its {@link GHTeam#getSlug()}
|
||||
*
|
||||
*
|
||||
* @param slug
|
||||
* the slug
|
||||
* @return the team by slug
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
* @see <a href= "https://developer.github.com/v3/teams/#get-team-by-name">documentation</a>
|
||||
*/
|
||||
public GHTeam getTeamBySlug(String slug) throws IOException {
|
||||
for (GHTeam t : listTeams()) {
|
||||
if (t.getSlug().equals(slug))
|
||||
return t;
|
||||
}
|
||||
return null;
|
||||
return root.createRequest()
|
||||
.withUrlPath(String.format("/orgs/%s/teams/%s", login, slug))
|
||||
.fetch(GHTeam.class)
|
||||
.wrapUp(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -387,7 +421,7 @@ public class GHOrganization extends GHPerson {
|
||||
* The 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;
|
||||
|
||||
// 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 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
|
||||
protected Integer total_private_repos;
|
||||
@@ -46,13 +46,16 @@ public abstract class GHPerson extends GHObject {
|
||||
* the io exception
|
||||
*/
|
||||
protected synchronized void populate() throws IOException {
|
||||
if (created_at != null) {
|
||||
if (super.getCreatedAt() != null) {
|
||||
return; // already populated
|
||||
}
|
||||
if (root == null || root.isOffline()) {
|
||||
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 {
|
||||
try {
|
||||
return root.createRequest()
|
||||
.withUrlPath("/repos/" + login + '/' + name)
|
||||
.fetch(GHRepository.class)
|
||||
.wrap(root);
|
||||
return GHRepository.read(root, login, name);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
@@ -234,6 +234,18 @@ public abstract class GHPerson extends GHObject {
|
||||
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 {
|
||||
populate();
|
||||
return super.getCreatedAt();
|
||||
|
||||
@@ -42,7 +42,6 @@ public class GHProject extends GHObject {
|
||||
|
||||
private String owner_url;
|
||||
private String html_url;
|
||||
private String node_id;
|
||||
private String name;
|
||||
private String body;
|
||||
private int number;
|
||||
@@ -81,10 +80,8 @@ public class GHProject extends GHObject {
|
||||
} else if (owner_url.contains("/users/")) {
|
||||
owner = root.createRequest().withUrlPath(getOwnerUrl().getPath()).fetch(GHUser.class).wrapUp(root);
|
||||
} else if (owner_url.contains("/repos/")) {
|
||||
owner = root.createRequest()
|
||||
.withUrlPath(getOwnerUrl().getPath())
|
||||
.fetch(GHRepository.class)
|
||||
.wrap(root);
|
||||
String[] pathElements = getOwnerUrl().getPath().split("/");
|
||||
owner = GHRepository.read(root, pathElements[1], pathElements[2]);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
@@ -105,10 +102,12 @@ public class GHProject extends GHObject {
|
||||
/**
|
||||
* Gets node id.
|
||||
*
|
||||
* @deprecated Use {@link GHObject#getNodeId()}
|
||||
* @return the node id
|
||||
*/
|
||||
@Deprecated
|
||||
public String getNode_id() {
|
||||
return node_id;
|
||||
return getNodeId();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,7 +190,7 @@ public class GHProject extends GHObject {
|
||||
* @return the api route
|
||||
*/
|
||||
protected String getApiRoute() {
|
||||
return "/projects/" + id;
|
||||
return "/projects/" + getId();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,7 +289,7 @@ public class GHProject extends GHObject {
|
||||
final GHProject project = this;
|
||||
return root.createRequest()
|
||||
.withPreview(INERTIA)
|
||||
.withUrlPath(String.format("/projects/%d/columns", id))
|
||||
.withUrlPath(String.format("/projects/%d/columns", getId()))
|
||||
.toIterable(GHProjectColumn[].class, item -> item.wrap(project));
|
||||
}
|
||||
|
||||
@@ -308,7 +307,7 @@ public class GHProject extends GHObject {
|
||||
.method("POST")
|
||||
.withPreview(INERTIA)
|
||||
.with("name", name)
|
||||
.withUrlPath(String.format("/projects/%d/columns", id))
|
||||
.withUrlPath(String.format("/projects/%d/columns", getId()))
|
||||
.fetch(GHProjectColumn.class)
|
||||
.wrap(this);
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ public class GHProjectCard extends GHObject {
|
||||
* @return the api route
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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;
|
||||
return root.createRequest()
|
||||
.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));
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ public class GHProjectColumn extends GHObject {
|
||||
.method("POST")
|
||||
.withPreview(INERTIA)
|
||||
.with("note", note)
|
||||
.withUrlPath(String.format("/projects/columns/%d/cards", id))
|
||||
.withUrlPath(String.format("/projects/columns/%d/cards", getId()))
|
||||
.fetch(GHProjectCard.class)
|
||||
.wrap(this);
|
||||
}
|
||||
@@ -177,7 +177,7 @@ public class GHProjectColumn extends GHObject {
|
||||
.withPreview(INERTIA)
|
||||
.with("content_type", issue instanceof GHPullRequest ? "PullRequest" : "Issue")
|
||||
.with("content_id", issue.getId())
|
||||
.withUrlPath(String.format("/projects/columns/%d/cards", id))
|
||||
.withUrlPath(String.format("/projects/columns/%d/cards", getId()))
|
||||
.fetch(GHProjectCard.class)
|
||||
.wrap(this);
|
||||
}
|
||||
|
||||
@@ -29,13 +29,14 @@ import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.CheckForNull;
|
||||
|
||||
import static org.kohsuke.github.Previews.LYDIAN;
|
||||
import static org.kohsuke.github.Previews.SHADOW_CAT;
|
||||
|
||||
/**
|
||||
@@ -71,13 +72,6 @@ public class GHPullRequest extends GHIssue implements Refreshable {
|
||||
private GHUser[] requested_reviewers;
|
||||
private GHTeam[] requested_teams;
|
||||
|
||||
/**
|
||||
* GitHub doesn't return some properties of {@link GHIssue} when requesting the GET on the 'pulls' API route as
|
||||
* opposed to 'issues' API route. This flag remembers whether we made the GET call on the 'issues' route on this
|
||||
* object to fill in those missing details
|
||||
*/
|
||||
private transient boolean fetchedIssueDetails;
|
||||
|
||||
GHPullRequest wrapUp(GHRepository owner) {
|
||||
this.wrap(owner);
|
||||
return wrapUp(owner.root);
|
||||
@@ -103,7 +97,9 @@ public class GHPullRequest extends GHIssue implements Refreshable {
|
||||
protected String getApiRoute() {
|
||||
if (owner == null) {
|
||||
// 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;
|
||||
}
|
||||
@@ -174,12 +170,6 @@ public class GHPullRequest extends GHIssue implements Refreshable {
|
||||
return GitHubClient.parseDate(merged_at);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<GHLabel> getLabels() throws IOException {
|
||||
fetchIssue();
|
||||
return super.getLabels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GHUser getClosedBy() {
|
||||
return null;
|
||||
@@ -387,10 +377,14 @@ public class GHPullRequest extends GHIssue implements Refreshable {
|
||||
* Repopulates this object.
|
||||
*/
|
||||
public void refresh() throws IOException {
|
||||
if (root.isOffline()) {
|
||||
if (root == null || root.isOffline()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -558,6 +552,41 @@ public class GHPullRequest extends GHIssue implements Refreshable {
|
||||
.send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the base branch on the pull request
|
||||
*
|
||||
* @param newBaseBranch
|
||||
* the name of the new base branch
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
* @return the updated pull request
|
||||
*/
|
||||
public GHPullRequest setBaseBranch(String newBaseBranch) throws IOException {
|
||||
return root.createRequest()
|
||||
.method("PATCH")
|
||||
.with("base", newBaseBranch)
|
||||
.withUrlPath(getApiRoute())
|
||||
.fetch(GHPullRequest.class)
|
||||
.wrapUp(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the branch. The same as pressing the button in the web GUI.
|
||||
*
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
*/
|
||||
@Preview
|
||||
@Deprecated
|
||||
public void updateBranch() throws IOException {
|
||||
root.createRequest()
|
||||
.withPreview(LYDIAN)
|
||||
.method("PUT")
|
||||
.with("expected_head_sha", head.getSha())
|
||||
.withUrlPath(getApiRoute() + "/update-branch")
|
||||
.send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this pull request.
|
||||
* <p>
|
||||
@@ -619,10 +648,4 @@ public class GHPullRequest extends GHIssue implements Refreshable {
|
||||
MERGE, SQUASH, REBASE
|
||||
}
|
||||
|
||||
private void fetchIssue() throws IOException {
|
||||
if (!fetchedIssueDetails) {
|
||||
root.createRequest().withUrlPath(getIssuesApiRoute()).fetchInto(this);
|
||||
fetchedIssueDetails = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
86
src/main/java/org/kohsuke/github/GHPullRequestChanges.java
Normal file
86
src/main/java/org/kohsuke/github/GHPullRequestChanges.java
Normal file
@@ -0,0 +1,86 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
|
||||
/**
|
||||
* Wrapper to define changed fields on pull_request action="edited"
|
||||
*
|
||||
* @see GHEventPayload.PullRequest
|
||||
*/
|
||||
@SuppressFBWarnings("UWF_UNWRITTEN_FIELD")
|
||||
public class GHPullRequestChanges {
|
||||
|
||||
private GHCommitPointer base;
|
||||
private GHFrom title;
|
||||
private GHFrom body;
|
||||
|
||||
/**
|
||||
* Old target branch for pull request.
|
||||
*
|
||||
* @return old target branch info (or null if not changed)
|
||||
*/
|
||||
public GHCommitPointer getBase() {
|
||||
return base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Old pull request title.
|
||||
*
|
||||
* @return old pull request title (or null if not changed)
|
||||
*/
|
||||
public GHFrom getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Old pull request body.
|
||||
*
|
||||
* @return old pull request body (or null if not changed)
|
||||
*/
|
||||
public GHFrom getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.kohsuke.github.GHCommitPointer
|
||||
*/
|
||||
public static class GHCommitPointer {
|
||||
private GHFrom ref;
|
||||
private GHFrom sha;
|
||||
|
||||
/**
|
||||
* Named ref to the commit. This (from value) appears to be a "short ref" that doesn't include "refs/heads/"
|
||||
* portion.
|
||||
*
|
||||
* @return the ref
|
||||
*/
|
||||
public GHFrom getRef() {
|
||||
return ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* SHA1 of the commit.
|
||||
*
|
||||
* @return sha
|
||||
*/
|
||||
public GHFrom getSha() {
|
||||
return sha;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for changed values.
|
||||
*/
|
||||
public static class GHFrom {
|
||||
private String from;
|
||||
|
||||
/**
|
||||
* Previous value that was changed.
|
||||
*
|
||||
* @return previous value
|
||||
*/
|
||||
public String getFrom() {
|
||||
return from;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,7 @@ public class GHPullRequestReview extends GHObject {
|
||||
private String commit_id;
|
||||
private GHPullRequestReviewState state;
|
||||
private String submitted_at;
|
||||
private String html_url;
|
||||
|
||||
GHPullRequestReview wrapUp(GHPullRequest owner) {
|
||||
this.owner = owner;
|
||||
@@ -102,7 +103,7 @@ public class GHPullRequestReview extends GHObject {
|
||||
|
||||
@Override
|
||||
public URL getHtmlUrl() {
|
||||
return null;
|
||||
return GitHubClient.parseURL(html_url);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,7 +112,7 @@ public class GHPullRequestReview extends GHObject {
|
||||
* @return the api route
|
||||
*/
|
||||
protected String getApiRoute() {
|
||||
return owner.getApiRoute() + "/reviews/" + id;
|
||||
return owner.getApiRoute() + "/reviews/" + getId();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,6 +44,7 @@ public class GHPullRequestReviewComment extends GHObject implements Reactable {
|
||||
private String body;
|
||||
private GHUser user;
|
||||
private String path;
|
||||
private String html_url;
|
||||
private int position = -1;
|
||||
private int original_position = -1;
|
||||
private long in_reply_to_id = -1L;
|
||||
@@ -143,7 +144,7 @@ public class GHPullRequestReviewComment extends GHObject implements Reactable {
|
||||
|
||||
@Override
|
||||
public URL getHtmlUrl() {
|
||||
return null;
|
||||
return GitHubClient.parseURL(html_url);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,7 +153,7 @@ public class GHPullRequestReviewComment extends GHObject implements Reactable {
|
||||
* @return the api route
|
||||
*/
|
||||
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 org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
@@ -29,7 +30,7 @@ public class GHRateLimit {
|
||||
/**
|
||||
* 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
|
||||
public int remaining;
|
||||
@@ -37,7 +38,7 @@ public class GHRateLimit {
|
||||
/**
|
||||
* 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
|
||||
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()}
|
||||
* 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
|
||||
public Date reset;
|
||||
@@ -65,17 +66,58 @@ public class GHRateLimit {
|
||||
@Nonnull
|
||||
private final Record integrationManifest;
|
||||
|
||||
/**
|
||||
* The default GHRateLimit provided to new {@link GitHubClient}s.
|
||||
*
|
||||
* Contains all expired records that will cause {@link GitHubClient#rateLimit(RateLimitTarget)} to refresh with new
|
||||
* data when called.
|
||||
*
|
||||
* Private, but made internal for testing.
|
||||
*/
|
||||
@Nonnull
|
||||
static GHRateLimit Unknown() {
|
||||
return new GHRateLimit(new UnknownLimitRecord(),
|
||||
new UnknownLimitRecord(),
|
||||
new UnknownLimitRecord(),
|
||||
new UnknownLimitRecord());
|
||||
}
|
||||
static final GHRateLimit DEFAULT = new GHRateLimit(UnknownLimitRecord.DEFAULT,
|
||||
UnknownLimitRecord.DEFAULT,
|
||||
UnknownLimitRecord.DEFAULT,
|
||||
UnknownLimitRecord.DEFAULT);
|
||||
|
||||
/**
|
||||
* Creates a new {@link GHRateLimit} from a single record for the specified endpoint with place holders for other
|
||||
* records.
|
||||
*
|
||||
* This is used to create {@link GHRateLimit} instances that can merged with other instances.
|
||||
*
|
||||
* @param record
|
||||
* the rate limit record. Can be a regular {@link Record} constructed from header information or an
|
||||
* {@link UnknownLimitRecord} placeholder.
|
||||
* @param rateLimitTarget
|
||||
* which rate limit record to fill
|
||||
* @return a new {@link GHRateLimit} instance containing the supplied record
|
||||
*/
|
||||
@Nonnull
|
||||
static GHRateLimit fromHeaderRecord(Record header) {
|
||||
return new GHRateLimit(header, new UnknownLimitRecord(), new UnknownLimitRecord(), new UnknownLimitRecord());
|
||||
static GHRateLimit fromRecord(@Nonnull Record record, @Nonnull RateLimitTarget rateLimitTarget) {
|
||||
if (rateLimitTarget == RateLimitTarget.CORE || rateLimitTarget == RateLimitTarget.NONE) {
|
||||
return new GHRateLimit(record,
|
||||
UnknownLimitRecord.DEFAULT,
|
||||
UnknownLimitRecord.DEFAULT,
|
||||
UnknownLimitRecord.DEFAULT);
|
||||
} else if (rateLimitTarget == RateLimitTarget.SEARCH) {
|
||||
return new GHRateLimit(UnknownLimitRecord.DEFAULT,
|
||||
record,
|
||||
UnknownLimitRecord.DEFAULT,
|
||||
UnknownLimitRecord.DEFAULT);
|
||||
} else if (rateLimitTarget == RateLimitTarget.GRAPHQL) {
|
||||
return new GHRateLimit(UnknownLimitRecord.DEFAULT,
|
||||
UnknownLimitRecord.DEFAULT,
|
||||
record,
|
||||
UnknownLimitRecord.DEFAULT);
|
||||
} else if (rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST) {
|
||||
return new GHRateLimit(UnknownLimitRecord.DEFAULT,
|
||||
UnknownLimitRecord.DEFAULT,
|
||||
UnknownLimitRecord.DEFAULT,
|
||||
record);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown rate limit target: " + rateLimitTarget.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@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.
|
||||
* @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
|
||||
* @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.
|
||||
* Issue #605.
|
||||
* The search record provides the rate limit status for the Search API.
|
||||
*
|
||||
* @return a rate limit record
|
||||
* @since 1.115
|
||||
*/
|
||||
@Nonnull
|
||||
Record getSearch() {
|
||||
public Record getSearch() {
|
||||
return search;
|
||||
}
|
||||
|
||||
/**
|
||||
* The graphql object provides your rate limit status for the GraphQL API. TODO: integrate with header limit
|
||||
* updating. Issue #605.
|
||||
* The graphql record provides the rate limit status for the GraphQL API.
|
||||
*
|
||||
* @return a rate limit record
|
||||
* @since 1.115
|
||||
*/
|
||||
@Nonnull
|
||||
Record getGraphQL() {
|
||||
public Record getGraphQL() {
|
||||
return graphql;
|
||||
}
|
||||
|
||||
/**
|
||||
* The integration_manifest object provides your rate limit status for the GitHub App Manifest code conversion
|
||||
* endpoint. TODO: integrate with header limit updating. Issue #605.
|
||||
* The integration manifest record provides the rate limit status for the GitHub App Manifest code conversion
|
||||
* endpoint.
|
||||
*
|
||||
* @return a rate limit record
|
||||
* @since 1.115
|
||||
*/
|
||||
@Nonnull
|
||||
Record getIntegrationManifest() {
|
||||
public Record getIntegrationManifest() {
|
||||
return integrationManifest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GHRateLimit {" + "core " + getCore().toString() + "search " + getSearch().toString() + "graphql "
|
||||
+ getGraphQL().toString() + "integrationManifest " + getIntegrationManifest().toString() + '}';
|
||||
return "GHRateLimit {" + "core " + getCore().toString() + ", search " + getSearch().toString() + ", graphql "
|
||||
+ getGraphQL().toString() + ", integrationManifest " + getIntegrationManifest().toString() + "}";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -221,44 +264,111 @@ public class GHRateLimit {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate {@link Record} for a particular url path.
|
||||
*
|
||||
* @param urlPath
|
||||
* the url path of the request
|
||||
* @return the {@link Record} for a url path.
|
||||
* Merge a {@link GHRateLimit} with another one to create a new {@link GHRateLimit} keeping the latest
|
||||
* {@link Record}s from each.
|
||||
*
|
||||
* @param newLimit
|
||||
* {@link GHRateLimit} with potentially updated {@link Record}s.
|
||||
* @return a merged {@link GHRateLimit} with the latest {@link Record}s from these two instances. If the merged
|
||||
* instance is equal to the current instance, the current instance is returned.
|
||||
*/
|
||||
@Nonnull
|
||||
Record getRecordForUrlPath(@Nonnull String urlPath) {
|
||||
if (urlPath.equals("/rate_limit")) {
|
||||
return new UnknownLimitRecord();
|
||||
} else if (urlPath.startsWith("/search")) {
|
||||
return getSearch();
|
||||
} else if (urlPath.startsWith("/graphql")) {
|
||||
return getGraphQL();
|
||||
} else if (urlPath.startsWith("/app-manifests")) {
|
||||
return getIntegrationManifest();
|
||||
} else {
|
||||
GHRateLimit getMergedRateLimit(@Nonnull GHRateLimit newLimit) {
|
||||
|
||||
GHRateLimit merged = new GHRateLimit(getCore().currentOrUpdated(newLimit.getCore()),
|
||||
getSearch().currentOrUpdated(newLimit.getSearch()),
|
||||
getGraphQL().currentOrUpdated(newLimit.getGraphQL()),
|
||||
getIntegrationManifest().currentOrUpdated(newLimit.getIntegrationManifest()));
|
||||
|
||||
if (merged.equals(this)) {
|
||||
merged = this;
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the specified {@link Record}.
|
||||
*
|
||||
* {@link RateLimitTarget#NONE} will return {@link UnknownLimitRecord#DEFAULT} to prevent any clients from
|
||||
* accidentally waiting on that record to reset before continuing.
|
||||
*
|
||||
* @param rateLimitTarget
|
||||
* the target rate limit record
|
||||
* @return the target {@link Record} from this instance.
|
||||
*/
|
||||
@Nonnull
|
||||
Record getRecord(@Nonnull RateLimitTarget rateLimitTarget) {
|
||||
if (rateLimitTarget == RateLimitTarget.CORE) {
|
||||
return getCore();
|
||||
} else if (rateLimitTarget == RateLimitTarget.SEARCH) {
|
||||
return getSearch();
|
||||
} else if (rateLimitTarget == RateLimitTarget.GRAPHQL) {
|
||||
return getGraphQL();
|
||||
} else if (rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST) {
|
||||
return getIntegrationManifest();
|
||||
} else if (rateLimitTarget == RateLimitTarget.NONE) {
|
||||
return UnknownLimitRecord.DEFAULT;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown rate limit target: " + rateLimitTarget.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static class UnknownLimitRecord extends Record {
|
||||
|
||||
// One hour
|
||||
private static final long unknownLimitResetSeconds = 60L * 60L;
|
||||
private static final long defaultUnknownLimitResetSeconds = Duration.ofSeconds(30).getSeconds();
|
||||
|
||||
/**
|
||||
* 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 unknownRemaining = 999999;
|
||||
|
||||
private UnknownLimitRecord() {
|
||||
super(unknownLimit, unknownRemaining, System.currentTimeMillis() / 1000L + unknownLimitResetSeconds);
|
||||
// The default UnknownLimitRecord is an expired record.
|
||||
private static final UnknownLimitRecord DEFAULT = new UnknownLimitRecord(Long.MIN_VALUE);
|
||||
|
||||
// The starting current UnknownLimitRecord is an expired record.
|
||||
private static 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;
|
||||
|
||||
/**
|
||||
* Allotted API call per hour.
|
||||
* Allotted API call per time period.
|
||||
*/
|
||||
private final int limit;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
@@ -291,9 +399,11 @@ public class GHRateLimit {
|
||||
private final long createdAtEpochSeconds = System.currentTimeMillis() / 1000;
|
||||
|
||||
/**
|
||||
* The time at which the rate limit will reset. This value is calculated based on
|
||||
* {@link #getResetEpochSeconds()} by calling {@link #calculateResetDate}. If the clock on the local machine not
|
||||
* synchronized with the server clock, this time value will be adjusted to match the local machine's clock.
|
||||
* The date at which the rate limit will reset, adjusted to local machine time if the local machine's clock not
|
||||
* synchronized with to the same clock as the GitHub server.
|
||||
*
|
||||
* @see #calculateResetDate(String)
|
||||
* @see #getResetDate()
|
||||
*/
|
||||
@Nonnull
|
||||
private final Date resetDate;
|
||||
@@ -341,12 +451,58 @@ public class GHRateLimit {
|
||||
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.
|
||||
* <p>
|
||||
* {@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
|
||||
* accurate to the local machine.
|
||||
* calculated based on the local machine clock.
|
||||
* </p>
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* @return a long representing the time in epoch seconds when the rate limit will reset
|
||||
* @see #getResetDate() #getResetDate()
|
||||
* @see #getResetDate()
|
||||
*/
|
||||
public long getResetEpochSeconds() {
|
||||
return resetEpochSeconds;
|
||||
@@ -424,6 +580,8 @@ public class GHRateLimit {
|
||||
/**
|
||||
* Whether the rate limit reset date indicated by this instance is expired
|
||||
*
|
||||
* If attempting to wait for the rate limit to reset, consider implementing a {@link RateLimitChecker} instead.
|
||||
*
|
||||
* @return true if the rate limit reset date has passed. Otherwise false.
|
||||
*/
|
||||
public boolean isExpired() {
|
||||
@@ -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
|
||||
* clock not synchronized with to the same clock as the GitHub server.
|
||||
* The date at which the rate limit will reset, adjusted to local machine time if the local machine's clock not
|
||||
* synchronized with to the same clock as the GitHub server.
|
||||
*
|
||||
* If attempting to wait for the rate limit to reset, consider implementing a {@link RateLimitChecker} instead.
|
||||
*
|
||||
|
||||
@@ -58,6 +58,6 @@ public class GHReaction extends GHObject {
|
||||
* the io exception
|
||||
*/
|
||||
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;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -90,6 +91,78 @@ public class GHRef {
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,7 @@ import static java.lang.String.*;
|
||||
* Release in a github repository.
|
||||
*
|
||||
* @see GHRepository#getReleases() GHRepository#getReleases()
|
||||
* @see GHRepository#listReleases() () GHRepository#listReleases()
|
||||
* @see GHRepository#createRelease(String) GHRepository#createRelease(String)
|
||||
*/
|
||||
public class GHRelease extends GHObject {
|
||||
@@ -23,6 +24,7 @@ public class GHRelease extends GHObject {
|
||||
|
||||
private String html_url;
|
||||
private String assets_url;
|
||||
private List<GHAsset> assets;
|
||||
private String upload_url;
|
||||
private String tag_name;
|
||||
private String target_commitish;
|
||||
@@ -249,18 +251,45 @@ public class GHRelease extends GHObject {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets assets.
|
||||
* Get the cached assets.
|
||||
*
|
||||
* @return the assets
|
||||
*
|
||||
* @deprecated This should be the default behavior of {@link #getAssets()} in a future release. This method is
|
||||
* introduced in addition to enable a transition to using cached asset information while keeping the
|
||||
* existing logic in place for backwards compatibility.
|
||||
*/
|
||||
@Deprecated
|
||||
@Preview
|
||||
public List<GHAsset> assets() {
|
||||
return assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-fetch the assets of this release.
|
||||
*
|
||||
* @return the assets
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
* @deprecated The behavior of this method will change in a future release. It will then provide cached assets as
|
||||
* provided by {@link #assets()}. Use {@link #listAssets()} instead to fetch up-to-date information of
|
||||
* assets.
|
||||
*/
|
||||
@Deprecated
|
||||
public List<GHAsset> getAssets() throws IOException {
|
||||
return listAssets().toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-fetch the assets of this release.
|
||||
*
|
||||
* @return the assets
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
*/
|
||||
public List<GHAsset> getAssets() throws IOException {
|
||||
public PagedIterable<GHAsset> listAssets() throws IOException {
|
||||
Requester builder = owner.root.createRequest();
|
||||
|
||||
return builder.withUrlPath(getApiTailUrl("assets"))
|
||||
.toIterable(GHAsset[].class, item -> item.wrap(this))
|
||||
.toList();
|
||||
return builder.withUrlPath(getApiTailUrl("assets")).toIterable(GHAsset[].class, item -> item.wrap(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,7 +299,7 @@ public class GHRelease extends GHObject {
|
||||
* the io exception
|
||||
*/
|
||||
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 +312,6 @@ public class GHRelease extends GHObject {
|
||||
}
|
||||
|
||||
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 {
|
||||
return builder.method("PATCH")
|
||||
.withUrlPath(base.owner.getApiTailUrl("releases/" + base.id))
|
||||
.withUrlPath(base.owner.getApiTailUrl("releases/" + base.getId()))
|
||||
.fetch(GHRelease.class)
|
||||
.wrap(base.owner);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,6 @@ class GHRepoHook extends GHHook {
|
||||
|
||||
@Override
|
||||
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;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
|
||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
@@ -45,8 +46,10 @@ import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.WeakHashMap;
|
||||
@@ -63,10 +66,12 @@ import static org.kohsuke.github.Previews.*;
|
||||
@SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" },
|
||||
justification = "JSON API")
|
||||
public class GHRepository extends GHObject {
|
||||
/* package almost final */ GitHub root;
|
||||
/* package almost final */ transient GitHub root;
|
||||
|
||||
private String nodeId, description, homepage, name, full_name;
|
||||
|
||||
private String description, homepage, name, full_name;
|
||||
private String html_url; // this is the UI
|
||||
|
||||
/*
|
||||
* The license information makes use of the preview API.
|
||||
*
|
||||
@@ -75,22 +80,30 @@ public class GHRepository extends GHObject {
|
||||
private GHLicense license;
|
||||
|
||||
private String git_url, ssh_url, clone_url, svn_url, mirror_url;
|
||||
|
||||
private GHUser owner; // not fully populated. beware.
|
||||
private boolean has_issues, has_wiki, fork, has_downloads, has_pages, archived;
|
||||
|
||||
private boolean has_issues, has_wiki, fork, has_downloads, has_pages, archived, has_projects;
|
||||
|
||||
private boolean allow_squash_merge;
|
||||
|
||||
private boolean allow_merge_commit;
|
||||
|
||||
private boolean allow_rebase_merge;
|
||||
|
||||
private boolean delete_branch_on_merge;
|
||||
|
||||
@JsonProperty("private")
|
||||
private boolean _private;
|
||||
|
||||
private int forks_count, stargazers_count, watchers_count, size, open_issues_count, subscribers_count;
|
||||
|
||||
private String pushed_at;
|
||||
|
||||
private Map<Integer, GHMilestone> milestones = new WeakHashMap<Integer, GHMilestone>();
|
||||
|
||||
private String default_branch, language;
|
||||
|
||||
private Map<String, GHCommit> commits = new WeakHashMap<String, GHCommit>();
|
||||
|
||||
@SkipFromToString
|
||||
@@ -98,6 +111,12 @@ public class GHRepository extends GHObject {
|
||||
|
||||
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.
|
||||
*
|
||||
@@ -185,6 +204,15 @@ public class GHRepository extends GHObject {
|
||||
boolean pull, push, admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets node id
|
||||
*
|
||||
* @return the node id
|
||||
*/
|
||||
public String getNodeId() {
|
||||
return nodeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets description.
|
||||
*
|
||||
@@ -221,6 +249,17 @@ public class GHRepository extends GHObject {
|
||||
return clone_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Git http transport url string.
|
||||
*
|
||||
* @return the string
|
||||
* @deprecated Typo of {@link #getHttpTransportUrl()}
|
||||
*/
|
||||
@Deprecated
|
||||
public String gitHttpTransportUrl() {
|
||||
return clone_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Subversion URL to access this repository: https://github.com/rails/rails
|
||||
*
|
||||
@@ -547,6 +586,15 @@ public class GHRepository extends GHObject {
|
||||
return has_issues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has projects boolean.
|
||||
*
|
||||
* @return the boolean
|
||||
*/
|
||||
public boolean hasProjects() {
|
||||
return has_projects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has wiki boolean.
|
||||
*
|
||||
@@ -650,6 +698,28 @@ public class GHRepository extends GHObject {
|
||||
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.
|
||||
*
|
||||
@@ -939,12 +1009,12 @@ public class GHRepository extends GHObject {
|
||||
@NonNull String method,
|
||||
@CheckForNull GHOrganization.Permission permission) throws IOException {
|
||||
Requester requester = root.createRequest().method(method);
|
||||
|
||||
if (permission != null) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -971,8 +1041,9 @@ public class GHRepository extends GHObject {
|
||||
|
||||
private void edit(String key, String value) throws IOException {
|
||||
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(key, value).method("PATCH").withUrlPath(getApiTailUrl("")).send();
|
||||
}
|
||||
|
||||
@@ -988,6 +1059,18 @@ public class GHRepository extends GHObject {
|
||||
edit("has_issues", String.valueOf(v));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables projects for this repository.
|
||||
*
|
||||
* @param v
|
||||
* the v
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
*/
|
||||
public void enableProjects(boolean v) throws IOException {
|
||||
edit("has_projects", String.valueOf(v));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables Wiki for this repository.
|
||||
*
|
||||
@@ -1205,8 +1288,9 @@ public class GHRepository extends GHObject {
|
||||
// this API is asynchronous. we need to wait for a bit
|
||||
for (int i = 0; i < 10; i++) {
|
||||
GHRepository r = root.getMyself().getRepository(name);
|
||||
if (r != null)
|
||||
if (r != null) {
|
||||
return r;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(3000);
|
||||
} catch (InterruptedException e) {
|
||||
@@ -1235,8 +1319,9 @@ public class GHRepository extends GHObject {
|
||||
// this API is asynchronous. we need to wait for a bit
|
||||
for (int i = 0; i < 10; i++) {
|
||||
GHRepository r = org.getRepository(name);
|
||||
if (r != null)
|
||||
if (r != null) {
|
||||
return r;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(3000);
|
||||
} catch (InterruptedException e) {
|
||||
@@ -1497,8 +1582,7 @@ public class GHRepository extends GHObject {
|
||||
* on failure communicating with GitHub, potentially due to an invalid ref type being requested
|
||||
*/
|
||||
public PagedIterable<GHRef> listRefs() throws IOException {
|
||||
final String url = String.format("/repos/%s/%s/git/refs", getOwnerName(), name);
|
||||
return root.createRequest().withUrlPath(url).toIterable(GHRef[].class, item -> item.wrap(root));
|
||||
return listRefs("");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1524,8 +1608,7 @@ public class GHRepository extends GHObject {
|
||||
* on failure communicating with GitHub, potentially due to an invalid ref type being requested
|
||||
*/
|
||||
public PagedIterable<GHRef> listRefs(String refType) throws IOException {
|
||||
final String url = String.format("/repos/%s/%s/git/refs/%s", getOwnerName(), name, refType);
|
||||
return root.createRequest().withUrlPath(url).toIterable(GHRef[].class, item -> item.wrap(root));
|
||||
return GHRef.readMatching(this, refType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1538,10 +1621,7 @@ public class GHRepository extends GHObject {
|
||||
* on failure communicating with GitHub, potentially due to an invalid ref type being requested
|
||||
*/
|
||||
public GHRef getRef(String refName) throws IOException {
|
||||
return root.createRequest()
|
||||
.withUrlPath(getApiTailUrl(String.format("git/refs/%s", refName)))
|
||||
.fetch(GHRef.class)
|
||||
.wrap(root);
|
||||
return GHRef.read(this, refName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1708,6 +1788,20 @@ public class GHRepository extends GHObject {
|
||||
.toIterable(GHCommitComment[].class, item -> item.wrap(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all comments on a specific commit.
|
||||
*
|
||||
* @param commitSha
|
||||
* the hash of the commit
|
||||
*
|
||||
* @return the paged iterable
|
||||
*/
|
||||
public PagedIterable<GHCommitComment> listCommitComments(String commitSha) {
|
||||
return root.createRequest()
|
||||
.withUrlPath(String.format("/repos/%s/%s/commits/%s/comments", getOwnerName(), name, commitSha))
|
||||
.toIterable(GHCommitComment[].class, item -> item.wrap(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the basic license details for the repository.
|
||||
* <p>
|
||||
@@ -1745,7 +1839,7 @@ public class GHRepository extends GHObject {
|
||||
}
|
||||
|
||||
/**
|
||||
* /** Lists all the commit statues attached to the given commit, newer ones first.
|
||||
* /** Lists all the commit statuses attached to the given commit, newer ones first.
|
||||
*
|
||||
* @param sha1
|
||||
* the sha 1
|
||||
@@ -1773,6 +1867,27 @@ public class GHRepository extends GHObject {
|
||||
return v.isEmpty() ? null : v.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets check runs for given ref.
|
||||
*
|
||||
* @param ref
|
||||
* ref
|
||||
* @return check runs for given ref
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
* @see <a href="https://developer.github.com/v3/checks/runs/#list-check-runs-for-a-specific-ref">List check runs
|
||||
* for a specific ref</a>
|
||||
*/
|
||||
@Preview
|
||||
@Deprecated
|
||||
public PagedIterable<GHCheckRun> getCheckRuns(String ref) throws IOException {
|
||||
GitHubRequest request = root.createRequest()
|
||||
.withUrlPath(String.format("/repos/%s/%s/commits/%s/check-runs", getOwnerName(), name, ref))
|
||||
.withPreview(ANTIOPE)
|
||||
.build();
|
||||
return new GHCheckRunsIterable(root, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a commit status
|
||||
*
|
||||
@@ -1828,6 +1943,34 @@ public class GHRepository extends GHObject {
|
||||
return createCommitStatus(sha1, state, targetUrl, description, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a check run for a commit.
|
||||
*
|
||||
* @param name
|
||||
* an identifier for the run
|
||||
* @param headSHA
|
||||
* the commit hash
|
||||
* @return a builder which you should customize, then call {@link GHCheckRunBuilder#create}
|
||||
*/
|
||||
@Preview
|
||||
@Deprecated
|
||||
public @NonNull GHCheckRunBuilder createCheckRun(@NonNull String name, @NonNull String headSHA) {
|
||||
return new GHCheckRunBuilder(this, name, headSHA);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing check run.
|
||||
*
|
||||
* @param checkId
|
||||
* the existing checkId
|
||||
* @return a builder which you should customize, then call {@link GHCheckRunBuilder#create}
|
||||
*/
|
||||
@Preview
|
||||
@Deprecated
|
||||
public @NonNull GHCheckRunBuilder updateCheckRun(long checkId) {
|
||||
return new GHCheckRunBuilder(this, checkId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists repository events.
|
||||
*
|
||||
@@ -1851,9 +1994,7 @@ public class GHRepository extends GHObject {
|
||||
* the io exception
|
||||
*/
|
||||
public PagedIterable<GHLabel> listLabels() throws IOException {
|
||||
return root.createRequest()
|
||||
.withUrlPath(getApiTailUrl("labels"))
|
||||
.toIterable(GHLabel[].class, item -> item.wrapUp(this));
|
||||
return GHLabel.readAll(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1866,7 +2007,7 @@ public class GHRepository extends GHObject {
|
||||
* the io exception
|
||||
*/
|
||||
public GHLabel getLabel(String name) throws IOException {
|
||||
return root.createRequest().withUrlPath(getApiTailUrl("labels/" + name)).fetch(GHLabel.class).wrapUp(this);
|
||||
return GHLabel.read(this, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1881,7 +2022,7 @@ public class GHRepository extends GHObject {
|
||||
* the io exception
|
||||
*/
|
||||
public GHLabel createLabel(String name, String color) throws IOException {
|
||||
return createLabel(name, color, "");
|
||||
return GHLabel.create(this).name(name).color(color).description("").done();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1898,14 +2039,7 @@ public class GHRepository extends GHObject {
|
||||
* the io exception
|
||||
*/
|
||||
public GHLabel createLabel(String name, String color, String description) throws IOException {
|
||||
return root.createRequest()
|
||||
.method("POST")
|
||||
.with("name", name)
|
||||
.with("color", color)
|
||||
.with("description", description)
|
||||
.withUrlPath(getApiTailUrl("labels"))
|
||||
.fetch(GHLabel.class)
|
||||
.wrapUp(this);
|
||||
return GHLabel.create(this).name(name).color(color).description(description).done();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2082,9 +2216,15 @@ public class GHRepository extends GHObject {
|
||||
|
||||
GHRepository wrap(GitHub root) {
|
||||
this.root = root;
|
||||
if (root.isOffline()) {
|
||||
if (root.isOffline() && owner != null) {
|
||||
owner.wrapUp(root);
|
||||
}
|
||||
if (source != null) {
|
||||
source.wrap(root);
|
||||
}
|
||||
if (parent != null) {
|
||||
parent.wrap(root);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -2404,10 +2544,13 @@ public class GHRepository extends GHObject {
|
||||
* @see #getParent() #getParent()
|
||||
*/
|
||||
public GHRepository getSource() throws IOException {
|
||||
if (source == null)
|
||||
if (fork && source == null) {
|
||||
populate();
|
||||
}
|
||||
if (source == null) {
|
||||
return null;
|
||||
if (source.root == null)
|
||||
source = root.getRepository(source.getFullName());
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
@@ -2422,10 +2565,13 @@ public class GHRepository extends GHObject {
|
||||
* @see #getSource() #getSource()
|
||||
*/
|
||||
public GHRepository getParent() throws IOException {
|
||||
if (parent == null)
|
||||
if (fork && parent == null) {
|
||||
populate();
|
||||
}
|
||||
|
||||
if (parent == null) {
|
||||
return null;
|
||||
if (parent.root == null)
|
||||
parent = root.getRepository(parent.getFullName());
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
@@ -2644,8 +2790,9 @@ public class GHRepository extends GHObject {
|
||||
}
|
||||
|
||||
String getApiTailUrl(String tail) {
|
||||
if (tail.length() > 0 && !tail.startsWith("/"))
|
||||
if (tail.length() > 0 && !tail.startsWith("/")) {
|
||||
tail = '/' + tail;
|
||||
}
|
||||
return "/repos/" + getOwnerName() + "/" + name + tail;
|
||||
}
|
||||
|
||||
@@ -2744,4 +2891,32 @@ public class GHRepository extends GHObject {
|
||||
.fetch(GHTagObject.class)
|
||||
.wrap(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate this object.
|
||||
*
|
||||
* @throws java.io.IOException
|
||||
* The IO exception
|
||||
*/
|
||||
void populate() throws IOException {
|
||||
if (root.isOffline()) {
|
||||
return; // can't populate if the root is offline
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
@@ -315,21 +316,12 @@ public class GHRepositoryStatistics {
|
||||
* the io exception
|
||||
*/
|
||||
public List<CodeFrequency> getCodeFrequency() throws IOException {
|
||||
// Map to arrays first, since there are no field names in the
|
||||
// returned JSON.
|
||||
try {
|
||||
Integer[][] list = root.createRequest()
|
||||
CodeFrequency[] list = root.createRequest()
|
||||
.withUrlPath(getApiTailUrl("code_frequency"))
|
||||
.fetch(Integer[][].class);
|
||||
.fetch(CodeFrequency[].class);
|
||||
|
||||
// Convert to proper objects.
|
||||
List<CodeFrequency> returnList = new ArrayList<>();
|
||||
for (Integer[] item : list) {
|
||||
CodeFrequency cf = new CodeFrequency(Arrays.asList(item));
|
||||
returnList.add(cf);
|
||||
}
|
||||
|
||||
return returnList;
|
||||
return Arrays.asList(list);
|
||||
} catch (MismatchedInputException e) {
|
||||
// This sometimes happens when retrieving code frequency statistics
|
||||
// for a repository for the first time. It is probably still being
|
||||
@@ -342,10 +334,12 @@ public class GHRepositoryStatistics {
|
||||
* The type 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) {
|
||||
week = item.get(0);
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public List<Integer> getOwnerCommits() {
|
||||
return owner;
|
||||
return Collections.unmodifiableList(owner);
|
||||
}
|
||||
|
||||
Participation wrapUp(GitHub root) {
|
||||
@@ -455,28 +449,22 @@ public class GHRepositoryStatistics {
|
||||
* the io exception
|
||||
*/
|
||||
public List<PunchCardItem> getPunchCard() throws IOException {
|
||||
// Map to ArrayLists first, since there are no field names in the
|
||||
// returned JSON.
|
||||
Integer[][] list = root.createRequest().withUrlPath(getApiTailUrl("punch_card")).fetch(Integer[][].class);
|
||||
|
||||
// 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;
|
||||
PunchCardItem[] list = root.createRequest()
|
||||
.withUrlPath(getApiTailUrl("punch_card"))
|
||||
.fetch(PunchCardItem[].class);
|
||||
return Arrays.asList(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* The type 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) {
|
||||
dayOfWeek = item.get(0);
|
||||
hourOfDay = item.get(1);
|
||||
|
||||
@@ -25,6 +25,7 @@ public abstract class GHSearchBuilder<T> extends GHQueryBuilder<T> {
|
||||
super(root);
|
||||
this.receiverType = receiverType;
|
||||
req.withUrlPath(getApiUrl());
|
||||
req.rateLimit(RateLimitTarget.SEARCH);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,7 @@ public class GHTagObject {
|
||||
private String message;
|
||||
private GitUser tagger;
|
||||
private GHRef.GHObject object;
|
||||
private GHVerification verification;
|
||||
|
||||
GHTagObject wrap(GHRepository owner) {
|
||||
this.owner = owner;
|
||||
@@ -97,4 +98,13 @@ public class GHTagObject {
|
||||
public GHRef.GHObject getObject() {
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Verification Status.
|
||||
*
|
||||
* @return the Verification status
|
||||
*/
|
||||
public GHVerification getVerification() {
|
||||
return verification;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* A team in GitHub organization.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class GHTeam implements Refreshable {
|
||||
public class GHTeam extends GHObject implements Refreshable {
|
||||
private String html_url;
|
||||
private String name;
|
||||
private String permission;
|
||||
private String slug;
|
||||
private String description;
|
||||
private Privacy privacy;
|
||||
|
||||
private int id;
|
||||
private GHOrganization organization; // populated by GET /user/teams where Teams+Orgs are returned together
|
||||
|
||||
protected /* final */ GitHub root;
|
||||
@@ -130,12 +134,49 @@ 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() {
|
||||
return id;
|
||||
@Nonnull
|
||||
public PagedIterable<GHDiscussion> listDiscussions() throws IOException {
|
||||
return GHDiscussion.readAll(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* List members with specified role paged iterable.
|
||||
*
|
||||
* @param role
|
||||
* the role
|
||||
* @return the paged iterable
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
*/
|
||||
public PagedIterable<GHUser> listMembers(String role) throws IOException {
|
||||
return root.createRequest()
|
||||
.withUrlPath(api("/members"))
|
||||
.with("role", role)
|
||||
.toIterable(GHUser[].class, item -> item.wrapUp(root));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,7 +187,20 @@ public class GHTeam implements Refreshable {
|
||||
* the io exception
|
||||
*/
|
||||
public PagedIterable<GHUser> listMembers() throws IOException {
|
||||
return root.createRequest().withUrlPath(api("/members")).toIterable(GHUser[].class, item -> item.wrapUp(root));
|
||||
return listMembers("all");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,7 +223,7 @@ public class GHTeam implements Refreshable {
|
||||
*/
|
||||
public boolean hasMember(GHUser user) {
|
||||
try {
|
||||
root.createRequest().withUrlPath("/teams/" + id + "/members/" + user.getLogin()).send();
|
||||
root.createRequest().withUrlPath("/teams/" + getId() + "/members/" + user.getLogin()).send();
|
||||
return true;
|
||||
} catch (IOException ignore) {
|
||||
return false;
|
||||
@@ -302,7 +356,22 @@ public class GHTeam implements Refreshable {
|
||||
}
|
||||
|
||||
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 +390,28 @@ public class GHTeam implements Refreshable {
|
||||
public void refresh() throws IOException {
|
||||
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
|
||||
* @return a builder to continue with building
|
||||
*/
|
||||
public GHTeamBuilder parentTeamId(int parentTeamId) {
|
||||
public GHTeamBuilder parentTeamId(long parentTeamId) {
|
||||
this.builder.with("parent_team_id", parentTeamId);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -173,6 +173,19 @@ public class GHUser extends GHPerson {
|
||||
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) {
|
||||
for (GHUser f : users)
|
||||
f.root = root;
|
||||
|
||||
82
src/main/java/org/kohsuke/github/GHVerification.java
Normal file
82
src/main/java/org/kohsuke/github/GHVerification.java
Normal file
@@ -0,0 +1,82 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
|
||||
/**
|
||||
* The commit/tag can be signed by user. This object holds the verification status. Whether the Commit/Tag is signed or
|
||||
* not.
|
||||
*
|
||||
* @see <a href="https://developer.github.com/v3/git/tags/#signature-verification-object">tags signature
|
||||
* verificatiion</a>
|
||||
* @see <a href="https://developer.github.com/v3/git/commits/#signature-verification-object">commits signature
|
||||
* verificatiion</a>
|
||||
*
|
||||
* @author Sourabh Sarvotham Parkala
|
||||
*/
|
||||
@SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" },
|
||||
justification = "JSON API")
|
||||
public class GHVerification {
|
||||
private String signature, payload;
|
||||
private boolean verified;
|
||||
private Reason reason;
|
||||
|
||||
/**
|
||||
* Indicates whether GitHub considers the signature in this commit to be verified.
|
||||
*
|
||||
* @return true if the signature is valid else returns false.
|
||||
*/
|
||||
public boolean isVerified() {
|
||||
return verified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets reason for verification value.
|
||||
*
|
||||
* @return return reason of type {@link Reason}, such as "valid" or "unsigned". The possible values can be found in
|
||||
* {@link Reason}}
|
||||
*/
|
||||
public Reason getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets signature used for the verification.
|
||||
*
|
||||
* @return null if not signed else encoded signature.
|
||||
*/
|
||||
public String getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the payload that was signed.
|
||||
*
|
||||
* @return null if not signed else encoded signature.
|
||||
*/
|
||||
public String getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* The possible values for reason in verification object from github.
|
||||
*
|
||||
* @see <a href="https://developer.github.com/v3/repos/commits/#signature-verification-object">List of possible
|
||||
* reason values</a>
|
||||
* @author Sourabh Sarvotham Parkala
|
||||
*/
|
||||
public enum Reason {
|
||||
EXPIRED_KEY,
|
||||
NOT_SIGNING_KEY,
|
||||
GPGVERIFY_ERROR,
|
||||
GPGVERIFY_UNAVAILABLE,
|
||||
UNSIGNED,
|
||||
UNKNOWN_SIGNATURE_TYPE,
|
||||
NO_USER,
|
||||
UNVERIFIED_EMAIL,
|
||||
BAD_EMAIL,
|
||||
UNKNOWN_KEY,
|
||||
MALFORMED_SIGNATURE,
|
||||
INVALID,
|
||||
VALID
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@
|
||||
*/
|
||||
package org.kohsuke.github;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectReader;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
|
||||
|
||||
import java.io.*;
|
||||
@@ -371,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
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
*/
|
||||
@Nonnull
|
||||
public GHRateLimit getRateLimit() throws IOException {
|
||||
return client.getRateLimit();
|
||||
}
|
||||
@@ -386,8 +396,11 @@ public class GitHub {
|
||||
* GitHub Enterprise) or if no requests have been made.
|
||||
*
|
||||
* @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() {
|
||||
return client.lastRateLimit();
|
||||
}
|
||||
@@ -398,10 +411,13 @@ public class GitHub {
|
||||
* @return the current rate limit data.
|
||||
* @throws IOException
|
||||
* if we couldn't get the current rate limit data.
|
||||
* @deprecated implement a {@link RateLimitChecker} and add it via
|
||||
* {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}.
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
public GHRateLimit rateLimit() throws IOException {
|
||||
return client.rateLimit();
|
||||
return client.rateLimit(RateLimitTarget.CORE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -411,7 +427,7 @@ public class GitHub {
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
*/
|
||||
@WithBridgeMethods(GHUser.class)
|
||||
@WithBridgeMethods(value = GHUser.class)
|
||||
public GHMyself getMyself() throws IOException {
|
||||
client.requireCredential();
|
||||
synchronized (this) {
|
||||
@@ -516,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
|
||||
* the name
|
||||
@@ -527,9 +543,10 @@ public class GitHub {
|
||||
*/
|
||||
public GHRepository getRepository(String name) throws IOException {
|
||||
String[] tokens = name.split("/");
|
||||
return createRequest().withUrlPath("/repos/" + tokens[0] + '/' + tokens[1])
|
||||
.fetch(GHRepository.class)
|
||||
.wrap(this);
|
||||
if (tokens.length < 2) {
|
||||
throw new IllegalArgumentException("Repository name must be in format owner/repo");
|
||||
}
|
||||
return GHRepository.read(this, tokens[0], tokens[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -724,7 +741,11 @@ public class GitHub {
|
||||
* @return the team
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
*
|
||||
* @deprecated Use {@link GHOrganization#getTeam(long)}
|
||||
* @see <a href= "https://developer.github.com/v3/teams/#get-team-legacy">deprecation notice</a>
|
||||
*/
|
||||
@Deprecated
|
||||
public GHTeam getTeam(int id) throws IOException {
|
||||
return createRequest().withUrlPath("/teams/" + id).fetch(GHTeam.class).wrapUp(this);
|
||||
}
|
||||
@@ -1186,6 +1207,32 @@ public class GitHub {
|
||||
"UTF-8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not use this method. This method will be removed and should never have been needed in the first place.
|
||||
*
|
||||
* @return an {@link ObjectWriter} instance that can be further configured.
|
||||
* @deprecated DO NOT USE THIS METHOD. Provided for backward compatibility with projects that did their own jackson
|
||||
* mapping of this project's data objects, such as Jenkins Blue Ocean.
|
||||
*/
|
||||
@Deprecated
|
||||
@Nonnull
|
||||
public static ObjectWriter getMappingObjectWriter() {
|
||||
return GitHubClient.getMappingObjectWriter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not use this method. This method will be removed and should never have been needed in the first place.
|
||||
*
|
||||
* @return an {@link ObjectReader} instance that can be further configured.
|
||||
* @deprecated DO NOT USE THIS METHOD. Provided for backward compatibility with projects that did their own jackson
|
||||
* mapping of this project's data objects, such as Jenkins Blue Ocean.
|
||||
*/
|
||||
@Deprecated
|
||||
@Nonnull
|
||||
public static ObjectReader getMappingObjectReader() {
|
||||
return GitHubClient.getMappingObjectReader(GitHub.offline());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
GitHubClient getClient() {
|
||||
return client;
|
||||
|
||||
@@ -9,7 +9,6 @@ import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Properties;
|
||||
@@ -359,6 +358,18 @@ public class GitHubBuilder implements Cloneable {
|
||||
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}.
|
||||
* <p>
|
||||
@@ -377,15 +388,15 @@ public class GitHubBuilder implements Cloneable {
|
||||
* request.
|
||||
* </p>
|
||||
*
|
||||
* @param coreRateLimitChecker
|
||||
* the {@link RateLimitChecker} for core GitHub API requests
|
||||
* @param rateLimitChecker
|
||||
* the {@link RateLimitChecker} for requests
|
||||
* @param rateLimitTarget
|
||||
* the {@link RateLimitTarget} specifying which rate limit record to check
|
||||
* @return the git hub builder
|
||||
*/
|
||||
public GitHubBuilder withRateLimitChecker(@Nonnull RateLimitChecker coreRateLimitChecker) {
|
||||
this.rateLimitChecker = new GitHubRateLimitChecker(coreRateLimitChecker,
|
||||
RateLimitChecker.NONE,
|
||||
RateLimitChecker.NONE,
|
||||
RateLimitChecker.NONE);
|
||||
public GitHubBuilder withRateLimitChecker(@Nonnull RateLimitChecker rateLimitChecker,
|
||||
@Nonnull RateLimitTarget rateLimitTarget) {
|
||||
this.rateLimitChecker = this.rateLimitChecker.with(rateLimitChecker, rateLimitTarget);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.fasterxml.jackson.databind.ObjectReader;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
|
||||
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.IOException;
|
||||
@@ -19,15 +19,15 @@ import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.TimeZone;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -44,7 +44,7 @@ import static java.util.logging.Level.*;
|
||||
* A GitHub API Client
|
||||
* <p>
|
||||
* 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>
|
||||
*/
|
||||
abstract class GitHubClient {
|
||||
@@ -70,18 +70,18 @@ abstract class GitHubClient {
|
||||
|
||||
private HttpConnector connector;
|
||||
|
||||
private final Object headerRateLimitLock = new Object();
|
||||
private GHRateLimit headerRateLimit = null;
|
||||
private volatile GHRateLimit rateLimit = null;
|
||||
private final Object rateLimitLock = new Object();
|
||||
|
||||
@Nonnull
|
||||
private GHRateLimit rateLimit = GHRateLimit.DEFAULT;
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(GitHubClient.class.getName());
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
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'",
|
||||
"yyyy-MM-dd'T'HH:mm:ss.S'Z'" // GitHub App endpoints return a different date format
|
||||
};
|
||||
private static final DateTimeFormatter DATE_TIME_PARSER_SLASHES = DateTimeFormatter
|
||||
.ofPattern("yyyy/MM/dd HH:mm:ss Z");
|
||||
|
||||
static {
|
||||
MAPPER.setVisibility(new VisibilityChecker.Std(NONE, NONE, NONE, NONE, ANY));
|
||||
@@ -141,20 +141,20 @@ abstract class GitHubClient {
|
||||
}
|
||||
|
||||
private <T> T fetch(Class<T> type, String urlPath) throws IOException {
|
||||
return this
|
||||
.sendRequest(GitHubRequest.newBuilder().withApiUrl(getApiUrl()).withUrlPath(urlPath).build(),
|
||||
(responseInfo) -> GitHubResponse.parseBody(responseInfo, type))
|
||||
.body();
|
||||
GitHubRequest request = GitHubRequest.newBuilder().withApiUrl(getApiUrl()).withUrlPath(urlPath).build();
|
||||
return this.sendRequest(request, (responseInfo) -> GitHubResponse.parseBody(responseInfo, type)).body();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the credential is valid.
|
||||
* Ensures that the credential for this client is valid.
|
||||
*
|
||||
* @return the boolean
|
||||
*/
|
||||
public boolean isCredentialValid() {
|
||||
try {
|
||||
fetch(GHUser.class, "/user");
|
||||
// If 404, ratelimit returns a default value.
|
||||
// This works as credential test because invalid credentials returns 401, not 404
|
||||
getRateLimit();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
if (LOGGER.isLoggable(FINE))
|
||||
@@ -206,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
|
||||
* @throws IOException
|
||||
* the io exception
|
||||
*/
|
||||
@Nonnull
|
||||
public GHRateLimit getRateLimit() throws IOException {
|
||||
GHRateLimit rateLimit;
|
||||
return getRateLimit(RateLimitTarget.NONE);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
GHRateLimit getRateLimit(@Nonnull RateLimitTarget rateLimitTarget) throws IOException {
|
||||
GHRateLimit result;
|
||||
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) {
|
||||
// GitHub Enterprise doesn't have the rate limit
|
||||
// return a default rate limit that
|
||||
rateLimit = GHRateLimit.Unknown();
|
||||
}
|
||||
// For some versions of GitHub Enterprise, the rate_limit endpoint returns a 404.
|
||||
LOGGER.log(FINE, "/rate_limit returned 404 Not Found.");
|
||||
|
||||
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
|
||||
* GitHub Enterprise) or if no requests have been made.
|
||||
* Returns the most recently observed rate limit data.
|
||||
*
|
||||
* @return the most recently observed rate limit data or {@code null}.
|
||||
*/
|
||||
@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.
|
||||
* Generally, instead of calling this you should implement a {@link RateLimitChecker} or call
|
||||
*
|
||||
* @return the current rate limit data.
|
||||
* @throws IOException
|
||||
* if we couldn't get the current rate limit data.
|
||||
* @return the most recently observed rate limit data. This may include expired or
|
||||
* {@link GHRateLimit.UnknownLimitRecord} entries.
|
||||
* @deprecated implement a {@link RateLimitChecker} and add it via
|
||||
* {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}.
|
||||
*/
|
||||
@Nonnull
|
||||
public GHRateLimit rateLimit() throws IOException {
|
||||
synchronized (headerRateLimitLock) {
|
||||
if (headerRateLimit != null && !headerRateLimit.isExpired()) {
|
||||
return headerRateLimit;
|
||||
@Deprecated
|
||||
GHRateLimit lastRateLimit() {
|
||||
synchronized (rateLimitLock) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -335,39 +388,43 @@ abstract class GitHubClient {
|
||||
|
||||
GitHubResponse.ResponseInfo responseInfo = null;
|
||||
try {
|
||||
if (LOGGER.isLoggable(FINE)) {
|
||||
LOGGER.log(FINE,
|
||||
"GitHub API request [" + (login == null ? "anonymous" : login) + "]: " + request.method()
|
||||
+ " " + request.url().toString());
|
||||
try {
|
||||
if (LOGGER.isLoggable(FINE)) {
|
||||
LOGGER.log(FINE,
|
||||
"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);
|
||||
|
||||
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().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);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(responseInfo);
|
||||
}
|
||||
|
||||
handleLimitingErrors(responseInfo);
|
||||
|
||||
} while (--retries >= 0);
|
||||
|
||||
throw new GHIOException("Ran out of retries for URL: " + request.url().toString());
|
||||
@@ -496,58 +553,25 @@ abstract class GitHubClient {
|
||||
}
|
||||
|
||||
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 {
|
||||
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);
|
||||
} catch (NumberFormatException e) {
|
||||
if (LOGGER.isLoggable(FINEST)) {
|
||||
LOGGER.log(FINEST, "Malformed X-RateLimit-Limit header value " + limitString, e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
||||
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);
|
||||
} 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)) {
|
||||
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 {
|
||||
@@ -567,23 +591,6 @@ abstract class GitHubClient {
|
||||
"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 String rate_limit_url;
|
||||
|
||||
@@ -633,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) {
|
||||
try {
|
||||
return s == null ? null : new URL(s);
|
||||
@@ -675,22 +651,24 @@ abstract class GitHubClient {
|
||||
static Date parseDate(String timestamp) {
|
||||
if (timestamp == null)
|
||||
return null;
|
||||
for (String f : TIME_FORMATS) {
|
||||
try {
|
||||
SimpleDateFormat df = new SimpleDateFormat(f);
|
||||
df.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
return df.parse(timestamp);
|
||||
} catch (ParseException e) {
|
||||
// try next
|
||||
}
|
||||
|
||||
return Date.from(parseInstant(timestamp));
|
||||
}
|
||||
|
||||
static Instant parseInstant(String timestamp) {
|
||||
if (timestamp == null)
|
||||
return null;
|
||||
|
||||
if (timestamp.charAt(4) == '/') {
|
||||
// Unsure where this is used, but retained for compatibility.
|
||||
return Instant.from(DATE_TIME_PARSER_SLASHES.parse(timestamp));
|
||||
} else {
|
||||
return Instant.from(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(timestamp));
|
||||
}
|
||||
throw new IllegalStateException("Unable to parse the timestamp: " + timestamp);
|
||||
}
|
||||
|
||||
static String printDate(Date dt) {
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||
df.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
return df.format(dt);
|
||||
return DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(dt.getTime()).truncatedTo(ChronoUnit.SECONDS));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,7 +24,7 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
* A GitHub API Client for HttpUrlConnection
|
||||
* <p>
|
||||
* 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>
|
||||
* 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());
|
||||
|
||||
@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.InterruptedIOException;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* A GitHub API Rate Limit Checker called before each request. This class provides the basic infrastructure for calling
|
||||
* 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.
|
||||
* A GitHub API Rate Limit Checker called before each request.
|
||||
*
|
||||
* <p>
|
||||
* GitHub allots a certain number of requests to each user or application per period of time (usually per hour). The
|
||||
* number of requests remaining is returned in the response header and can also be requested using
|
||||
* {@link GitHub#getRateLimit()}. This requests per interval is referred to as the "rate limit".
|
||||
* GitHub allots a certain number of requests to each user or application per period of time. The number of requests
|
||||
* remaining and the time when the number will be reset is returned in the response header and can also be requested
|
||||
* using {@link GitHub#getRateLimit()}. The "requests per interval" is referred to as the "rate limit".
|
||||
* </p>
|
||||
* <p>
|
||||
* 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
|
||||
* met.
|
||||
* Different parts of the GitHub API have separate rate limits, but most of REST API uses {@link RateLimitTarget#CORE}.
|
||||
* Checking your rate limit using {@link GitHub#getRateLimit()} does not effect your rate limit. GitHub prefers that
|
||||
* clients stop before exceeding their rate limit rather than stopping after they exceed it.
|
||||
* </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.
|
||||
* This class provides the infrastructure for calling the appropriate {@link RateLimitChecker} before each request and
|
||||
* 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>
|
||||
*/
|
||||
class GitHubRateLimitChecker {
|
||||
@@ -39,6 +41,8 @@ class GitHubRateLimitChecker {
|
||||
@Nonnull
|
||||
private final RateLimitChecker integrationManifest;
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(GitHubRateLimitChecker.class.getName());
|
||||
|
||||
GitHubRateLimitChecker() {
|
||||
this(RateLimitChecker.NONE, RateLimitChecker.NONE, RateLimitChecker.NONE, RateLimitChecker.NONE);
|
||||
}
|
||||
@@ -48,40 +52,57 @@ class GitHubRateLimitChecker {
|
||||
@Nonnull RateLimitChecker graphql,
|
||||
@Nonnull RateLimitChecker integrationManifest) {
|
||||
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.graphql = Objects.requireNonNull(graphql);
|
||||
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
|
||||
* request.
|
||||
* <p>
|
||||
* This method does not do the actual check. Instead it select the appropriate {@link RateLimitChecker} and
|
||||
* {@link GHRateLimit.Record} for the current request's urlPath. If the {@link RateLimitChecker} for this the
|
||||
* current request's urlPath is {@link RateLimitChecker#NONE} the rate limit is not checked. If not, it calls
|
||||
* {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)}. which decides if the rate limit has been
|
||||
* exceeded and then sleeps for as long is it choose.
|
||||
* This method does not do the actual check. Instead it selects the appropriate {@link RateLimitChecker} and
|
||||
* {@link GHRateLimit.Record} for the current request's {@link RateLimitTarget}. It then calls
|
||||
* {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)}.
|
||||
* </p>
|
||||
* <p>
|
||||
* It is up to the {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} 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
|
||||
* not, that method will return {@code false}.
|
||||
* It is up to {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} to which decide if the rate limit
|
||||
* has been exceeded. If it has, {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} will sleep for as
|
||||
* long is it chooses and then return {@code true}. If not, that method will return {@code false}.
|
||||
* </p>
|
||||
* <p>
|
||||
* As long as {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} returns {@code true}, this method
|
||||
* 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)} to apply any number of strategies to
|
||||
* controlling the speed at which requests are made. When it returns {@code false} this method will return and the
|
||||
* request will be sent.
|
||||
* {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} again. This looping allows different
|
||||
* {@link RateLimitChecker} implementations to apply any number of strategies to controlling the speed at which
|
||||
* requests are made.
|
||||
* </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>
|
||||
*
|
||||
* @param client
|
||||
@@ -92,14 +113,14 @@ class GitHubRateLimitChecker {
|
||||
* if there is an I/O error
|
||||
*/
|
||||
void checkRateLimit(GitHubClient client, GitHubRequest request) throws IOException {
|
||||
RateLimitChecker guard = selectChecker(request.urlPath());
|
||||
RateLimitChecker guard = selectChecker(request.rateLimitTarget());
|
||||
if (guard == RateLimitChecker.NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For the first rate limit, accept the current limit if a valid one is already present.
|
||||
GHRateLimit rateLimit = client.rateLimit();
|
||||
GHRateLimit.Record rateLimitRecord = rateLimit.getRecordForUrlPath(request.urlPath());
|
||||
GHRateLimit rateLimit = client.rateLimit(request.rateLimitTarget());
|
||||
GHRateLimit.Record rateLimitRecord = rateLimit.getRecord(request.rateLimitTarget());
|
||||
long waitCount = 0;
|
||||
try {
|
||||
while (guard.checkRateLimit(rateLimitRecord, waitCount)) {
|
||||
@@ -112,8 +133,8 @@ class GitHubRateLimitChecker {
|
||||
Thread.sleep(1000);
|
||||
|
||||
// After the first wait, always request a new rate limit from the server.
|
||||
rateLimit = client.getRateLimit();
|
||||
rateLimitRecord = rateLimit.getRecordForUrlPath(request.urlPath());
|
||||
rateLimit = client.getRateLimit(request.rateLimitTarget());
|
||||
rateLimitRecord = rateLimit.getRecord(request.rateLimitTarget());
|
||||
}
|
||||
} catch (InterruptedException 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
|
||||
* {@link GHRateLimit#getRecordForUrlPath(String)}.
|
||||
* Gets the appropriate {@link RateLimitChecker} for a particular target.
|
||||
*
|
||||
* @param urlPath
|
||||
* the url path of the request
|
||||
* @return the {@link RateLimitChecker} for a url path.
|
||||
* Analogous with {@link GHRateLimit#getRecord(RateLimitTarget)}.
|
||||
*
|
||||
* @param rateLimitTarget
|
||||
* the rate limit to check
|
||||
* @return the {@link RateLimitChecker} for a particular target
|
||||
*/
|
||||
@Nonnull
|
||||
private RateLimitChecker selectChecker(@Nonnull String urlPath) {
|
||||
if (urlPath.equals("/rate_limit")) {
|
||||
private RateLimitChecker selectChecker(@Nonnull RateLimitTarget rateLimitTarget) {
|
||||
if (rateLimitTarget == RateLimitTarget.NONE) {
|
||||
return RateLimitChecker.NONE;
|
||||
} else if (urlPath.startsWith("/search")) {
|
||||
} else if (rateLimitTarget == RateLimitTarget.CORE) {
|
||||
return core;
|
||||
} else if (rateLimitTarget == RateLimitTarget.SEARCH) {
|
||||
return search;
|
||||
} else if (urlPath.startsWith("/graphql")) {
|
||||
} else if (rateLimitTarget == RateLimitTarget.GRAPHQL) {
|
||||
return graphql;
|
||||
} else if (urlPath.startsWith("/app-manifests")) {
|
||||
} else if (rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST) {
|
||||
return integrationManifest;
|
||||
} 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 urlPath;
|
||||
private final String method;
|
||||
private final RateLimitTarget rateLimitTarget;
|
||||
private final InputStream body;
|
||||
private final boolean forceBody;
|
||||
|
||||
@@ -56,6 +57,7 @@ class GitHubRequest {
|
||||
@Nonnull String apiUrl,
|
||||
@Nonnull String urlPath,
|
||||
@Nonnull String method,
|
||||
@Nonnull RateLimitTarget rateLimitTarget,
|
||||
@CheckForNull InputStream body,
|
||||
boolean forceBody) throws MalformedURLException {
|
||||
this.args = Collections.unmodifiableList(new ArrayList<>(args));
|
||||
@@ -64,6 +66,7 @@ class GitHubRequest {
|
||||
this.apiUrl = apiUrl;
|
||||
this.urlPath = urlPath;
|
||||
this.method = method;
|
||||
this.rateLimitTarget = rateLimitTarget;
|
||||
this.body = body;
|
||||
this.forceBody = forceBody;
|
||||
String tailApiUrl = buildTailApiUrl();
|
||||
@@ -119,6 +122,16 @@ class GitHubRequest {
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rate limit target for this request.
|
||||
*
|
||||
* @return the rate limit to use for this request.
|
||||
*/
|
||||
@Nonnull
|
||||
public RateLimitTarget rateLimitTarget() {
|
||||
return rateLimitTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* The arguments for this request. Depending on the {@link #method()} and {@code #inBody()} these maybe added to the
|
||||
* url or to the request body.
|
||||
@@ -217,7 +230,15 @@ class GitHubRequest {
|
||||
* @return a {@link Builder} based on this request.
|
||||
*/
|
||||
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() {
|
||||
@@ -281,6 +302,10 @@ class GitHubRequest {
|
||||
*/
|
||||
@Nonnull
|
||||
private String method;
|
||||
|
||||
@Nonnull
|
||||
private RateLimitTarget rateLimitTarget;
|
||||
|
||||
private InputStream body;
|
||||
private boolean forceBody;
|
||||
|
||||
@@ -294,6 +319,7 @@ class GitHubRequest {
|
||||
GitHubClient.GITHUB_URL,
|
||||
"/",
|
||||
"GET",
|
||||
RateLimitTarget.CORE,
|
||||
null,
|
||||
false);
|
||||
}
|
||||
@@ -304,6 +330,7 @@ class GitHubRequest {
|
||||
@Nonnull String apiUrl,
|
||||
@Nonnull String urlPath,
|
||||
@Nonnull String method,
|
||||
@Nonnull RateLimitTarget rateLimitTarget,
|
||||
@CheckForNull @WillClose InputStream body,
|
||||
boolean forceBody) {
|
||||
this.args = new ArrayList<>(args);
|
||||
@@ -312,6 +339,7 @@ class GitHubRequest {
|
||||
this.apiUrl = apiUrl;
|
||||
this.urlPath = urlPath;
|
||||
this.method = method;
|
||||
this.rateLimitTarget = rateLimitTarget;
|
||||
this.body = body;
|
||||
this.forceBody = forceBody;
|
||||
}
|
||||
@@ -324,7 +352,15 @@ class GitHubRequest {
|
||||
* if the GitHub API URL cannot be constructed
|
||||
*/
|
||||
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
|
||||
* @param 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);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -363,8 +401,11 @@ class GitHubRequest {
|
||||
* @return the request builder
|
||||
*/
|
||||
public B withHeader(String name, String value) {
|
||||
setHeader(name, value);
|
||||
return (B) this;
|
||||
String oldValue = headers.get(name);
|
||||
if (!StringUtils.isBlank(oldValue)) {
|
||||
value = oldValue + ", " + value;
|
||||
}
|
||||
return setHeader(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -562,6 +603,18 @@ class GitHubRequest {
|
||||
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.
|
||||
*
|
||||
@@ -583,13 +636,17 @@ class GitHubRequest {
|
||||
* when needing to set query parameters on requests methods that don't usually have them, such as
|
||||
* {@link GHRelease#uploadAsset(String, InputStream, String)}.
|
||||
*
|
||||
* @param urlOrPath
|
||||
* @param rawUrlPath
|
||||
* the content type
|
||||
* @return the request builder
|
||||
*/
|
||||
B setRawUrlPath(String urlOrPath) {
|
||||
Objects.requireNonNull(urlOrPath);
|
||||
this.urlPath = urlOrPath;
|
||||
B setRawUrlPath(@Nonnull String rawUrlPath) {
|
||||
Objects.requireNonNull(rawUrlPath);
|
||||
// This method should only work for full urls, which must start with "http"
|
||||
if (!rawUrlPath.startsWith("http")) {
|
||||
throw new GHException("Raw URL must start with 'http'");
|
||||
}
|
||||
this.urlPath = rawUrlPath;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@@ -603,10 +660,10 @@ class GitHubRequest {
|
||||
* the content type
|
||||
* @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
|
||||
if (urlPathItems.length == 1 && !urlPathItems[0].startsWith("/")) {
|
||||
return setRawUrlPath(urlPathItems[0]);
|
||||
if (urlPathItems.length == 0 && !urlPath.startsWith("/")) {
|
||||
return setRawUrlPath(urlPath);
|
||||
}
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
String tailUrlPath = String.join("/", urlPathItems);
|
||||
|
||||
if (this.urlPath.endsWith("/")) {
|
||||
tailUrlPath = StringUtils.stripStart(tailUrlPath, "/");
|
||||
} else {
|
||||
tailUrlPath = StringUtils.prependIfMissing(tailUrlPath, "/");
|
||||
String tailUrlPath = urlPath;
|
||||
if (urlPathItems.length != 0) {
|
||||
tailUrlPath += "/" + String.join("/", urlPathItems);
|
||||
}
|
||||
|
||||
this.urlPath += urlPathEncode(tailUrlPath);
|
||||
tailUrlPath = StringUtils.prependIfMissing(tailUrlPath, "/");
|
||||
|
||||
this.urlPath = urlPathEncode(tailUrlPath);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@@ -658,7 +714,7 @@ class GitHubRequest {
|
||||
*/
|
||||
private static String urlPathEncode(String value) {
|
||||
try {
|
||||
return new URI(null, null, value, null, null).toString();
|
||||
return new URI(null, null, value, null, null).toASCIIString();
|
||||
} catch (URISyntaxException ex) {
|
||||
throw new AssertionError(ex);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.databind.InjectableValues;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -12,9 +14,12 @@ import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.CheckForNull;
|
||||
import javax.annotation.Nonnull;
|
||||
@@ -30,6 +35,8 @@ import javax.annotation.Nonnull;
|
||||
*/
|
||||
class GitHubResponse<T> {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(GitHubResponse.class.getName());
|
||||
|
||||
private final int statusCode;
|
||||
|
||||
@Nonnull
|
||||
@@ -71,9 +78,14 @@ class GitHubResponse<T> {
|
||||
@CheckForNull
|
||||
static <T> T parseBody(ResponseInfo responseInfo, Class<T> type) throws IOException {
|
||||
|
||||
if (responseInfo.statusCode() == HttpURLConnection.HTTP_NO_CONTENT && type != null && type.isArray()) {
|
||||
// no content
|
||||
return type.cast(Array.newInstance(type.getComponentType(), 0));
|
||||
if (responseInfo.statusCode() == HttpURLConnection.HTTP_NO_CONTENT) {
|
||||
if (type != null && type.isArray()) {
|
||||
// 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();
|
||||
@@ -82,9 +94,10 @@ class GitHubResponse<T> {
|
||||
inject.addValue(ResponseInfo.class, responseInfo);
|
||||
|
||||
return GitHubClient.getMappingObjectReader(responseInfo).forType(type).readValue(data);
|
||||
} catch (JsonMappingException e) {
|
||||
String message = "Failed to deserialize " + data;
|
||||
throw new IOException(message, e);
|
||||
} catch (JsonMappingException | JsonParseException e) {
|
||||
String message = "Failed to deserialize: " + data;
|
||||
LOGGER.log(Level.FINE, message);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,9 +120,10 @@ class GitHubResponse<T> {
|
||||
String data = responseInfo.getBodyAsString();
|
||||
try {
|
||||
return GitHubClient.getMappingObjectReader(responseInfo).withValueToUpdate(instance).readValue(data);
|
||||
} catch (JsonMappingException e) {
|
||||
String message = "Failed to deserialize " + data;
|
||||
throw new IOException(message, e);
|
||||
} catch (JsonMappingException | JsonParseException e) {
|
||||
String message = "Failed to deserialize: " + data;
|
||||
LOGGER.log(Level.FINE, message);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +218,10 @@ class GitHubResponse<T> {
|
||||
* Initial response information supplied to a {@link BodyHandler} when a response is initially received and before
|
||||
* the body is processed.
|
||||
*/
|
||||
static abstract class ResponseInfo {
|
||||
static abstract class ResponseInfo implements Closeable {
|
||||
|
||||
private static final Comparator<String> nullableCaseInsensitiveComparator = Comparator
|
||||
.nullsFirst(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
private final int statusCode;
|
||||
@Nonnull
|
||||
@@ -217,7 +234,12 @@ class GitHubResponse<T> {
|
||||
@Nonnull Map<String, List<String>> headers) {
|
||||
this.request = request;
|
||||
this.statusCode = statusCode;
|
||||
this.headers = Collections.unmodifiableMap(new HashMap<>(headers));
|
||||
|
||||
// Response header field names must be case-insensitive.
|
||||
TreeMap<String, List<String>> caseInsensitiveMap = new TreeMap<>(nullableCaseInsensitiveComparator);
|
||||
caseInsensitiveMap.putAll(headers);
|
||||
|
||||
this.headers = Collections.unmodifiableMap(caseInsensitiveMap);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,14 +320,11 @@ class GitHubResponse<T> {
|
||||
* @throws IOException
|
||||
* if an I/O Exception occurs.
|
||||
*/
|
||||
@Nonnull
|
||||
String getBodyAsString() throws IOException {
|
||||
InputStreamReader r = null;
|
||||
try {
|
||||
r = new InputStreamReader(this.bodyStream(), StandardCharsets.UTF_8);
|
||||
return IOUtils.toString(r);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(r);
|
||||
}
|
||||
r = new InputStreamReader(this.bodyStream(), StandardCharsets.UTF_8);
|
||||
return IOUtils.toString(r);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import javax.annotation.CheckForNull;
|
||||
|
||||
/**
|
||||
* Represents a user in Git who authors/commits a commit.
|
||||
* <p>
|
||||
@@ -15,10 +17,10 @@ import java.util.Date;
|
||||
@SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" },
|
||||
justification = "JSON API")
|
||||
public class GitUser {
|
||||
private String name, email, date;
|
||||
private String name, email, date, username;
|
||||
|
||||
/**
|
||||
* Gets name.
|
||||
* Gets the git user name for an author or committer on a git commit.
|
||||
*
|
||||
* @return Human readable name of the user, such as "Kohsuke Kawaguchi"
|
||||
*/
|
||||
@@ -27,14 +29,26 @@ public class GitUser {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets email.
|
||||
* Gets the git email for an author or committer on a git commit.
|
||||
*
|
||||
* @return E -mail address, such as "foo@example.com"
|
||||
* @return E-mail address, such as "foo@example.com"
|
||||
*/
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets username. Note: it presents only in events.
|
||||
*
|
||||
* @return GitHub username
|
||||
*/
|
||||
@Preview
|
||||
@Deprecated
|
||||
@CheckForNull
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets date.
|
||||
*
|
||||
|
||||
@@ -5,7 +5,6 @@ import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -8,6 +8,20 @@ package org.kohsuke.github;
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class Previews {
|
||||
/**
|
||||
* Check-runs and check-suites
|
||||
*
|
||||
* @see <a href="https://developer.github.com/v3/previews/#check-runs-and-check-suites-api">GitHub API Previews</a>
|
||||
*/
|
||||
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
|
||||
@@ -23,6 +37,14 @@ class Previews {
|
||||
*/
|
||||
static final String GAMBIT = "application/vnd.github.gambit-preview+json";
|
||||
|
||||
/**
|
||||
* List branches or pull requests for a commit
|
||||
*
|
||||
* @see <a href="https://developer.github.com/v3/previews/#list-branches-or-pull-requests-for-a-commit">GitHub API
|
||||
* Previews</a>
|
||||
*/
|
||||
static final String GROOT = "application/vnd.github.groot-preview+json";
|
||||
|
||||
/**
|
||||
* Manage projects
|
||||
*
|
||||
@@ -30,6 +52,13 @@ class Previews {
|
||||
*/
|
||||
static final String INERTIA = "application/vnd.github.inertia-preview+json";
|
||||
|
||||
/**
|
||||
* Update a pull request branch
|
||||
*
|
||||
* @see <a href="https://developer.github.com/v3/previews/#update-a-pull-request-branch">GitHub API Previews</a>
|
||||
*/
|
||||
static final String LYDIAN = "application/vnd.github.lydian-preview+json";
|
||||
|
||||
/**
|
||||
* Require multiple approving reviews
|
||||
*
|
||||
@@ -52,6 +81,14 @@ class Previews {
|
||||
*/
|
||||
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
|
||||
*
|
||||
@@ -72,4 +109,5 @@ class Previews {
|
||||
* @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";
|
||||
|
||||
}
|
||||
|
||||
@@ -5,20 +5,17 @@ import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* A GitHub API Rate Limit Checker called before each request
|
||||
*
|
||||
* <p>
|
||||
* GitHub allots a certain number of requests to each user or application per period of time (usually per hour). The
|
||||
* number of requests remaining is returned in the response header and can also be requested using
|
||||
* {@link GitHub#getRateLimit()}. This requests per interval is referred to as the "rate limit".
|
||||
* GitHub allots a certain number of requests to each user or application per period of time. The number of requests
|
||||
* remaining and the time when the number will be reset is returned in the response header and can also be requested
|
||||
* using {@link GitHub#getRateLimit()}. The "requests per interval" is referred to as the "rate limit".
|
||||
* </p>
|
||||
* <p>
|
||||
* 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
|
||||
* met.
|
||||
* </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 {
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <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.
|
||||
* </p>
|
||||
* <p>
|
||||
* It is important to remember that rate limit reset times are only accurate to the second. Trying to sleep to
|
||||
* exactly the reset time would be likely to produce worse behavior rather than better. For this reason
|
||||
* {@link GitHubRateLimitChecker} may choose to add more sleep times when a checker indicates the rate limit was
|
||||
* exceeded.
|
||||
* As long as this method returns {@code true} it is guaranteed that {@link GitHubRateLimitChecker} will retrieve
|
||||
* updated rate limit information and call this method again with {@code count} incremented by one. When this
|
||||
* checker returns {@code false}, the calling {@link GitHubRateLimitChecker} will let the request continue.
|
||||
* </p>
|
||||
* <p>
|
||||
* As long as this method returns {@code true} it is guaranteed that {@link GitHubRateLimitChecker} will get updated
|
||||
* rate limit information and call this method again with {@code count} incremented by one. After this method
|
||||
* returns {@code true} at least once, the calling {@link GitHubRateLimitChecker} may choose to wait some additional
|
||||
* period of time between calls to this checker.
|
||||
* Rate limit reset times are only accurate to the second. Trying to sleep to exactly the reset time could result in
|
||||
* requests being sent before the new rate limit was available. For this reason, if this method returned
|
||||
* {@code true} at least once for a particular request, {@link GitHubRateLimitChecker} may choose to sleep for some
|
||||
* small additional between calls and before letting the request continue.
|
||||
* </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
|
||||
* the current {@link GHRateLimit.Record} to check against.
|
||||
* @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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* The interface Refreshable.
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
*/
|
||||
package org.kohsuke.github;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
@@ -108,7 +111,10 @@ class Requester extends GitHubRequest.Builder<Requester> {
|
||||
* the io exception
|
||||
*/
|
||||
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')
|
||||
continue;
|
||||
|
||||
Buffer buffer = new Buffer();
|
||||
buffer.writeUtf8(s, 0, i);
|
||||
buffer.writeUtf8CodePoint('?');
|
||||
for (int j = i + Character.charCount(c); j < length; j += Character.charCount(c)) {
|
||||
c = s.codePointAt(j);
|
||||
buffer.writeUtf8CodePoint(c > '\u001f' && c < '\u007f' ? c : '?');
|
||||
try (Buffer buffer = new Buffer()) {
|
||||
buffer.writeUtf8(s, 0, i);
|
||||
buffer.writeUtf8CodePoint('?');
|
||||
for (int j = i + Character.charCount(c); j < length; j += Character.charCount(c)) {
|
||||
c = s.codePointAt(j);
|
||||
buffer.writeUtf8CodePoint(c > '\u001f' && c < '\u007f' ? c : '?');
|
||||
}
|
||||
return buffer.readUtf8();
|
||||
}
|
||||
return buffer.readUtf8();
|
||||
}
|
||||
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">
|
||||
<item name="Introduction" href="/index.html"/>
|
||||
<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"/>
|
||||
</menu>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<item name="JWT Authentication" href="/githubappjwtauth.html"/>
|
||||
<item name="App Installation Token " href="/githubappappinsttokenauth.html"/>
|
||||
</item>
|
||||
<item name="Working with organizations" href="/createorglevelresources.html"/>
|
||||
</menu>
|
||||
|
||||
<menu name="References">
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
|
||||
public class AbstractGHAppInstallationTest extends AbstractGitHubWireMockTest {
|
||||
|
||||
private static String TEST_APP_ID_1 = "82994";
|
||||
private static String TEST_APP_ID_2 = "83009";
|
||||
private static String TEST_APP_ID_3 = "89368";
|
||||
private static String PRIVATE_KEY_FILE_APP_1 = "/ghapi-test-app-1.private-key.pem";
|
||||
private static String PRIVATE_KEY_FILE_APP_2 = "/ghapi-test-app-2.private-key.pem";
|
||||
private static String PRIVATE_KEY_FILE_APP_3 = "/ghapi-test-app-3.private-key.pem";
|
||||
|
||||
private String createJwtToken(String keyFileResouceName, String appId) {
|
||||
try {
|
||||
String keyPEM = IOUtils.toString(this.getClass().getResource(keyFileResouceName), "US-ASCII")
|
||||
.replaceAll("(?m)^--.*", "") // remove comments from PEM to allow decoding
|
||||
.replaceAll("\\s", "");
|
||||
|
||||
PKCS8EncodedKeySpec keySpecPKCS8 = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyPEM));
|
||||
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpecPKCS8);
|
||||
|
||||
return Jwts.builder()
|
||||
.setIssuedAt(Date.from(Instant.now()))
|
||||
.setExpiration(Date.from(Instant.now().plus(5, ChronoUnit.MINUTES)))
|
||||
.setIssuer(appId)
|
||||
.signWith(privateKey)
|
||||
.compact();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error creating JWT token.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private GHAppInstallation getAppInstallationWithToken(String jwtToken) throws IOException {
|
||||
GitHub gitHub = getGitHubBuilder().withJwtToken(jwtToken)
|
||||
.withEndpoint(mockGitHub.apiServer().baseUrl())
|
||||
.build();
|
||||
|
||||
GHAppInstallation appInstallation = gitHub.getApp()
|
||||
.listInstallations()
|
||||
.toList()
|
||||
.stream()
|
||||
.filter(it -> it.getAccount().login.equals("hub4j-test-org"))
|
||||
.findFirst()
|
||||
.get();
|
||||
|
||||
appInstallation
|
||||
.setRoot(getGitHubBuilder().withAppInstallationToken(appInstallation.createToken().create().getToken())
|
||||
.withEndpoint(mockGitHub.apiServer().baseUrl())
|
||||
.build());
|
||||
|
||||
return appInstallation;
|
||||
}
|
||||
|
||||
protected GHAppInstallation getAppInstallationWithTokenApp1() throws IOException {
|
||||
return getAppInstallationWithToken(createJwtToken(PRIVATE_KEY_FILE_APP_1, TEST_APP_ID_1));
|
||||
}
|
||||
|
||||
protected GHAppInstallation getAppInstallationWithTokenApp2() throws IOException {
|
||||
return getAppInstallationWithToken(createJwtToken(PRIVATE_KEY_FILE_APP_2, TEST_APP_ID_2));
|
||||
}
|
||||
|
||||
protected GHAppInstallation getAppInstallationWithTokenApp3() throws IOException {
|
||||
return getAppInstallationWithToken(createJwtToken(PRIVATE_KEY_FILE_APP_3, TEST_APP_ID_3));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,7 +31,7 @@ public abstract class AbstractGitHubWireMockTest extends Assert {
|
||||
|
||||
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_PASSWORD = "placeholder-password";
|
||||
@@ -134,7 +134,7 @@ public abstract class AbstractGitHubWireMockTest extends Assert {
|
||||
|
||||
protected void verifyAuthenticated(GitHub instance) {
|
||||
assertThat(
|
||||
"GitHub connection believes it is anonymous. Make sure you set GITHUB_OAUTH or both GITHUB_USER and GITHUB_PASSWORD environment variables",
|
||||
"GitHub connection believes it is anonymous. Make sure you set GITHUB_OAUTH or both GITHUB_LOGIN and GITHUB_PASSWORD environment variables",
|
||||
instance.isAnonymous(),
|
||||
Matchers.is(false));
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.junit.Test;
|
||||
import org.kohsuke.github.GHCommit.File;
|
||||
import org.kohsuke.github.GHOrganization.Permission;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
@@ -19,6 +20,7 @@ import java.util.Map.Entry;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.CoreMatchers.sameInstance;
|
||||
import static org.hamcrest.Matchers.hasProperty;
|
||||
import static org.hamcrest.Matchers.oneOf;
|
||||
|
||||
@@ -39,13 +41,40 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
||||
"a test repository",
|
||||
"http://github-api.kohsuke.org/",
|
||||
true);
|
||||
|
||||
assertThat(r.hasIssues(), is(true));
|
||||
assertThat(r.hasWiki(), is(true));
|
||||
assertThat(r.hasDownloads(), is(true));
|
||||
assertThat(r.hasProjects(), is(true));
|
||||
|
||||
r.enableIssueTracker(false);
|
||||
r.enableDownloads(false);
|
||||
r.enableWiki(false);
|
||||
r.enableProjects(false);
|
||||
|
||||
r.renameTo(targetName);
|
||||
getUser().getRepository(targetName).delete();
|
||||
|
||||
// local instance remains unchanged
|
||||
assertThat(r.getName(), equalTo("github-api-test-rename"));
|
||||
assertThat(r.hasIssues(), is(true));
|
||||
assertThat(r.hasWiki(), is(true));
|
||||
assertThat(r.hasDownloads(), is(true));
|
||||
assertThat(r.hasProjects(), is(true));
|
||||
|
||||
r = gitHub.getMyself().getRepository(targetName);
|
||||
|
||||
// values are updated
|
||||
assertThat(r.hasIssues(), is(false));
|
||||
assertThat(r.hasWiki(), is(false));
|
||||
assertThat(r.hasDownloads(), is(false));
|
||||
assertThat(r.getName(), equalTo(targetName));
|
||||
|
||||
// ISSUE: #765
|
||||
// From what I can tell this is a bug in GithHub.
|
||||
// updating `has_projects` doesn't seem to do anything
|
||||
assertThat(r.hasProjects(), is(true));
|
||||
|
||||
r.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -57,14 +86,12 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
||||
.homepage("http://github-api.kohsuke.org/")
|
||||
.autoInit(true)
|
||||
.create();
|
||||
r.enableIssueTracker(false);
|
||||
r.enableDownloads(false);
|
||||
r.enableWiki(false);
|
||||
if (mockGitHub.isUseProxy()) {
|
||||
Thread.sleep(3000);
|
||||
}
|
||||
assertNotNull(r.getReadme());
|
||||
getUser().getRepository(name).delete();
|
||||
|
||||
r.delete();
|
||||
}
|
||||
|
||||
private void cleanupUserRepository(final String name) throws IOException {
|
||||
@@ -76,8 +103,38 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
||||
@Test
|
||||
public void testCredentialValid() throws IOException {
|
||||
assertTrue(gitHub.isCredentialValid());
|
||||
GitHub connect = GitHub.connect("totally", "bogus");
|
||||
assertFalse(connect.isCredentialValid());
|
||||
assertThat(gitHub.lastRateLimit().getCore(), not(instanceOf(GHRateLimit.UnknownLimitRecord.class)));
|
||||
assertThat(gitHub.lastRateLimit().getCore().getLimit(), equalTo(5000));
|
||||
|
||||
gitHub = getGitHubBuilder().withOAuthToken("bogus", "user")
|
||||
.withEndpoint(mockGitHub.apiServer().baseUrl())
|
||||
.build();
|
||||
assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT));
|
||||
assertFalse(gitHub.isCredentialValid());
|
||||
// For invalid credentials, we get a 401 but it includes anonymous rate limit headers
|
||||
assertThat(gitHub.lastRateLimit().getCore(), not(instanceOf(GHRateLimit.UnknownLimitRecord.class)));
|
||||
assertThat(gitHub.lastRateLimit().getCore().getLimit(), equalTo(60));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCredentialValidEnterprise() throws IOException {
|
||||
// Simulated GHE: getRateLimit returns 404
|
||||
assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT));
|
||||
assertThat(gitHub.lastRateLimit().getCore().isExpired(), is(true));
|
||||
assertTrue(gitHub.isCredentialValid());
|
||||
|
||||
// lastRateLimitUpdates because 404 still includes header rate limit info
|
||||
assertThat(gitHub.lastRateLimit(), notNullValue());
|
||||
assertThat(gitHub.lastRateLimit(), not(equalTo(GHRateLimit.DEFAULT)));
|
||||
assertThat(gitHub.lastRateLimit().getCore().isExpired(), is(false));
|
||||
|
||||
gitHub = getGitHubBuilder().withOAuthToken("bogus", "user")
|
||||
.withEndpoint(mockGitHub.apiServer().baseUrl())
|
||||
.build();
|
||||
assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT));
|
||||
assertFalse(gitHub.isCredentialValid());
|
||||
// Simulated GHE: For invalid credentials, we get a 401 that does not include ratelimit info
|
||||
assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -146,7 +203,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
||||
|
||||
@Test
|
||||
public void testGetIssues() throws Exception {
|
||||
List<GHIssue> closedIssues = gitHub.getOrganization("github-api")
|
||||
List<GHIssue> closedIssues = gitHub.getOrganization("hub4j")
|
||||
.getRepository("github-api")
|
||||
.getIssues(GHIssueState.CLOSED);
|
||||
// prior to using PagedIterable GHRepository.getIssues(GHIssueState) would only retrieve 30 issues
|
||||
@@ -159,7 +216,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
||||
|
||||
@Test
|
||||
public void testListIssues() throws IOException {
|
||||
Iterable<GHIssue> closedIssues = gitHub.getOrganization("github-api")
|
||||
Iterable<GHIssue> closedIssues = gitHub.getOrganization("hub4j")
|
||||
.getRepository("github-api")
|
||||
.listIssues(GHIssueState.CLOSED);
|
||||
|
||||
@@ -234,16 +291,35 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
||||
return team.hasMember(gitHub.getMyself());
|
||||
}
|
||||
|
||||
@Ignore("Needs mocking check")
|
||||
@Test
|
||||
public void testShouldFetchTeam() throws Exception {
|
||||
GHOrganization j = gitHub.getOrganization(GITHUB_API_TEST_ORG);
|
||||
GHTeam teamByName = j.getTeams().get("Core Developers");
|
||||
GHOrganization organization = gitHub.getOrganization(GITHUB_API_TEST_ORG);
|
||||
GHTeam teamByName = organization.getTeams().get("Core Developers");
|
||||
|
||||
GHTeam teamById = gitHub.getTeam(teamByName.getId());
|
||||
GHTeam teamById = gitHub.getTeam((int) teamByName.getId());
|
||||
assertNotNull(teamById);
|
||||
|
||||
assertEquals(teamByName, teamById);
|
||||
assertEquals(teamByName.getId(), teamById.getId());
|
||||
assertEquals(teamByName.getDescription(), teamById.getDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldFetchTeamFromOrganization() throws Exception {
|
||||
GHOrganization organization = gitHub.getOrganization(GITHUB_API_TEST_ORG);
|
||||
GHTeam teamByName = organization.getTeams().get("Core Developers");
|
||||
|
||||
GHTeam teamById = organization.getTeam(teamByName.getId());
|
||||
assertNotNull(teamById);
|
||||
|
||||
assertEquals(teamByName.getId(), teamById.getId());
|
||||
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")
|
||||
@@ -258,7 +334,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
||||
@Ignore("Needs mocking check")
|
||||
@Test
|
||||
public void testFetchPullRequestAsList() throws Exception {
|
||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
||||
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||
assertEquals("master", r.getMasterBranch());
|
||||
PagedIterable<GHPullRequest> i = r.listPullRequests(GHIssueState.CLOSED);
|
||||
List<GHPullRequest> prs = i.toList();
|
||||
@@ -564,7 +640,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
||||
|
||||
@Test
|
||||
public void testCommitStatus() throws Exception {
|
||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
||||
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||
|
||||
GHCommitStatus state;
|
||||
|
||||
@@ -580,10 +656,12 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
||||
|
||||
@Test
|
||||
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");
|
||||
assertEquals(commit.getCommitShortInfo().getAuthor().getName(), "Kohsuke Kawaguchi");
|
||||
assertEquals(commit.getCommitShortInfo().getMessage(), "doc");
|
||||
assertFalse(commit.getCommitShortInfo().getVerification().isVerified());
|
||||
assertEquals(commit.getCommitShortInfo().getVerification().getReason(), GHVerification.Reason.UNSIGNED);
|
||||
}
|
||||
|
||||
@Ignore("Needs mocking check")
|
||||
@@ -741,7 +819,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
||||
|
||||
@Test // issue #99
|
||||
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.getContent(), "This is a markdown readme.\n");
|
||||
}
|
||||
@@ -749,7 +827,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
||||
@Ignore("Needs mocking check")
|
||||
@Test
|
||||
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;
|
||||
for (GHTreeEntry e : masterTree.getTree()) {
|
||||
if ("readme".equalsIgnoreCase(e.getPath().replaceAll("\\.md", ""))) {
|
||||
@@ -762,7 +840,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
||||
|
||||
@Test
|
||||
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;
|
||||
for (GHTreeEntry e : masterTree.getTree()) {
|
||||
if (e.getPath().endsWith(AppTest.class.getSimpleName() + ".java")) {
|
||||
@@ -778,10 +856,10 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
||||
cleanupLabel("test");
|
||||
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();
|
||||
for (GHLabel l : lst) {
|
||||
// System.out.println(l.getName());
|
||||
assertThat(l.getUrl(), containsString(l.getName().replace(" ", "%20")));
|
||||
}
|
||||
assertTrue(lst.size() > 5);
|
||||
GHLabel e = r.getLabel("enhancement");
|
||||
@@ -791,9 +869,13 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
||||
|
||||
GHLabel t = null;
|
||||
GHLabel t2 = null;
|
||||
GHLabel t3 = null;
|
||||
try {// CRUD
|
||||
t = r.createLabel("test", "123456");
|
||||
t2 = r.getLabel("test");
|
||||
assertThat(t, not(sameInstance(t2)));
|
||||
assertThat(t, equalTo(t2));
|
||||
|
||||
assertEquals(t.getName(), t2.getName());
|
||||
assertEquals(t.getColor(), "123456");
|
||||
assertEquals(t.getColor(), t2.getColor());
|
||||
@@ -801,28 +883,69 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
||||
assertEquals(t.getDescription(), t2.getDescription());
|
||||
assertEquals(t.getUrl(), t2.getUrl());
|
||||
|
||||
t.setColor("000000");
|
||||
// update works on multiple changes in one call
|
||||
t3 = t.update().color("000000").description("It is dark!").done();
|
||||
|
||||
// instances behave as immutable by default. Update returns a new updated instance.
|
||||
assertThat(t, not(sameInstance(t2)));
|
||||
assertThat(t, equalTo(t2));
|
||||
|
||||
assertThat(t, not(sameInstance(t3)));
|
||||
assertThat(t, not(equalTo(t3)));
|
||||
|
||||
// This is annoying behavior, but it is by design at this time.
|
||||
// Verifying so we can know when it is fixed.
|
||||
assertEquals(t.getColor(), "123456");
|
||||
assertEquals(t.getDescription(), "");
|
||||
assertEquals(t3.getColor(), "000000");
|
||||
assertEquals(t3.getDescription(), "It is dark!");
|
||||
|
||||
// Test deprecated methods
|
||||
t.setDescription("Deprecated");
|
||||
t = r.getLabel("test");
|
||||
t.setDescription("this is also a test");
|
||||
|
||||
GHLabel t3 = r.getLabel("test");
|
||||
// By using the old instance t when calling setDescription it also sets color to the old value
|
||||
// this is a bad behavior, but it is expected
|
||||
assertEquals(t.getColor(), "123456");
|
||||
assertEquals(t.getDescription(), "Deprecated");
|
||||
|
||||
t.setColor("000000");
|
||||
t = r.getLabel("test");
|
||||
assertEquals(t.getColor(), "000000");
|
||||
assertEquals(t.getDescription(), "Deprecated");
|
||||
|
||||
// set() makes a single change
|
||||
t3 = t.set().description("this is also a test");
|
||||
|
||||
// instances behave as immutable by default. Update returns a new updated instance.
|
||||
assertThat(t, not(sameInstance(t3)));
|
||||
assertThat(t, not(equalTo(t3)));
|
||||
|
||||
assertEquals(t3.getColor(), "000000");
|
||||
assertEquals(t3.getDescription(), "this is also a test");
|
||||
|
||||
t.delete();
|
||||
try {
|
||||
t = r.getLabel("test");
|
||||
fail("Test label should be deleted.");
|
||||
} catch (IOException ex) {
|
||||
assertThat(ex, instanceOf(FileNotFoundException.class));
|
||||
}
|
||||
|
||||
t = r.createLabel("test2", "123457", "this is a different test");
|
||||
t2 = r.getLabel("test2");
|
||||
|
||||
assertEquals(t.getName(), t2.getName());
|
||||
assertEquals(t.getColor(), "123457");
|
||||
assertEquals(t.getColor(), t2.getColor());
|
||||
assertEquals(t.getDescription(), "this is a different test");
|
||||
assertEquals(t.getDescription(), t2.getDescription());
|
||||
assertEquals(t.getUrl(), t2.getUrl());
|
||||
t.delete();
|
||||
|
||||
// Allow null description
|
||||
t = GHLabel.create(r).name("test2").color("123458").done();
|
||||
assertThat(t.getName(), equalTo("test2"));
|
||||
assertThat(t.getDescription(), is(nullValue()));
|
||||
|
||||
} finally {
|
||||
cleanupLabel("test");
|
||||
cleanupLabel("test2");
|
||||
@@ -832,7 +955,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
||||
void cleanupLabel(String name) {
|
||||
if (mockGitHub.isUseProxy()) {
|
||||
try {
|
||||
GHLabel t = getGitHubBeforeAfter().getRepository("github-api-test-org/test-labels").getLabel("test");
|
||||
GHLabel t = getGitHubBeforeAfter().getRepository("hub4j-test-org/test-labels").getLabel(name);
|
||||
t.delete();
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -901,7 +1024,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
||||
|
||||
@Test
|
||||
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;
|
||||
// retrieval
|
||||
@@ -973,7 +1096,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
|
||||
public void blob() throws Exception {
|
||||
Assume.assumeFalse(SystemUtils.IS_OS_WINDOWS);
|
||||
|
||||
GHRepository r = gitHub.getRepository("github-api/github-api");
|
||||
GHRepository r = gitHub.getRepository("hub4j/github-api");
|
||||
String sha1 = "a12243f2fc5b8c2ba47dd677d0b0c7583539584d";
|
||||
|
||||
assertBlobContent(r.readBlob(sha1));
|
||||
|
||||
@@ -5,12 +5,15 @@ import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
@@ -18,26 +21,50 @@ import static org.hamcrest.Matchers.equalTo;
|
||||
public class BridgeMethodTest extends Assert {
|
||||
|
||||
@Test
|
||||
public void lastStatus() throws IOException {
|
||||
GHObject obj = new GHIssue();
|
||||
public void testBridgeMethods() throws IOException {
|
||||
|
||||
List<Method> createdAtMethods = new ArrayList<>();
|
||||
for (Method method : obj.getClass().getMethods()) {
|
||||
if (method.getName().equalsIgnoreCase("getCreatedAt")) {
|
||||
if (method.getReturnType() == Date.class) {
|
||||
createdAtMethods.add(0, method);
|
||||
} else {
|
||||
createdAtMethods.add(method);
|
||||
}
|
||||
// Some would say this is redundant, given that bridge methods are so thin anyway
|
||||
// In the interest of maintaining binary compatibility, we'll do this anyway for a sampling of methods
|
||||
|
||||
// Something odd here
|
||||
// verifyBridgeMethods(new GHCommit(), "getAuthor", GHCommit.GHAuthor.class, GitUser.class);
|
||||
// verifyBridgeMethods(new GHCommit(), "getCommitter", GHCommit.GHAuthor.class, GitUser.class);
|
||||
|
||||
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(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));
|
||||
assertThat(foundMethods, containsInAnyOrder(returnTypes));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.google.common.collect.Iterables;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
@@ -24,4 +26,98 @@ public class CommitTest extends AbstractGitHubWireMockTest {
|
||||
assertEquals(expected.getFiles().size(), commit.getFiles().size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listPullRequestsOfNotIncludedCommit() throws Exception {
|
||||
GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("listPrsListHeads");
|
||||
|
||||
GHCommit commit = repo.getCommit("f66f7ca691ace6f4a9230292efb932b49214d72c");
|
||||
|
||||
assertThat("The commit is supposed to be not part of any pull request",
|
||||
commit.listPullRequests().toList().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listPullRequests() throws Exception {
|
||||
GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("listPrsListHeads");
|
||||
Integer prNumber = 2;
|
||||
|
||||
GHCommit commit = repo.getCommit("6b9956fe8c3d030dbc49c9d4c4166b0ceb4198fc");
|
||||
|
||||
List<GHPullRequest> listedPrs = commit.listPullRequests().toList();
|
||||
|
||||
assertEquals(listedPrs.size(), 1);
|
||||
|
||||
assertThat("Pull request " + prNumber + " not found by searching from commit.",
|
||||
listedPrs.stream().findFirst().filter(it -> it.getNumber() == prNumber).isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listPullRequestsOfCommitWith2PullRequests() throws Exception {
|
||||
GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("listPrsListHeads");
|
||||
Integer[] expectedPrs = new Integer[]{ 1, 2 };
|
||||
|
||||
GHCommit commit = repo.getCommit("442aa213f924a5984856f16e52a18153aaf41ad3");
|
||||
|
||||
List<GHPullRequest> listedPrs = commit.listPullRequests().toList();
|
||||
|
||||
assertEquals(listedPrs.size(), 2);
|
||||
|
||||
listedPrs.stream()
|
||||
.forEach(pr -> assertThat("PR#" + pr.getNumber() + " not expected to be matched.",
|
||||
Arrays.stream(expectedPrs).anyMatch(prNumber -> prNumber.equals(pr.getNumber()))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listBranchesWhereHead() throws Exception {
|
||||
GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("listPrsListHeads");
|
||||
|
||||
GHCommit commit = repo.getCommit("ab92e13c0fc844fd51a379a48a3ad0b18231215c");
|
||||
|
||||
assertThat("Commit which was supposed to be HEAD in the \"master\" branch was not found.",
|
||||
commit.listBranchesWhereHead()
|
||||
.toList()
|
||||
.stream()
|
||||
.findFirst()
|
||||
.filter(it -> it.getName().equals("master"))
|
||||
.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listBranchesWhereHead2Heads() throws Exception {
|
||||
GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("listPrsListHeads");
|
||||
|
||||
GHCommit commit = repo.getCommit("ab92e13c0fc844fd51a379a48a3ad0b18231215c");
|
||||
|
||||
assertEquals("Commit which was supposed to be HEAD in 2 branches was not found as such.",
|
||||
2,
|
||||
commit.listBranchesWhereHead().toList().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listBranchesWhereHeadOfCommitWithHeadNowhere() throws Exception {
|
||||
GHRepository repo = gitHub.getOrganization("hub4j-test-org").getRepository("listPrsListHeads");
|
||||
|
||||
GHCommit commit = repo.getCommit("7460916bfb8e9966d6b9d3e8ae378c82c6b8e43e");
|
||||
|
||||
assertThat("Commit which was not supposed to be HEAD in any branch was found as HEAD.",
|
||||
commit.listBranchesWhereHead().toList().isEmpty());
|
||||
}
|
||||
|
||||
@Test // issue 737
|
||||
public void commitSignatureVerification() throws Exception {
|
||||
GHRepository repo = gitHub.getRepository("stapler/stapler");
|
||||
PagedIterable<GHCommit> commits = repo.queryCommits().path("pom.xml").list();
|
||||
for (GHCommit commit : Iterables.limit(commits, 10)) {
|
||||
GHCommit expected = repo.getCommit(commit.getSHA1());
|
||||
assertEquals(expected.getCommitShortInfo().getVerification().isVerified(),
|
||||
commit.getCommitShortInfo().getVerification().isVerified());
|
||||
assertEquals(expected.getCommitShortInfo().getVerification().getReason(),
|
||||
commit.getCommitShortInfo().getVerification().getReason());
|
||||
assertEquals(expected.getCommitShortInfo().getVerification().getSignature(),
|
||||
commit.getCommitShortInfo().getVerification().getSignature());
|
||||
assertEquals(expected.getCommitShortInfo().getVerification().getPayload(),
|
||||
commit.getCommitShortInfo().getVerification().getPayload());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
29
src/test/java/org/kohsuke/github/GHAppInstallationTest.java
Normal file
29
src/test/java/org/kohsuke/github/GHAppInstallationTest.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public class GHAppInstallationTest extends AbstractGHAppInstallationTest {
|
||||
|
||||
@Test
|
||||
public void testListRepositoriesTwoRepos() throws IOException {
|
||||
GHAppInstallation appInstallation = getAppInstallationWithTokenApp1();
|
||||
|
||||
List<GHRepository> repositories = appInstallation.listRepositories().toList();
|
||||
|
||||
assertEquals(2, repositories.size());
|
||||
assertTrue(repositories.stream().anyMatch(it -> it.getName().equals("empty")));
|
||||
assertTrue(repositories.stream().anyMatch(it -> it.getName().equals("test-readme")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListRepositoriesNoPermissions() throws IOException {
|
||||
GHAppInstallation appInstallation = getAppInstallationWithTokenApp2();
|
||||
|
||||
assertTrue("App does not have permissions and should have 0 repositories",
|
||||
appInstallation.listRepositories().toList().isEmpty());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,8 +28,8 @@ public class GHAppTest extends AbstractGitHubWireMockTest {
|
||||
@Test
|
||||
public void getGitHubApp() throws IOException {
|
||||
GHApp app = gitHub.getApp();
|
||||
assertThat(app.id, is((long) 11111));
|
||||
assertThat(app.getOwner().id, is((long) 111111111));
|
||||
assertThat(app.getId(), is((long) 11111));
|
||||
assertThat(app.getOwner().getId(), is((long) 111111111));
|
||||
assertThat(app.getOwner().login, is("bogus"));
|
||||
assertThat(app.getName(), is("Bogus-Development"));
|
||||
assertThat(app.getDescription(), is(""));
|
||||
@@ -132,8 +132,8 @@ public class GHAppTest extends AbstractGitHubWireMockTest {
|
||||
Map<String, GHPermissionType> appPermissions = appInstallation.getPermissions();
|
||||
GHUser appAccount = appInstallation.getAccount();
|
||||
|
||||
assertThat(appInstallation.id, is((long) 11111111));
|
||||
assertThat(appAccount.id, is((long) 111111111));
|
||||
assertThat(appInstallation.getId(), is((long) 11111111));
|
||||
assertThat(appAccount.getId(), is((long) 111111111));
|
||||
assertThat(appAccount.login, is("bogus"));
|
||||
assertThat(appInstallation.getRepositorySelection(), is(GHRepositorySelection.SELECTED));
|
||||
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.RequiredStatusChecks;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
|
||||
public class GHBranchProtectionTest extends AbstractGitHubWireMockTest {
|
||||
private static final String BRANCH = "master";
|
||||
private static final String BRANCH_REF = "heads/" + BRANCH;
|
||||
@@ -32,6 +34,14 @@ public class GHBranchProtectionTest extends AbstractGitHubWireMockTest {
|
||||
.includeAdmins()
|
||||
.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();
|
||||
assertNotNull(statusChecks);
|
||||
assertTrue(statusChecks.isRequiresBranchUpToDate());
|
||||
@@ -54,11 +64,32 @@ public class GHBranchProtectionTest extends AbstractGitHubWireMockTest {
|
||||
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
|
||||
public void testEnableRequireReviewsOnly() throws Exception {
|
||||
GHBranchProtection protection = branch.enableProtection().requireReviews().enable();
|
||||
|
||||
RequiredReviews requiredReviews = 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
|
||||
@@ -73,4 +104,12 @@ public class GHBranchProtectionTest extends AbstractGitHubWireMockTest {
|
||||
protection.disableSignedCommits();
|
||||
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();
|
||||
}
|
||||
}
|
||||
144
src/test/java/org/kohsuke/github/GHCheckRunBuilderTest.java
Normal file
144
src/test/java/org/kohsuke/github/GHCheckRunBuilderTest.java
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright 2020 CloudBees, Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.kohsuke.github;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
||||
@SuppressWarnings("deprecation") // preview
|
||||
public class GHCheckRunBuilderTest extends AbstractGHAppInstallationTest {
|
||||
|
||||
protected GitHub getInstallationGithub() throws IOException {
|
||||
return getAppInstallationWithTokenApp3().getRoot();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createCheckRun() throws Exception {
|
||||
GHCheckRun checkRun = getInstallationGithub().getRepository("hub4j-test-org/test-checks")
|
||||
.createCheckRun("foo", "89a9ae301e35e667756034fdc933b1fc94f63fc1")
|
||||
.withStatus(GHCheckRun.Status.COMPLETED)
|
||||
.withConclusion(GHCheckRun.Conclusion.SUCCESS)
|
||||
.withDetailsURL("http://nowhere.net/stuff")
|
||||
.withExternalID("whatever")
|
||||
.withStartedAt(new Date(999_999_000))
|
||||
.withCompletedAt(new Date(999_999_999))
|
||||
.add(new GHCheckRunBuilder.Output("Some Title", "what happened…")
|
||||
.add(new GHCheckRunBuilder.Annotation("stuff.txt",
|
||||
1,
|
||||
GHCheckRun.AnnotationLevel.NOTICE,
|
||||
"hello to you too").withTitle("Look here"))
|
||||
.add(new GHCheckRunBuilder.Image("Unikitty",
|
||||
"https://i.pinimg.com/474x/9e/65/c0/9e65c0972294f1e10f648c9780a79fab.jpg")
|
||||
.withCaption("Princess Unikitty")))
|
||||
.add(new GHCheckRunBuilder.Action("Help", "what I need help with", "doit"))
|
||||
.create();
|
||||
assertEquals("completed", checkRun.getStatus());
|
||||
assertEquals(1, checkRun.getOutput().getAnnotationsCount());
|
||||
assertEquals(1424883286, checkRun.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createCheckRunManyAnnotations() throws Exception {
|
||||
GHCheckRunBuilder.Output output = new GHCheckRunBuilder.Output("Big Run", "Lots of stuff here »");
|
||||
for (int i = 0; i < 101; i++) {
|
||||
output.add(
|
||||
new GHCheckRunBuilder.Annotation("stuff.txt", 1, GHCheckRun.AnnotationLevel.NOTICE, "hello #" + i));
|
||||
}
|
||||
GHCheckRun checkRun = getInstallationGithub().getRepository("hub4j-test-org/test-checks")
|
||||
.createCheckRun("big", "89a9ae301e35e667756034fdc933b1fc94f63fc1")
|
||||
.withConclusion(GHCheckRun.Conclusion.SUCCESS)
|
||||
.add(output)
|
||||
.create();
|
||||
assertEquals("completed", checkRun.getStatus());
|
||||
assertEquals("Big Run", checkRun.getOutput().getTitle());
|
||||
assertEquals("Lots of stuff here »", checkRun.getOutput().getSummary());
|
||||
assertEquals(101, checkRun.getOutput().getAnnotationsCount());
|
||||
assertEquals(1424883599, checkRun.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createCheckRunNoAnnotations() throws Exception {
|
||||
GHCheckRun checkRun = getInstallationGithub().getRepository("hub4j-test-org/test-checks")
|
||||
.createCheckRun("quick", "89a9ae301e35e667756034fdc933b1fc94f63fc1")
|
||||
.withConclusion(GHCheckRun.Conclusion.NEUTRAL)
|
||||
.add(new GHCheckRunBuilder.Output("Quick note", "nothing more to see here"))
|
||||
.create();
|
||||
assertEquals("completed", checkRun.getStatus());
|
||||
assertEquals(0, checkRun.getOutput().getAnnotationsCount());
|
||||
assertEquals(1424883957, checkRun.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createPendingCheckRun() throws Exception {
|
||||
GHCheckRun checkRun = getInstallationGithub().getRepository("hub4j-test-org/test-checks")
|
||||
.createCheckRun("outstanding", "89a9ae301e35e667756034fdc933b1fc94f63fc1")
|
||||
.withStatus(GHCheckRun.Status.IN_PROGRESS)
|
||||
.create();
|
||||
assertEquals("in_progress", checkRun.getStatus());
|
||||
assertNull(checkRun.getConclusion());
|
||||
assertEquals(1424883451, checkRun.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createCheckRunErrMissingConclusion() throws Exception {
|
||||
try {
|
||||
getInstallationGithub().getRepository("hub4j-test-org/test-checks")
|
||||
.createCheckRun("outstanding", "89a9ae301e35e667756034fdc933b1fc94f63fc1")
|
||||
.withStatus(GHCheckRun.Status.COMPLETED)
|
||||
.create();
|
||||
fail("should have been rejected");
|
||||
} catch (HttpException x) {
|
||||
assertEquals(422, x.getResponseCode());
|
||||
assertThat(x.getMessage(), containsString("\\\"conclusion\\\" wasn't supplied"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateCheckRun() throws Exception {
|
||||
GHCheckRun checkRun = getInstallationGithub().getRepository("hub4j-test-org/test-checks")
|
||||
.createCheckRun("foo", "89a9ae301e35e667756034fdc933b1fc94f63fc1")
|
||||
.withStatus(GHCheckRun.Status.IN_PROGRESS)
|
||||
.withStartedAt(new Date(999_999_000))
|
||||
.add(new GHCheckRunBuilder.Output("Some Title", "what happened…")
|
||||
.add(new GHCheckRunBuilder.Annotation("stuff.txt",
|
||||
1,
|
||||
GHCheckRun.AnnotationLevel.NOTICE,
|
||||
"hello to you too").withTitle("Look here")))
|
||||
.create();
|
||||
GHCheckRun updated = checkRun.update()
|
||||
.withStatus(GHCheckRun.Status.COMPLETED)
|
||||
.withConclusion(GHCheckRun.Conclusion.SUCCESS)
|
||||
.withCompletedAt(new Date(999_999_999))
|
||||
.create();
|
||||
assertEquals(updated.getStartedAt(), new Date(999_999_000));
|
||||
assertEquals(updated.getName(), "foo");
|
||||
assertEquals(1, checkRun.getOutput().getAnnotationsCount());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -7,9 +8,11 @@ import org.junit.Test;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.Matchers.hasProperty;
|
||||
|
||||
/**
|
||||
* Integration test for {@link GHContent}.
|
||||
@@ -26,7 +29,7 @@ public class GHContentIntegrationTest extends AbstractGitHubWireMockTest {
|
||||
@After
|
||||
public void cleanup() throws Exception {
|
||||
if (mockGitHub.isUseProxy()) {
|
||||
repo = getGitHubBeforeAfter().getRepository("github-api-test-org/GHContentIntegrationTest");
|
||||
repo = getGitHubBeforeAfter().getRepository("hub4j-test-org/GHContentIntegrationTest");
|
||||
try {
|
||||
GHContent content = repo.getFileContent(createdFilename);
|
||||
if (content != null) {
|
||||
@@ -39,12 +42,12 @@ public class GHContentIntegrationTest extends AbstractGitHubWireMockTest {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
repo = gitHub.getRepository("github-api-test-org/GHContentIntegrationTest");
|
||||
repo = gitHub.getRepository("hub4j-test-org/GHContentIntegrationTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
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");
|
||||
|
||||
assertTrue(content.isFile());
|
||||
@@ -123,7 +126,7 @@ public class GHContentIntegrationTest extends AbstractGitHubWireMockTest {
|
||||
} catch (GHFileNotFoundException e) {
|
||||
assertThat(e.getMessage(),
|
||||
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");
|
||||
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 java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Martin van Zijl
|
||||
@@ -10,17 +12,42 @@ import java.io.IOException;
|
||||
public class GHDeploymentTest extends AbstractGitHubWireMockTest {
|
||||
|
||||
@Test
|
||||
public void testGetDeploymentById() throws IOException {
|
||||
GHRepository repo = getRepository();
|
||||
GHDeployment deployment = repo.getDeployment(178653229);
|
||||
public void testGetDeploymentByIdStringPayload() throws IOException {
|
||||
final GHRepository repo = getRepository();
|
||||
final GHDeployment deployment = repo.getDeployment(178653229);
|
||||
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 {
|
||||
return getRepository(gitHub);
|
||||
}
|
||||
|
||||
private GHRepository getRepository(GitHub gitHub) throws IOException {
|
||||
return gitHub.getOrganization("github-api-test-org").getRepository("github-api");
|
||||
private GHRepository getRepository(final GitHub gitHub) throws IOException {
|
||||
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,18 +3,23 @@ package org.kohsuke.github;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
public class GHEventPayloadTest {
|
||||
public class GHEventPayloadTest extends AbstractGitHubWireMockTest {
|
||||
|
||||
@Rule
|
||||
public final PayloadRule payload = new PayloadRule(".json");
|
||||
|
||||
public GHEventPayloadTest() {
|
||||
useDefaultGitHub = false;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commit_comment() throws Exception {
|
||||
GHEventPayload.CommitComment event = GitHub.offline()
|
||||
@@ -156,6 +161,16 @@ public class GHEventPayloadTest {
|
||||
// @Test
|
||||
// public void page_build() throws Exception {}
|
||||
|
||||
@Test
|
||||
public void ping() throws Exception {
|
||||
GHEventPayload.Ping event = GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.Ping.class);
|
||||
|
||||
assertThat(event.getAction(), nullValue());
|
||||
assertThat(event.getSender().getLogin(), is("seregamorph"));
|
||||
assertThat(event.getRepository().getName(), is("acme-project-project"));
|
||||
assertThat(event.getOrganization(), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Payload("public")
|
||||
public void public_() throws Exception {
|
||||
@@ -199,6 +214,75 @@ public class GHEventPayloadTest {
|
||||
assertThat(event.getSender().getLogin(), is("baxterthehacker"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pull_request_edited_base() throws Exception {
|
||||
GHEventPayload.PullRequest event = GitHub.offline()
|
||||
.parseEventPayload(payload.asReader(), GHEventPayload.PullRequest.class);
|
||||
|
||||
assertThat(event.getAction(), is("edited"));
|
||||
assertThat(event.getChanges().getTitle(), nullValue());
|
||||
assertThat(event.getPullRequest().getTitle(), is("REST-276 - easy-random"));
|
||||
assertThat(event.getChanges().getBase().getRef().getFrom(), is("develop"));
|
||||
assertThat(event.getChanges().getBase().getSha().getFrom(), is("4b0f3b9fd582b071652ccfccd10bfc8c143cff96"));
|
||||
assertThat(event.getPullRequest().getBase().getRef(), is("4.3"));
|
||||
assertThat(event.getPullRequest().getBody(), startsWith("**JIRA Ticket URL:**"));
|
||||
assertThat(event.getChanges().getBody(), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pull_request_edited_title() throws Exception {
|
||||
GHEventPayload.PullRequest event = GitHub.offline()
|
||||
.parseEventPayload(payload.asReader(), GHEventPayload.PullRequest.class);
|
||||
|
||||
assertThat(event.getAction(), is("edited"));
|
||||
assertThat(event.getChanges().getTitle().getFrom(), is("REST-276 - easy-random"));
|
||||
assertThat(event.getPullRequest().getTitle(), is("REST-276 - easy-random 4.3.0"));
|
||||
assertThat(event.getChanges().getBase(), nullValue());
|
||||
assertThat(event.getPullRequest().getBase().getRef(), is("4.3"));
|
||||
assertThat(event.getPullRequest().getBody(), startsWith("**JIRA Ticket URL:**"));
|
||||
assertThat(event.getChanges().getBody(), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pull_request_labeled() throws Exception {
|
||||
GHEventPayload.PullRequest event = GitHub.offline()
|
||||
.parseEventPayload(payload.asReader(), GHEventPayload.PullRequest.class);
|
||||
assertThat(event.getAction(), is("labeled"));
|
||||
assertThat(event.getNumber(), is(79));
|
||||
assertThat(event.getPullRequest().getNumber(), is(79));
|
||||
assertThat(event.getPullRequest().getTitle(), is("Base POJO test enhancement"));
|
||||
assertThat(event.getPullRequest().getBody(),
|
||||
is("This is a pretty simple change that we need to pull into develop."));
|
||||
assertThat(event.getPullRequest().getUser().getLogin(), is("seregamorph"));
|
||||
assertThat(event.getPullRequest().getHead().getUser().getLogin(), is("trilogy-group"));
|
||||
assertThat(event.getPullRequest().getHead().getRef(), is("changes"));
|
||||
assertThat(event.getPullRequest().getHead().getLabel(), is("trilogy-group:changes"));
|
||||
assertThat(event.getPullRequest().getHead().getSha(), is("4b91e3a970fb967fb7be4d52e0969f8e3fb063d0"));
|
||||
assertThat(event.getPullRequest().getBase().getUser().getLogin(), is("trilogy-group"));
|
||||
assertThat(event.getPullRequest().getBase().getRef(), is("3.10"));
|
||||
assertThat(event.getPullRequest().getBase().getLabel(), is("trilogy-group:3.10"));
|
||||
assertThat(event.getPullRequest().getBase().getSha(), is("7a735f17d686c6a1fc7df5b9d395e5863868f364"));
|
||||
assertThat(event.getPullRequest().isMerged(), is(false));
|
||||
assertThat(event.getPullRequest().getMergeable(), is(TRUE));
|
||||
assertThat(event.getPullRequest().getMergeableState(), is("draft"));
|
||||
assertThat(event.getPullRequest().getMergedBy(), nullValue());
|
||||
assertThat(event.getPullRequest().getCommentsCount(), is(1));
|
||||
assertThat(event.getPullRequest().getReviewComments(), is(14));
|
||||
assertThat(event.getPullRequest().getAdditions(), is(137));
|
||||
assertThat(event.getPullRequest().getDeletions(), is(81));
|
||||
assertThat(event.getPullRequest().getChangedFiles(), is(22));
|
||||
assertThat(event.getPullRequest().getLabels().iterator().next().getName(), is("Ready for Review"));
|
||||
assertThat(event.getRepository().getName(), is("trilogy-rest-api-framework"));
|
||||
assertThat(event.getRepository().getOwner().getLogin(), is("trilogy-group"));
|
||||
assertThat(event.getSender().getLogin(), is("schernov-xo"));
|
||||
assertThat(event.getLabel().getUrl(),
|
||||
is("https://api.github.com/repos/trilogy-group/trilogy-rest-api-framework/labels/rest%20api"));
|
||||
assertThat(event.getLabel().getName(), is("rest api"));
|
||||
assertThat(event.getLabel().getColor(), is("fef2c0"));
|
||||
assertThat(event.getLabel().getDescription(), is("REST API pull request"));
|
||||
assertThat(event.getOrganization().getLogin(), is("trilogy-group"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pull_request_review() throws Exception {
|
||||
GHEventPayload.PullRequestReview event = GitHub.offline()
|
||||
@@ -212,6 +296,8 @@ public class GHEventPayloadTest {
|
||||
assertThat(event.getPullRequest().getNumber(), is(8));
|
||||
assertThat(event.getPullRequest().getTitle(), is("Add a README description"));
|
||||
assertThat(event.getPullRequest().getBody(), is("Just a few more details"));
|
||||
assertThat(event.getReview().getHtmlUrl(),
|
||||
hasToString("https://github.com/baxterthehacker/public-repo/pull/8#pullrequestreview-2626884"));
|
||||
assertThat(event.getPullRequest().getUser().getLogin(), is("skalnik"));
|
||||
assertThat(event.getPullRequest().getHead().getUser().getLogin(), is("skalnik"));
|
||||
assertThat(event.getPullRequest().getHead().getRef(), is("patch-2"));
|
||||
@@ -268,7 +354,9 @@ public class GHEventPayloadTest {
|
||||
assertThat(event.getCommits().size(), is(1));
|
||||
assertThat(event.getCommits().get(0).getSha(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c"));
|
||||
assertThat(event.getCommits().get(0).getAuthor().getEmail(), is("baxterthehacker@users.noreply.github.com"));
|
||||
assertThat(event.getCommits().get(0).getAuthor().getUsername(), is("baxterthehacker"));
|
||||
assertThat(event.getCommits().get(0).getCommitter().getEmail(), is("baxterthehacker@users.noreply.github.com"));
|
||||
assertThat(event.getCommits().get(0).getCommitter().getUsername(), is("baxterthehacker"));
|
||||
assertThat(event.getCommits().get(0).getAdded().size(), is(0));
|
||||
assertThat(event.getCommits().get(0).getRemoved().size(), is(0));
|
||||
assertThat(event.getCommits().get(0).getModified().size(), is(1));
|
||||
@@ -280,11 +368,88 @@ public class GHEventPayloadTest {
|
||||
assertThat(event.getPusher().getName(), is("baxterthehacker"));
|
||||
assertThat(event.getPusher().getEmail(), is("baxterthehacker@users.noreply.github.com"));
|
||||
assertThat(event.getSender().getLogin(), is("baxterthehacker"));
|
||||
assertThat(event.getCompare(),
|
||||
is("https://github.com/baxterthehacker/public-repo/compare/9049f1265b7d...0d1a26e67d8f"));
|
||||
}
|
||||
|
||||
// TODO implement support classes and write test
|
||||
// @Test
|
||||
// public void release() throws Exception {}
|
||||
@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"));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void release_published() throws Exception {
|
||||
GHEventPayload.Release event = GitHub.offline()
|
||||
.parseEventPayload(payload.asReader(), GHEventPayload.Release.class);
|
||||
|
||||
assertThat(event.getAction(), is("published"));
|
||||
assertThat(event.getSender().getLogin(), is("seregamorph"));
|
||||
assertThat(event.getRepository().getName(), is("company-rest-api-framework"));
|
||||
assertThat(event.getOrganization().getLogin(), is("company-group"));
|
||||
assertThat(event.getInstallation(), nullValue());
|
||||
assertThat(event.getRelease().getName(), is("4.2"));
|
||||
assertThat(event.getRelease().getTagName(), is("rest-api-framework-4.2"));
|
||||
assertThat(event.getRelease().getBody(), is("REST-269 - unique test executions (#86) Sergey Chernov"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void repository() throws Exception {
|
||||
@@ -320,7 +485,25 @@ public class GHEventPayloadTest {
|
||||
@Payload("check-run")
|
||||
public void checkRunEvent() throws Exception {
|
||||
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().getOwner().getLogin(), is("Codertocat"));
|
||||
assertThat(event.getAction(), is("created"));
|
||||
@@ -339,9 +522,9 @@ public class GHEventPayloadTest {
|
||||
assertThat(formatter.format(checkRun.getCompletedAt()), is("2019-05-15T20:22:22Z"));
|
||||
|
||||
assertThat(checkRun.getConclusion(), is("success"));
|
||||
assertThat(checkRun.getUrl().toString(),
|
||||
is("https://api.github.com/repos/Codertocat/Hello-World/check-runs/128620228"));
|
||||
assertThat(checkRun.getHtmlUrl().toString(), is("https://github.com/Codertocat/Hello-World/runs/128620228"));
|
||||
assertThat(checkRun.getUrl().toString(), endsWith("/repos/Codertocat/Hello-World/check-runs/128620228"));
|
||||
assertThat(checkRun.getHtmlUrl().toString(),
|
||||
endsWith("https://github.com/Codertocat/Hello-World/runs/128620228"));
|
||||
assertThat(checkRun.getDetailsUrl().toString(), is("https://octocoders.io"));
|
||||
assertThat(checkRun.getApp().getId(), is(29310L));
|
||||
assertThat(checkRun.getCheckSuite().getId(), is(118578147L));
|
||||
@@ -350,18 +533,41 @@ public class GHEventPayloadTest {
|
||||
assertThat(checkRun.getOutput().getText(), nullValue());
|
||||
assertThat(checkRun.getOutput().getAnnotationsCount(), is(0));
|
||||
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
|
||||
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
|
||||
@Payload("check-suite")
|
||||
public void checkSuiteEvent() throws Exception {
|
||||
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().getOwner().getLogin(), is("Codertocat"));
|
||||
assertThat(event.getAction(), is("completed"));
|
||||
@@ -378,7 +584,7 @@ public class GHEventPayloadTest {
|
||||
assertThat(checkSuite.getAfter(), is("ec26c3e57ca3a959ca5aad62de7213c562f8c821"));
|
||||
assertThat(checkSuite.getLatestCheckRunsCount(), is(1));
|
||||
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().getId(), is("ec26c3e57ca3a959ca5aad62de7213c562f8c821"));
|
||||
assertThat(checkSuite.getHeadCommit().getTreeId(), is("31b122c26a97cf9af023e9ddab94a82c6e77b0ea"));
|
||||
@@ -390,6 +596,50 @@ public class GHEventPayloadTest {
|
||||
assertThat(formatter.format(checkSuite.getHeadCommit().getTimestamp()), is("2019-05-15T15:20:30Z"));
|
||||
|
||||
assertThat(checkSuite.getApp().getId(), is(29310L));
|
||||
|
||||
assertThat(checkSuite.getPullRequests(), notNullValue());
|
||||
assertThat(checkSuite.getPullRequests().size(), equalTo(1));
|
||||
assertThat(checkSuite.getPullRequests().get(0).getNumber(), equalTo(2));
|
||||
return checkSuite;
|
||||
}
|
||||
|
||||
@Test
|
||||
@Payload("installation_repositories")
|
||||
public void InstallationRepositoriesEvent() throws Exception {
|
||||
GHEventPayload.InstallationRepositories event = GitHub.offline()
|
||||
.parseEventPayload(payload.asReader(), GHEventPayload.InstallationRepositories.class);
|
||||
|
||||
assertThat(event.getAction(), is("added"));
|
||||
assertThat(event.getInstallation().getId(), is(957387L));
|
||||
assertThat(event.getInstallation().getAccount().getLogin(), is("Codertocat"));
|
||||
assertThat(event.getRepositorySelection(), is("selected"));
|
||||
|
||||
assertThat(event.getRepositoriesAdded().get(0).getId(), is(186853007L));
|
||||
assertThat(event.getRepositoriesAdded().get(0).getNodeId(), is("MDEwOlJlcG9zaXRvcnkxODY4NTMwMDc="));
|
||||
assertThat(event.getRepositoriesAdded().get(0).getName(), is("Space"));
|
||||
assertThat(event.getRepositoriesAdded().get(0).getFullName(), is("Codertocat/Space"));
|
||||
assertThat(event.getRepositoriesAdded().get(0).isPrivate(), is(false));
|
||||
|
||||
assertThat(event.getRepositoriesRemoved(), is(Collections.emptyList()));
|
||||
assertThat(event.getSender().getLogin(), is("Codertocat"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Payload("installation")
|
||||
public void InstallationEvent() throws Exception {
|
||||
GHEventPayload.Installation event = GitHub.offline()
|
||||
.parseEventPayload(payload.asReader(), GHEventPayload.Installation.class);
|
||||
|
||||
assertThat(event.getAction(), is("deleted"));
|
||||
assertThat(event.getInstallation().getId(), is(2L));
|
||||
assertThat(event.getInstallation().getAccount().getLogin(), is("octocat"));
|
||||
|
||||
assertThat(event.getRepositories().get(0).getId(), is(1296269L));
|
||||
assertThat(event.getRepositories().get(0).getNodeId(), is("MDEwOlJlcG9zaXRvcnkxODY4NTMwMDc="));
|
||||
assertThat(event.getRepositories().get(0).getName(), is("Hello-World"));
|
||||
assertThat(event.getRepositories().get(0).getFullName(), is("octocat/Hello-World"));
|
||||
assertThat(event.getRepositories().get(0).isPrivate(), is(false));
|
||||
|
||||
assertThat(event.getSender().getLogin(), is("octocat"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package org.kohsuke.github;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -17,9 +19,12 @@ public class GHGistTest extends AbstractGitHubWireMockTest {
|
||||
.description("Test Gist")
|
||||
.file("abc.txt", "abc")
|
||||
.file("def.txt", "def")
|
||||
.file("ghi.txt", "ghi")
|
||||
.create();
|
||||
|
||||
assertThat(gist.getCreatedAt(), is(notNullValue()));
|
||||
assertThat(gist.getDescription(), equalTo("Test Gist"));
|
||||
assertThat(gist.getFiles().size(), equalTo(3));
|
||||
|
||||
assertNotNull(gist.getUpdatedAt());
|
||||
assertNotNull(gist.getCommentsUrl());
|
||||
@@ -28,7 +33,66 @@ public class GHGistTest extends AbstractGitHubWireMockTest {
|
||||
assertNotNull(gist.getGitPushUrl());
|
||||
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();
|
||||
|
||||
try {
|
||||
gitHub.getGist(id);
|
||||
fail("Gist should be deleted.");
|
||||
} catch (FileNotFoundException e) {
|
||||
assertThat(e, notNullValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -63,6 +127,8 @@ public class GHGistTest extends AbstractGitHubWireMockTest {
|
||||
GHGist gist = gitHub.getGist("9903708");
|
||||
|
||||
assertTrue(gist.isPublic());
|
||||
assertThat(gist.getId(), equalTo(9903708L));
|
||||
assertThat(gist.getGistId(), equalTo("9903708"));
|
||||
|
||||
assertEquals(1, gist.getFiles().size());
|
||||
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 {
|
||||
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.net.URL;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
/**
|
||||
* @author Duncan Dickinson
|
||||
*/
|
||||
@@ -91,7 +93,7 @@ public class GHLicenseTest extends AbstractGitHubWireMockTest {
|
||||
*/
|
||||
@Test
|
||||
public void checkRepositoryLicense() throws IOException {
|
||||
GHRepository repo = gitHub.getRepository("github-api/github-api");
|
||||
GHRepository repo = gitHub.getRepository("hub4j/github-api");
|
||||
GHLicense license = repo.getLicense();
|
||||
assertNotNull("The license is populated", license);
|
||||
assertTrue("The key is correct", license.getKey().equals("mit"));
|
||||
@@ -157,7 +159,7 @@ public class GHLicenseTest extends AbstractGitHubWireMockTest {
|
||||
*/
|
||||
@Test
|
||||
public void checkRepositoryFullLicense() throws IOException {
|
||||
GHRepository repo = gitHub.getRepository("github-api/github-api");
|
||||
GHRepository repo = gitHub.getRepository("hub4j/github-api");
|
||||
GHLicense license = repo.getLicense();
|
||||
assertNotNull("The license is populated", license);
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ import org.junit.Test;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Martin van Zijl
|
||||
*/
|
||||
@@ -23,7 +21,8 @@ public class GHMilestoneTest extends AbstractGitHubWireMockTest {
|
||||
}
|
||||
|
||||
for (GHMilestone milestone : getRepository(getGitHubBeforeAfter()).listMilestones(GHIssueState.ALL)) {
|
||||
if ("Original Title".equals(milestone.getTitle()) || "Updated Title".equals(milestone.getTitle())) {
|
||||
if ("Original Title".equals(milestone.getTitle()) || "Updated Title".equals(milestone.getTitle())
|
||||
|| "Unset Test Milestone".equals(milestone.getTitle())) {
|
||||
milestone.delete();
|
||||
}
|
||||
}
|
||||
@@ -54,11 +53,48 @@ public class GHMilestoneTest extends AbstractGitHubWireMockTest {
|
||||
assertEquals(OUTPUT_DUE_DATE, milestone.getDueOn());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsetMilestone() throws IOException {
|
||||
GHRepository repo = getRepository();
|
||||
GHMilestone milestone = repo.createMilestone("Unset Test Milestone", "For testUnsetMilestone");
|
||||
GHIssue issue = repo.createIssue("Issue for testUnsetMilestone").create();
|
||||
|
||||
// set the milestone
|
||||
issue.setMilestone(milestone);
|
||||
issue = repo.getIssue(issue.getNumber()); // force reload
|
||||
assertEquals(milestone.getNumber(), issue.getMilestone().getNumber());
|
||||
|
||||
// remove the milestone
|
||||
issue.setMilestone(null);
|
||||
issue = repo.getIssue(issue.getNumber()); // force reload
|
||||
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 {
|
||||
return getRepository(gitHub);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user