mirror of
https://github.com/jlengrand/github-api.git
synced 2026-03-11 08:21:23 +00:00
Compare commits
634 Commits
github-api
...
github-api
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
505bb8f06d | ||
|
|
f8408bd29f | ||
|
|
5f2c84a913 | ||
|
|
3011c99e3f | ||
|
|
ebc97f42ad | ||
|
|
4660c6d363 | ||
|
|
e239ef50ba | ||
|
|
8b4312a880 | ||
|
|
90daf8087e | ||
|
|
dd21bcb34c | ||
|
|
c4113f1ac7 | ||
|
|
efa48acd1d | ||
|
|
15e4d07a6d | ||
|
|
cb2248809c | ||
|
|
eb9551d81b | ||
|
|
87d1256a1b | ||
|
|
dd6179cf25 | ||
|
|
23c56ff887 | ||
|
|
c4eefa6917 | ||
|
|
202cff58f2 | ||
|
|
025806f0fd | ||
|
|
b0c54ef0f1 | ||
|
|
4d7681b1a4 | ||
|
|
340fb3f624 | ||
|
|
a83aad22ca | ||
|
|
5a418dcce6 | ||
|
|
ec5392708f | ||
|
|
901db92b11 | ||
|
|
01b8b10344 | ||
|
|
698d642ec8 | ||
|
|
90d1047fb2 | ||
|
|
b0d1eac477 | ||
|
|
cce02aec3d | ||
|
|
492ff58aa8 | ||
|
|
dd3e73996b | ||
|
|
931ed7adac | ||
|
|
861fd55d06 | ||
|
|
9a4eee4e7d | ||
|
|
ed76cdbddf | ||
|
|
9d91549803 | ||
|
|
a5425a3c71 | ||
|
|
f4b105b10f | ||
|
|
e4de09c55b | ||
|
|
d77be9d382 | ||
|
|
626909addb | ||
|
|
9b750bedef | ||
|
|
b976e0ef4e | ||
|
|
fd434292ad | ||
|
|
7b4d3a869b | ||
|
|
eeebb1b59f | ||
|
|
63136f64b7 | ||
|
|
1d2fbf2d92 | ||
|
|
1e52dded14 | ||
|
|
cfc7005275 | ||
|
|
f23afcd5aa | ||
|
|
4e88a0c91b | ||
|
|
d070f9deb0 | ||
|
|
b736e20a74 | ||
|
|
aad20d0a03 | ||
|
|
7ff97348d9 | ||
|
|
68dda3a46d | ||
|
|
2cd44f8c33 | ||
|
|
9775954aff | ||
|
|
1a071b0b54 | ||
|
|
8dcea59c74 | ||
|
|
f482f77871 | ||
|
|
b058c39ee1 | ||
|
|
b926b6c67f | ||
|
|
3fb8e5f799 | ||
|
|
277ccb5188 | ||
|
|
9ebc9c0867 | ||
|
|
f1f96713a4 | ||
|
|
fc3b6d2c2e | ||
|
|
d0d0716b3b | ||
|
|
73119afeff | ||
|
|
8939179be8 | ||
|
|
adba2e68db | ||
|
|
0ef8b471a3 | ||
|
|
205950fc5f | ||
|
|
8835b2c745 | ||
|
|
74fda40764 | ||
|
|
687a36937e | ||
|
|
2c7b8bd6e8 | ||
|
|
e9417f5fa1 | ||
|
|
5e08b34c43 | ||
|
|
7b436ffb3b | ||
|
|
1ee2ec3728 | ||
|
|
ed28768146 | ||
|
|
f931835176 | ||
|
|
0cf9bc2814 | ||
|
|
8b428f2c93 | ||
|
|
10238dbcd3 | ||
|
|
6229e0928d | ||
|
|
5c7b259fe9 | ||
|
|
cc84c867c0 | ||
|
|
5bf252e12d | ||
|
|
75512ff66a | ||
|
|
6f4832476a | ||
|
|
86b0d27299 | ||
|
|
5a8845f7f6 | ||
|
|
709e47f32f | ||
|
|
77590b4eb3 | ||
|
|
72fc313135 | ||
|
|
39b32cee2e | ||
|
|
bdcee7c052 | ||
|
|
4093e53b5b | ||
|
|
a4c1c8de24 | ||
|
|
7ed234c875 | ||
|
|
0359160ac6 | ||
|
|
2478dad9b5 | ||
|
|
690292352b | ||
|
|
271d18cddc | ||
|
|
e1465639e7 | ||
|
|
ce7ca59339 | ||
|
|
76610b25d7 | ||
|
|
dfce0bda7c | ||
|
|
41c0dd9727 | ||
|
|
232c0389d3 | ||
|
|
d95c8a4ab0 | ||
|
|
374fdb37e1 | ||
|
|
f78530636e | ||
|
|
0bf81f4fb9 | ||
|
|
dcc3b7f36b | ||
|
|
d6722266f5 | ||
|
|
11566891dc | ||
|
|
9aaf69cc9a | ||
|
|
a716a59489 | ||
|
|
bad0d1bbcf | ||
|
|
aa43e265b7 | ||
|
|
67280951ff | ||
|
|
c3a9f6f9f5 | ||
|
|
e631e46dd1 | ||
|
|
15163ffde0 | ||
|
|
b898284821 | ||
|
|
3bb7eb2e03 | ||
|
|
11fcb9d456 | ||
|
|
29f826448a | ||
|
|
a8cf4a7120 | ||
|
|
60dce94a47 | ||
|
|
c965b9cc24 | ||
|
|
762a32eb6d | ||
|
|
541dac1aee | ||
|
|
e2e2329301 | ||
|
|
9afad71b0f | ||
|
|
7bbe0f7e8a | ||
|
|
d90adfa98e | ||
|
|
1dbcc4b776 | ||
|
|
18696fca2d | ||
|
|
c40100b6da | ||
|
|
6396818740 | ||
|
|
6df5a0d47b | ||
|
|
15c18c5547 | ||
|
|
4b6981c2e7 | ||
|
|
1b4025300c | ||
|
|
983c871bff | ||
|
|
609f532f8c | ||
|
|
c6fc03c73a | ||
|
|
b6e48cc4f9 | ||
|
|
50f43cc178 | ||
|
|
f421067a0d | ||
|
|
edd9a2d5b6 | ||
|
|
41e0329f55 | ||
|
|
21ea916e0d | ||
|
|
3b6ca3020e | ||
|
|
3737845b78 | ||
|
|
a30c78cd12 | ||
|
|
f8fba41a30 | ||
|
|
052902fb49 | ||
|
|
58143c26bc | ||
|
|
5d83894056 | ||
|
|
e35667525f | ||
|
|
2f318152d8 | ||
|
|
bc518a9ae8 | ||
|
|
425ae2d536 | ||
|
|
a6cacd4aba | ||
|
|
5a7083537c | ||
|
|
e15f7a59fd | ||
|
|
a2fa526aa0 | ||
|
|
7e959d6a87 | ||
|
|
5121fe1cbf | ||
|
|
3c3d4fc151 | ||
|
|
a7f75c9a6c | ||
|
|
e7262b8fbe | ||
|
|
af3099c526 | ||
|
|
2b9d47cea8 | ||
|
|
5db90d3fc4 | ||
|
|
372d5ff758 | ||
|
|
ebf39eaea1 | ||
|
|
556786f2e1 | ||
|
|
0f64994537 | ||
|
|
ac64c2022b | ||
|
|
9802132b6f | ||
|
|
4d6c5c14f1 | ||
|
|
d228a5fb93 | ||
|
|
7b46ef10c8 | ||
|
|
8eb9fba051 | ||
|
|
7b58182683 | ||
|
|
95fbf9274b | ||
|
|
09557dfa0f | ||
|
|
4029fcc1ca | ||
|
|
d6627b1e34 | ||
|
|
86d75fd767 | ||
|
|
e2220bb3b3 | ||
|
|
1a9b8bd1da | ||
|
|
9eda2d3f77 | ||
|
|
e6d59df705 | ||
|
|
db845850b2 | ||
|
|
d516597659 | ||
|
|
3af5a8145a | ||
|
|
31bebd4a7a | ||
|
|
ccb87258b0 | ||
|
|
2ab71e88bd | ||
|
|
78bd7585bb | ||
|
|
92cc81d9b8 | ||
|
|
97a1c741de | ||
|
|
077d693959 | ||
|
|
94831fc10a | ||
|
|
1f298a88b0 | ||
|
|
7e49946bed | ||
|
|
1e81ab1017 | ||
|
|
311180d12e | ||
|
|
0efb206881 | ||
|
|
0b92fa5615 | ||
|
|
a58a5b56b2 | ||
|
|
b9764c0a44 | ||
|
|
590f7ba511 | ||
|
|
449909b0e8 | ||
|
|
40780525f8 | ||
|
|
0e3707d1c3 | ||
|
|
8ddbef093b | ||
|
|
4ccdfccdf0 | ||
|
|
3c8aa0c630 | ||
|
|
ba519f996a | ||
|
|
62d9b92e6e | ||
|
|
d2c909584d | ||
|
|
219916eb14 | ||
|
|
0a37ac901f | ||
|
|
4586baea27 | ||
|
|
d83961e85b | ||
|
|
7e35716ff0 | ||
|
|
d0cf1ac605 | ||
|
|
9a19d07ab8 | ||
|
|
922be0b164 | ||
|
|
d0b8e2e37e | ||
|
|
e3d6e08b6a | ||
|
|
16a6623095 | ||
|
|
7bf31ca149 | ||
|
|
030f2360ca | ||
|
|
08eafc4214 | ||
|
|
134ece9385 | ||
|
|
3e4b06e959 | ||
|
|
3e3c6f70ba | ||
|
|
3097378a10 | ||
|
|
53f7a0f78a | ||
|
|
45a6841772 | ||
|
|
80b93a6e33 | ||
|
|
6aaab641be | ||
|
|
4999490c06 | ||
|
|
c7c1cd8bb3 | ||
|
|
8a1f116305 | ||
|
|
f8fe1dda6d | ||
|
|
bf44232d5c | ||
|
|
8c8ba47ef9 | ||
|
|
b3580002d0 | ||
|
|
1249199b22 | ||
|
|
f39a47f354 | ||
|
|
f58f15925f | ||
|
|
a071f8bb86 | ||
|
|
d33609ee57 | ||
|
|
f2c08bc1e8 | ||
|
|
688d8ed7bc | ||
|
|
2ddb4018b8 | ||
|
|
3ec25683f7 | ||
|
|
25d426f483 | ||
|
|
9d91ebc47a | ||
|
|
c784ab6632 | ||
|
|
ed5e3f13a1 | ||
|
|
3dcd82745e | ||
|
|
c564dc5f29 | ||
|
|
d64453b661 | ||
|
|
834ed7db0e | ||
|
|
db1dde533f | ||
|
|
9c66d9465e | ||
|
|
8c193d004e | ||
|
|
7cbd7f999a | ||
|
|
4340c8cb00 | ||
|
|
6f6583772d | ||
|
|
c62bc8d5da | ||
|
|
f68bfcb3e2 | ||
|
|
a37ade13cd | ||
|
|
1e0129f2d8 | ||
|
|
2e1d4381b0 | ||
|
|
a409b4f49c | ||
|
|
926776204b | ||
|
|
bba490127d | ||
|
|
80672c43c0 | ||
|
|
926e0ea0e5 | ||
|
|
6da169cd56 | ||
|
|
aaf10aa118 | ||
|
|
145bdacf13 | ||
|
|
51309efd46 | ||
|
|
60fe143b07 | ||
|
|
3f921b69f6 | ||
|
|
d2ac81d7ba | ||
|
|
2e5a9479a8 | ||
|
|
ec664ab6a2 | ||
|
|
8818bf8cf0 | ||
|
|
cc53e0c8ff | ||
|
|
8353499d18 | ||
|
|
d2c5994616 | ||
|
|
500b9097f7 | ||
|
|
7993266e6e | ||
|
|
74090103a9 | ||
|
|
a18cde2340 | ||
|
|
ee98e97876 | ||
|
|
ff63bafd72 | ||
|
|
3f8c8be77a | ||
|
|
769cdc73e7 | ||
|
|
cef8f74c00 | ||
|
|
9f5ab709de | ||
|
|
c6fdae3b3c | ||
|
|
320cf0fea2 | ||
|
|
0261f1262f | ||
|
|
5278ae3662 | ||
|
|
2941c44de2 | ||
|
|
627222602e | ||
|
|
e13b1ffc48 | ||
|
|
10bafce217 | ||
|
|
4817969495 | ||
|
|
2feda55eb7 | ||
|
|
a74cfd453a | ||
|
|
a360f65133 | ||
|
|
6a903d65a7 | ||
|
|
d4b3166036 | ||
|
|
21a54e2333 | ||
|
|
37c58bffba | ||
|
|
4a42b7277c | ||
|
|
071aea397b | ||
|
|
9c02f918c1 | ||
|
|
15c5c9615e | ||
|
|
0b74692d62 | ||
|
|
8f24f55eb7 | ||
|
|
cd5ab1ae9b | ||
|
|
d29ea97948 | ||
|
|
9930e7bb93 | ||
|
|
b1d37e4848 | ||
|
|
893152cdf3 | ||
|
|
efbf98751d | ||
|
|
caac294e79 | ||
|
|
d145ad8f47 | ||
|
|
628034ae8c | ||
|
|
fee02a3d16 | ||
|
|
ce5ae13bf6 | ||
|
|
26b6a94e32 | ||
|
|
be6ef475ba | ||
|
|
55e218ac37 | ||
|
|
7d1f636cdd | ||
|
|
a4b8b9b09b | ||
|
|
4e8e28d27c | ||
|
|
4b52414435 | ||
|
|
811b96bcbe | ||
|
|
f3207855ca | ||
|
|
9f3c5b93e3 | ||
|
|
a56357f0c1 | ||
|
|
f8d14d2e56 | ||
|
|
b0c30759c8 | ||
|
|
54037e6e10 | ||
|
|
fc260d4a37 | ||
|
|
387d4e5bc9 | ||
|
|
4ffd46b391 | ||
|
|
3b49370c06 | ||
|
|
925d26e54c | ||
|
|
1283006b53 | ||
|
|
83a718c9db | ||
|
|
2abf03ccb7 | ||
|
|
a59810a487 | ||
|
|
9cc9e06ad1 | ||
|
|
0108a0c146 | ||
|
|
4188758d84 | ||
|
|
2144100f81 | ||
|
|
8a61e04be6 | ||
|
|
eaeb083891 | ||
|
|
25e117b412 | ||
|
|
6b7ceed1a5 | ||
|
|
0533fc98a2 | ||
|
|
edde3ab2fb | ||
|
|
ec20e518be | ||
|
|
930a582afa | ||
|
|
7536eeea54 | ||
|
|
d940d43907 | ||
|
|
3ad3dd9493 | ||
|
|
553df7ac85 | ||
|
|
9648602252 | ||
|
|
63d0d13330 | ||
|
|
ff9b538a49 | ||
|
|
6632da34c0 | ||
|
|
178c9ff4d0 | ||
|
|
bfefeae5c1 | ||
|
|
ebf953cbc4 | ||
|
|
19ec3321ae | ||
|
|
d82af9f1a0 | ||
|
|
4712d2c8ac | ||
|
|
a2df4217fd | ||
|
|
096c96550b | ||
|
|
2fb3f31930 | ||
|
|
f52b4a2e11 | ||
|
|
bbc78ffec6 | ||
|
|
3606f412b3 | ||
|
|
6e0e94094b | ||
|
|
976960f495 | ||
|
|
d2f2f3b2d3 | ||
|
|
2da7c45840 | ||
|
|
fb078de627 | ||
|
|
da46b7fddb | ||
|
|
c4e0729b7d | ||
|
|
eee9f0ace5 | ||
|
|
d0692458a3 | ||
|
|
c96e6c7c92 | ||
|
|
4956278f17 | ||
|
|
5858a86624 | ||
|
|
c87d178a6a | ||
|
|
6fc872b1fd | ||
|
|
589c5783a0 | ||
|
|
40165628d6 | ||
|
|
435be77249 | ||
|
|
b932ba856d | ||
|
|
094514f617 | ||
|
|
fab96879d0 | ||
|
|
367a5f0c57 | ||
|
|
0d2ecfbc67 | ||
|
|
5410ba3b1d | ||
|
|
716bfd4611 | ||
|
|
3830a58493 | ||
|
|
31d5cf6129 | ||
|
|
8c78d20e6e | ||
|
|
abe78cf0bb | ||
|
|
eeebfd5f04 | ||
|
|
5e3d3dd023 | ||
|
|
60175ebfad | ||
|
|
c6fafe453f | ||
|
|
838ecd0dd8 | ||
|
|
beec605e2c | ||
|
|
da1405a060 | ||
|
|
e38eeae533 | ||
|
|
8442e7e326 | ||
|
|
e6c82e2003 | ||
|
|
c4de972c53 | ||
|
|
d2adbaec89 | ||
|
|
72736588c9 | ||
|
|
46e726363a | ||
|
|
f7e5292b8c | ||
|
|
f67954fa3e | ||
|
|
ba6c6a17ae | ||
|
|
c21d61a70d | ||
|
|
df857d3a84 | ||
|
|
e9564f101b | ||
|
|
e8a2a69649 | ||
|
|
ba416b1294 | ||
|
|
3024b598ad | ||
|
|
5ac7a34a13 | ||
|
|
b63181d9b5 | ||
|
|
a3119e2cc7 | ||
|
|
e8b8971b72 | ||
|
|
d5f5fa0e0a | ||
|
|
c284f90a1a | ||
|
|
349df60ce8 | ||
|
|
5ccc3f4ccd | ||
|
|
8ba61bb3a6 | ||
|
|
cb3d413e5e | ||
|
|
1837699d8c | ||
|
|
2ef5dec466 | ||
|
|
a46c7acbd2 | ||
|
|
769d645237 | ||
|
|
389330df2e | ||
|
|
9d75913005 | ||
|
|
887ca772e0 | ||
|
|
45286598aa | ||
|
|
2e074b5bc4 | ||
|
|
560e3c257a | ||
|
|
6e0202fa0b | ||
|
|
ef241b1a07 | ||
|
|
f80cb541d5 | ||
|
|
1fe61f72e2 | ||
|
|
bf1b0edfd2 | ||
|
|
f0ab946b88 | ||
|
|
975ef1a43d | ||
|
|
7064865157 | ||
|
|
3dd738b0db | ||
|
|
6480dde247 | ||
|
|
555dab7403 | ||
|
|
13158a28e1 | ||
|
|
cbaca87bbc | ||
|
|
5166202f67 | ||
|
|
35d45ca47d | ||
|
|
b66ede98c7 | ||
|
|
1ba8f2ccbf | ||
|
|
82133c117a | ||
|
|
f71afca828 | ||
|
|
87f5231c9a | ||
|
|
b17f506c20 | ||
|
|
7f15f12668 | ||
|
|
e53e62bfa0 | ||
|
|
2e74517a4a | ||
|
|
aed888051e | ||
|
|
bd584124bb | ||
|
|
e658a7fa6b | ||
|
|
52108707bb | ||
|
|
0e226a8f78 | ||
|
|
1bf3e025b8 | ||
|
|
a0fdcca129 | ||
|
|
3e75e96718 | ||
|
|
1dd875adac | ||
|
|
2341f789ab | ||
|
|
8a95847b0a | ||
|
|
ea9f3eacbc | ||
|
|
435363a246 | ||
|
|
b6520cb6f9 | ||
|
|
d6d73f5165 | ||
|
|
f58dbceec7 | ||
|
|
3f1bb1a214 | ||
|
|
892d2acaa2 | ||
|
|
4e27d1b5a0 | ||
|
|
fff3272e42 | ||
|
|
803198620d | ||
|
|
9017fe70d5 | ||
|
|
ce47762fbf | ||
|
|
b40677a3ca | ||
|
|
c283c4e595 | ||
|
|
6aabaea96c | ||
|
|
65adb2f2b4 | ||
|
|
dcaf926a95 | ||
|
|
587278f282 | ||
|
|
cc3793cbcd | ||
|
|
c268a5dd07 | ||
|
|
58d10df5e3 | ||
|
|
ae2d01a878 | ||
|
|
dbc5b0b742 | ||
|
|
6af12c2335 | ||
|
|
dafb50d6a9 | ||
|
|
13c59b6618 | ||
|
|
8f95c4f179 | ||
|
|
9fd34aec7f | ||
|
|
17c7a3e7c5 | ||
|
|
40a8c110bf | ||
|
|
c9cd0a4d1f | ||
|
|
45eae77f8f | ||
|
|
926202900c | ||
|
|
21aa669503 | ||
|
|
dee28e7a7a | ||
|
|
c8f46a3666 | ||
|
|
ba7fe10a08 | ||
|
|
9e9db72878 | ||
|
|
69a87e2ab7 | ||
|
|
8ec2686e72 | ||
|
|
d034ca4d1f | ||
|
|
61cf71fd67 | ||
|
|
63dd1330e9 | ||
|
|
4411650c5a | ||
|
|
1c15751949 | ||
|
|
82acf4f107 | ||
|
|
5525ae8921 | ||
|
|
b5f7208b0d | ||
|
|
3b5bc98053 | ||
|
|
3a9ade667a | ||
|
|
057c32d410 | ||
|
|
33657c9c92 | ||
|
|
4c199256a5 | ||
|
|
9e62776905 | ||
|
|
9ba74b945d | ||
|
|
66656ce612 | ||
|
|
73a20ad829 | ||
|
|
6fc9a546cb | ||
|
|
3f6c225948 | ||
|
|
c7e9650a39 | ||
|
|
fd28a36b74 | ||
|
|
86543c84db | ||
|
|
706cdac4cc | ||
|
|
0b69743792 | ||
|
|
dd54a00172 | ||
|
|
b5514b891a | ||
|
|
31a6eca97f | ||
|
|
8557676561 | ||
|
|
39631461ae | ||
|
|
a124d7f714 | ||
|
|
fff07bf70b | ||
|
|
53d09bb5d8 | ||
|
|
a8ecf0bef0 | ||
|
|
74b3902d5f | ||
|
|
7433ed968e | ||
|
|
ddf2d69a68 | ||
|
|
f5b34861bd | ||
|
|
f0ff31a1af | ||
|
|
8ce76fba62 | ||
|
|
a947672320 | ||
|
|
5496e2b553 | ||
|
|
2f9edc65dd | ||
|
|
6372337456 | ||
|
|
b843606ebe | ||
|
|
7cda083b31 | ||
|
|
095604d4ca | ||
|
|
7e799d02e3 | ||
|
|
7f4612f872 | ||
|
|
6f6ff56338 | ||
|
|
27701a75d9 | ||
|
|
3717d2ed32 | ||
|
|
3a9b6665ee | ||
|
|
bbc42f8898 | ||
|
|
f6aea47c2c | ||
|
|
23069ac2fc | ||
|
|
0b2fdc598b | ||
|
|
aac30b923c | ||
|
|
318caf6123 | ||
|
|
6898893ffb | ||
|
|
cb34f4e91f | ||
|
|
0da4b320f1 | ||
|
|
facb5bd13e | ||
|
|
22bc768868 | ||
|
|
a9b6f7bc9c | ||
|
|
830fb7192d | ||
|
|
e8ff7a4ae8 | ||
|
|
dac2a56671 | ||
|
|
2718cf5ccb | ||
|
|
202a5d435c | ||
|
|
58af8d0e90 | ||
|
|
b950be9626 | ||
|
|
222277ae0a | ||
|
|
fbcf3a17b4 | ||
|
|
748cbdd03b | ||
|
|
57b26b6a21 | ||
|
|
a4026d46ce | ||
|
|
696daf7387 | ||
|
|
4a507a5625 | ||
|
|
ac1dd9a0ee | ||
|
|
44f1038516 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
target
|
||||
.idea/
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
22
LICENSE.txt
Normal file
22
LICENSE.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2011- Kohsuke Kawaguchi and other contributors
|
||||
|
||||
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.
|
||||
2
README
2
README
@@ -1,3 +1,3 @@
|
||||
Java API for GitHub
|
||||
|
||||
See http://kohsuke.org/github-api/ for more details
|
||||
See http://github-api.kohsuke.org/ for more details
|
||||
|
||||
121
pom.xml
121
pom.xml
@@ -3,11 +3,11 @@
|
||||
<parent>
|
||||
<groupId>org.kohsuke</groupId>
|
||||
<artifactId>pom</artifactId>
|
||||
<version>1</version>
|
||||
<version>14</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>github-api</artifactId>
|
||||
<version>1.16</version>
|
||||
<version>1.69</version>
|
||||
<name>GitHub API for Java</name>
|
||||
<url>http://github-api.kohsuke.org/</url>
|
||||
<description>GitHub API for Java</description>
|
||||
@@ -16,6 +16,7 @@
|
||||
<connection>scm:git:git@github.com/kohsuke/${project.artifactId}.git</connection>
|
||||
<developerConnection>scm:git:ssh://git@github.com/kohsuke/${project.artifactId}.git</developerConnection>
|
||||
<url>http://${project.artifactId}.kohsuke.org/</url>
|
||||
<tag>github-api-1.69</tag>
|
||||
</scm>
|
||||
|
||||
<distributionManagement>
|
||||
@@ -25,12 +26,16 @@
|
||||
</site>
|
||||
</distributionManagement>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.infradna.tool</groupId>
|
||||
<artifactId>bridge-method-injector</artifactId>
|
||||
<version>1.2</version>
|
||||
<version>1.14</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
@@ -39,32 +44,49 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>findbugs-maven-plugin</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<configuration>
|
||||
<xmlOutput>true</xmlOutput>
|
||||
<findbugsXmlWithMessages>true</findbugsXmlWithMessages>
|
||||
<failOnError>false</failOnError>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>run-findbugs</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jvnet.hudson</groupId>
|
||||
<artifactId>htmlunit</artifactId>
|
||||
<version>2.6-hudson-2</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<!-- hides JDK DOM classes in Eclipse -->
|
||||
<groupId>xml-apis</groupId>
|
||||
<artifactId>xml-apis</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<version>1.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<version>4.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
<version>1.5.0</version>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.2.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
@@ -74,10 +96,57 @@
|
||||
<dependency>
|
||||
<groupId>com.infradna.tool</groupId>
|
||||
<artifactId>bridge-method-annotation</artifactId>
|
||||
<version>1.4</version>
|
||||
<version>1.14</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.kohsuke.stapler</groupId>
|
||||
<artifactId>stapler-jetty</artifactId>
|
||||
<version>1.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jgit</groupId>
|
||||
<artifactId>org.eclipse.jgit</artifactId>
|
||||
<version>3.1.0.201310021548-r</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp</groupId>
|
||||
<artifactId>okhttp-urlconnection</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.kohsuke</groupId>
|
||||
<artifactId>wordnet-random-name</artifactId>
|
||||
<version>1.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>1.9.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>repo.jenkins-ci.org</id>
|
||||
<url>http://repo.jenkins-ci.org/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>repo.jenkins-ci.org</id>
|
||||
<url>http://repo.jenkins-ci.org/public/</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
<reporting>
|
||||
<plugins>
|
||||
@@ -87,4 +156,20 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</reporting>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>The MIT license</name>
|
||||
<url>http://www.opensource.org/licenses/mit-license.php</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<mailingLists>
|
||||
<mailingList>
|
||||
<name>User List</name>
|
||||
<post>github-api@googlegroups.com</post>
|
||||
<archive>https://groups.google.com/forum/#!forum/github-api</archive>
|
||||
</mailingList>
|
||||
</mailingLists>
|
||||
</project>
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
/**
|
||||
* Different API versions.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
enum ApiVersion {
|
||||
V2("https://github.com/api/v2/json"),
|
||||
V3("https://api.github.com");
|
||||
|
||||
final String url;
|
||||
|
||||
ApiVersion(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
101
src/main/java/org/kohsuke/github/GHAsset.java
Normal file
101
src/main/java/org/kohsuke/github/GHAsset.java
Normal file
@@ -0,0 +1,101 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Asset in a release.
|
||||
*
|
||||
* @see GHRelease#getAssets()
|
||||
*/
|
||||
public class GHAsset extends GHObject {
|
||||
GitHub root;
|
||||
GHRepository owner;
|
||||
private String name;
|
||||
private String label;
|
||||
private String state;
|
||||
private String content_type;
|
||||
private long size;
|
||||
private long download_count;
|
||||
private String browser_download_url;
|
||||
|
||||
public String getContentType() {
|
||||
return content_type;
|
||||
}
|
||||
|
||||
public void setContentType(String contentType) throws IOException {
|
||||
edit("content_type", contentType);
|
||||
this.content_type = contentType;
|
||||
}
|
||||
|
||||
public long getDownloadCount() {
|
||||
return download_count;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) throws IOException {
|
||||
edit("label", label);
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public GHRepository getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public GitHub getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This object has no HTML URL.
|
||||
*/
|
||||
@Override
|
||||
public URL getHtmlUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getBrowserDownloadUrl() {
|
||||
return browser_download_url;
|
||||
}
|
||||
|
||||
private void edit(String key, Object value) throws IOException {
|
||||
new Requester(root)._with(key, value).method("PATCH").to(getApiRoute());
|
||||
}
|
||||
|
||||
public void delete() throws IOException {
|
||||
new Requester(root).method("DELETE").to(getApiRoute());
|
||||
}
|
||||
|
||||
|
||||
private String getApiRoute() {
|
||||
return "/repos/" + owner.getOwnerName() + "/" + owner.getName() + "/releases/assets/" + id;
|
||||
}
|
||||
|
||||
GHAsset wrap(GHRelease release) {
|
||||
this.owner = release.getOwner();
|
||||
this.root = owner.root;
|
||||
return this;
|
||||
}
|
||||
|
||||
public static GHAsset[] wrap(GHAsset[] assets, GHRelease release) {
|
||||
for (GHAsset aTo : assets) {
|
||||
aTo.wrap(release);
|
||||
}
|
||||
return assets;
|
||||
}
|
||||
}
|
||||
92
src/main/java/org/kohsuke/github/GHAuthorization.java
Normal file
92
src/main/java/org/kohsuke/github/GHAuthorization.java
Normal file
@@ -0,0 +1,92 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Generated OAuth token
|
||||
*
|
||||
* @author janinko
|
||||
* @see GitHub#createToken(Collection, String, String)
|
||||
* @see <a href="http://developer.github.com/v3/oauth/#create-a-new-authorization">API documentation</a>
|
||||
*/
|
||||
public class GHAuthorization extends GHObject {
|
||||
public static final String USER = "user";
|
||||
public static final String USER_EMAIL = "user:email";
|
||||
public static final String USER_FOLLOW = "user:follow";
|
||||
public static final String PUBLIC_REPO = "public_repo";
|
||||
public static final String REPO = "repo";
|
||||
public static final String REPO_STATUS = "repo:status";
|
||||
public static final String DELETE_REPO = "delete_repo";
|
||||
public static final String NOTIFICATIONS = "notifications";
|
||||
public static final String GIST = "gist";
|
||||
public static final String READ_HOOK = "read:repo_hook";
|
||||
public static final String WRITE_HOOK = "write:repo_hook";
|
||||
public static final String AMIN_HOOK = "admin:repo_hook";
|
||||
public static final String READ_ORG = "read:org";
|
||||
public static final String WRITE_ORG = "write:org";
|
||||
public static final String ADMIN_ORG = "admin:org";
|
||||
public static final String READ_KEY = "read:public_key";
|
||||
public static final String WRITE_KEY = "write:public_key";
|
||||
public static final String ADMIN_KEY = "admin:public_key";
|
||||
|
||||
private GitHub root;
|
||||
private List<String> scopes;
|
||||
private String token;
|
||||
private App app;
|
||||
private String note;
|
||||
private String note_url;
|
||||
|
||||
public GitHub getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
public List<String> getScopes() {
|
||||
return scopes;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public URL getAppUrl() {
|
||||
return GitHub.parseURL(app.url);
|
||||
}
|
||||
|
||||
public String getAppName() {
|
||||
return app.name;
|
||||
}
|
||||
|
||||
public URL getApiURL() {
|
||||
return GitHub.parseURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This object has no HTML URL.
|
||||
*/
|
||||
@Override
|
||||
public URL getHtmlUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getNote() {
|
||||
return note;
|
||||
}
|
||||
|
||||
public URL getNoteUrl() {
|
||||
return GitHub.parseURL(note_url);
|
||||
}
|
||||
|
||||
/*package*/ GHAuthorization wrap(GitHub root) {
|
||||
this.root = root;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
private static class App {
|
||||
private String url;
|
||||
private String name;
|
||||
}
|
||||
}
|
||||
52
src/main/java/org/kohsuke/github/GHBranch.java
Normal file
52
src/main/java/org/kohsuke/github/GHBranch.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
/**
|
||||
* A branch in a repository.
|
||||
*
|
||||
* @author Yusuke Kokubo
|
||||
*/
|
||||
public class GHBranch {
|
||||
private GitHub root;
|
||||
private GHRepository owner;
|
||||
|
||||
private String name;
|
||||
private Commit commit;
|
||||
|
||||
public static class Commit {
|
||||
String sha,url;
|
||||
}
|
||||
|
||||
public GitHub getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repository that this branch is in.
|
||||
*/
|
||||
public GHRepository getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The commit that this branch currently points to.
|
||||
*/
|
||||
public String getSHA1() {
|
||||
return commit.sha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final String url = owner != null ? owner.getUrl().toString() : "unknown";
|
||||
return "Branch:" + name + " in " + url;
|
||||
}
|
||||
|
||||
/*package*/ GHBranch wrap(GHRepository repo) {
|
||||
this.owner = repo;
|
||||
this.root = repo.root;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
310
src/main/java/org/kohsuke/github/GHCommit.java
Normal file
310
src/main/java/org/kohsuke/github/GHCommit.java
Normal file
@@ -0,0 +1,310 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.AbstractList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A commit in a repository.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GHRepository#getCommit(String)
|
||||
* @see GHCommitComment#getCommit()
|
||||
*/
|
||||
public class GHCommit {
|
||||
private GHRepository owner;
|
||||
|
||||
private ShortInfo commit;
|
||||
|
||||
/**
|
||||
* Short summary of this commit.
|
||||
*/
|
||||
public static class ShortInfo {
|
||||
private GHAuthor author;
|
||||
private GHAuthor committer;
|
||||
|
||||
private String message;
|
||||
|
||||
private int comment_count;
|
||||
|
||||
@WithBridgeMethods(value = GHAuthor.class, castRequired = true)
|
||||
public GitUser getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
@WithBridgeMethods(value = GHAuthor.class, castRequired = true)
|
||||
public GitUser getCommitter() {
|
||||
return committer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit message.
|
||||
*/
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public int getCommentCount() {
|
||||
return comment_count;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link GitUser} instead.
|
||||
*/
|
||||
public static class GHAuthor extends GitUser {
|
||||
}
|
||||
|
||||
public static class Stats {
|
||||
int total,additions,deletions;
|
||||
}
|
||||
|
||||
/**
|
||||
* A file that was modified.
|
||||
*/
|
||||
public static class File {
|
||||
String status;
|
||||
int changes,additions,deletions;
|
||||
String raw_url, blob_url, sha, patch;
|
||||
String filename, previous_filename;
|
||||
|
||||
/**
|
||||
* Number of lines added + removed.
|
||||
*/
|
||||
public int getLinesChanged() {
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of lines added.
|
||||
*/
|
||||
public int getLinesAdded() {
|
||||
return additions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of lines removed.
|
||||
*/
|
||||
public int getLinesDeleted() {
|
||||
return deletions;
|
||||
}
|
||||
|
||||
/**
|
||||
* "modified", "added", or "deleted"
|
||||
*/
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Full path in the repository.
|
||||
*/
|
||||
public String getFileName() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Previous path, in case file has moved.
|
||||
*/
|
||||
public String getPreviousFilename() {
|
||||
return previous_filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual change.
|
||||
*/
|
||||
public String getPatch() {
|
||||
return patch;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL like 'https://raw.github.com/jenkinsci/jenkins/4eb17c197dfdcf8ef7ff87eb160f24f6a20b7f0e/core/pom.xml'
|
||||
* that resolves to the actual content of the file.
|
||||
*/
|
||||
public URL getRawUrl() {
|
||||
return GitHub.parseURL(raw_url);
|
||||
}
|
||||
|
||||
/**
|
||||
* URL like 'https://github.com/jenkinsci/jenkins/blob/1182e2ebb1734d0653142bd422ad33c21437f7cf/core/pom.xml'
|
||||
* that resolves to the HTML page that describes this file.
|
||||
*/
|
||||
public URL getBlobUrl() {
|
||||
return GitHub.parseURL(blob_url);
|
||||
}
|
||||
|
||||
/**
|
||||
* [0-9a-f]{40} SHA1 checksum.
|
||||
*/
|
||||
public String getSha() {
|
||||
return sha;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Parent {
|
||||
String url,sha;
|
||||
}
|
||||
|
||||
static class User {
|
||||
// TODO: what if someone who doesn't have an account on GitHub makes a commit?
|
||||
String url,avatar_url,login,gravatar_id;
|
||||
int id;
|
||||
}
|
||||
|
||||
String url,sha;
|
||||
List<File> files;
|
||||
Stats stats;
|
||||
List<Parent> parents;
|
||||
User author,committer;
|
||||
|
||||
|
||||
public ShortInfo getCommitShortInfo() {
|
||||
return commit;
|
||||
}
|
||||
|
||||
/**
|
||||
* The repository that contains the commit.
|
||||
*/
|
||||
public GHRepository getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of lines added + removed.
|
||||
*/
|
||||
public int getLinesChanged() {
|
||||
return stats.total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of lines added.
|
||||
*/
|
||||
public int getLinesAdded() {
|
||||
return stats.additions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of lines removed.
|
||||
*/
|
||||
public int getLinesDeleted() {
|
||||
return stats.deletions;
|
||||
}
|
||||
|
||||
/**
|
||||
* [0-9a-f]{40} SHA1 checksum.
|
||||
*/
|
||||
public String getSHA1() {
|
||||
return sha;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of files changed/added/removed in this commit.
|
||||
*
|
||||
* @return
|
||||
* Can be empty but never null.
|
||||
*/
|
||||
public List<File> getFiles() {
|
||||
return files!=null ? Collections.unmodifiableList(files) : Collections.<File>emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SHA1 of parent commit objects.
|
||||
*/
|
||||
public List<String> getParentSHA1s() {
|
||||
if (parents==null) return Collections.emptyList();
|
||||
return new AbstractList<String>() {
|
||||
@Override
|
||||
public String get(int index) {
|
||||
return parents.get(index).sha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return parents.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the parent commit objects and return them.
|
||||
*/
|
||||
public List<GHCommit> getParents() throws IOException {
|
||||
List<GHCommit> r = new ArrayList<GHCommit>();
|
||||
for (String sha1 : getParentSHA1s())
|
||||
r.add(owner.getCommit(sha1));
|
||||
return r;
|
||||
}
|
||||
|
||||
public GHUser getAuthor() throws IOException {
|
||||
return resolveUser(author);
|
||||
}
|
||||
|
||||
public GHUser getCommitter() throws IOException {
|
||||
return resolveUser(committer);
|
||||
}
|
||||
|
||||
private GHUser resolveUser(User author) throws IOException {
|
||||
if (author==null || author.login==null) return null;
|
||||
return owner.root.getUser(author.login);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists up all the commit comments in this repository.
|
||||
*/
|
||||
public PagedIterable<GHCommitComment> listComments() {
|
||||
return new PagedIterable<GHCommitComment>() {
|
||||
public PagedIterator<GHCommitComment> iterator() {
|
||||
return new PagedIterator<GHCommitComment>(owner.root.retrieve().asIterator(String.format("/repos/%s/%s/commits/%s/comments", owner.getOwnerName(), owner.getName(), sha), GHCommitComment[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHCommitComment[] page) {
|
||||
for (GHCommitComment c : page)
|
||||
c.wrap(owner);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a commit comment.
|
||||
*
|
||||
* I'm not sure how path/line/position parameters interact with each other.
|
||||
*/
|
||||
public GHCommitComment createComment(String body, String path, Integer line, Integer position) throws IOException {
|
||||
GHCommitComment r = new Requester(owner.root)
|
||||
.with("body",body)
|
||||
.with("path",path)
|
||||
.with("line",line)
|
||||
.with("position",position)
|
||||
.to(String.format("/repos/%s/%s/commits/%s/comments",owner.getOwnerName(),owner.getName(),sha),GHCommitComment.class);
|
||||
return r.wrap(owner);
|
||||
}
|
||||
|
||||
public GHCommitComment createComment(String body) throws IOException {
|
||||
return createComment(body,null,null,null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status of this commit, newer ones first.
|
||||
*/
|
||||
public PagedIterable<GHCommitStatus> listStatuses() throws IOException {
|
||||
return owner.listCommitStatuses(sha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last status of this commit, which is what gets shown in the UI.
|
||||
*/
|
||||
public GHCommitStatus getLastStatus() throws IOException {
|
||||
return owner.getLastCommitStatus(sha);
|
||||
}
|
||||
|
||||
GHCommit wrapUp(GHRepository owner) {
|
||||
this.owner = owner;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
108
src/main/java/org/kohsuke/github/GHCommitComment.java
Normal file
108
src/main/java/org/kohsuke/github/GHCommitComment.java
Normal file
@@ -0,0 +1,108 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A comment attached to a commit (or a specific line in a specific file of a commit.)
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GHRepository#listCommitComments()
|
||||
* @see GHCommit#listComments()
|
||||
* @see GHCommit#createComment(String, String, Integer, Integer)
|
||||
*/
|
||||
public class GHCommitComment extends GHObject {
|
||||
private GHRepository owner;
|
||||
|
||||
String body, html_url, commit_id;
|
||||
Integer line;
|
||||
String path;
|
||||
User user;
|
||||
|
||||
static class User {
|
||||
// TODO: what if someone who doesn't have an account on GitHub makes a commit?
|
||||
String url,avatar_url,login,gravatar_id;
|
||||
int id;
|
||||
}
|
||||
|
||||
public GHRepository getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL like 'https://github.com/kohsuke/sandbox-ant/commit/8ae38db0ea5837313ab5f39d43a6f73de3bd9000#commitcomment-1252827' to
|
||||
* show this commit comment in a browser.
|
||||
*/
|
||||
public URL getHtmlUrl() {
|
||||
return GitHub.parseURL(html_url);
|
||||
}
|
||||
|
||||
public String getSHA1() {
|
||||
return commit_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit comment in the GitHub flavored markdown format.
|
||||
*/
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* A commit comment can be on a specific line of a specific file, if so, this field points to a file.
|
||||
* Otherwise null.
|
||||
*/
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* A commit comment can be on a specific line of a specific file, if so, this field points to the line number in the file.
|
||||
* Otherwise -1.
|
||||
*/
|
||||
public int getLine() {
|
||||
return line!=null ? line : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user who put this comment.
|
||||
*/
|
||||
public GHUser getUser() throws IOException {
|
||||
return owner.root.getUser(user.login);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the commit to which this comment is associated with.
|
||||
*/
|
||||
public GHCommit getCommit() throws IOException {
|
||||
return getOwner().getCommit(getSHA1());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the body of the commit message.
|
||||
*/
|
||||
public void update(String body) throws IOException {
|
||||
GHCommitComment r = new Requester(owner.root)
|
||||
.with("body", body)
|
||||
.method("PATCH").to(getApiTail(), GHCommitComment.class);
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this comment.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
new Requester(owner.root).method("DELETE").to(getApiTail());
|
||||
}
|
||||
|
||||
private String getApiTail() {
|
||||
return String.format("/repos/%s/%s/comments/%s",owner.getOwnerName(),owner.getName(),id);
|
||||
}
|
||||
|
||||
|
||||
GHCommitComment wrap(GHRepository owner) {
|
||||
this.owner = owner;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@
|
||||
*/
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Identifies a commit in {@link GHPullRequest}.
|
||||
*
|
||||
@@ -31,11 +33,11 @@ package org.kohsuke.github;
|
||||
public class GHCommitPointer {
|
||||
private String ref, sha, label;
|
||||
private GHUser user;
|
||||
private GHRepository repository;
|
||||
private GHRepository repo;
|
||||
|
||||
/**
|
||||
* This points to the user who owns
|
||||
* the {@link #repository}.
|
||||
* the {@link #getRepository()}.
|
||||
*/
|
||||
public GHUser getUser() {
|
||||
return user;
|
||||
@@ -45,11 +47,11 @@ public class GHCommitPointer {
|
||||
* The repository that contains the commit.
|
||||
*/
|
||||
public GHRepository getRepository() {
|
||||
return repository;
|
||||
return repo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Named ref to the commit.
|
||||
* Named ref to the commit. This appears to be a "short ref" that doesn't include "refs/heads/" portion.
|
||||
*/
|
||||
public String getRef() {
|
||||
return ref;
|
||||
@@ -68,4 +70,16 @@ public class GHCommitPointer {
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the commit that this pointer is referring to.
|
||||
*/
|
||||
public GHCommit getCommit() throws IOException {
|
||||
return getRepository().getCommit(getSha());
|
||||
}
|
||||
|
||||
void wrapUp(GitHub root) {
|
||||
if (user!=null) user.root = root;
|
||||
if (repo!=null) repo.wrap(root);
|
||||
}
|
||||
}
|
||||
|
||||
105
src/main/java/org/kohsuke/github/GHCommitQueryBuilder.java
Normal file
105
src/main/java/org/kohsuke/github/GHCommitQueryBuilder.java
Normal file
@@ -0,0 +1,105 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Builds up query for listing commits.
|
||||
*
|
||||
* <p>
|
||||
* Call various methods that set the filter criteria, then {@link #list()} method to actually list up the commit.
|
||||
*
|
||||
* <pre>
|
||||
* GHRepository r = ...;
|
||||
* for (GHCommit c : r.queryCommits().since(x).until(y).author("kohsuke")) {
|
||||
* ...
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GHRepository#queryCommits()
|
||||
*/
|
||||
public class GHCommitQueryBuilder {
|
||||
private final Requester req;
|
||||
private final GHRepository repo;
|
||||
|
||||
/*package*/ GHCommitQueryBuilder(GHRepository repo) {
|
||||
this.repo = repo;
|
||||
this.req = repo.root.retrieve(); // requester to build up
|
||||
}
|
||||
|
||||
/**
|
||||
* GItHub login or email address by which to filter by commit author.
|
||||
*/
|
||||
public GHCommitQueryBuilder author(String author) {
|
||||
req.with("author",author);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only commits containing this file path will be returned.
|
||||
*/
|
||||
public GHCommitQueryBuilder path(String path) {
|
||||
req.with("path",path);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the SHA1 commit / tag / branch / etc to start listing commits from.
|
||||
*
|
||||
*/
|
||||
public GHCommitQueryBuilder from(String ref) {
|
||||
req.with("sha",ref);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHCommitQueryBuilder pageSize(int pageSize) {
|
||||
req.with("per_page",pageSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only commits after this date will be returned
|
||||
*/
|
||||
public GHCommitQueryBuilder since(Date dt) {
|
||||
req.with("since",GitHub.printDate(dt));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only commits after this date will be returned
|
||||
*/
|
||||
public GHCommitQueryBuilder since(long timestamp) {
|
||||
return since(new Date(timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Only commits before this date will be returned
|
||||
*/
|
||||
public GHCommitQueryBuilder until(Date dt) {
|
||||
req.with("until",GitHub.printDate(dt));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only commits before this date will be returned
|
||||
*/
|
||||
public GHCommitQueryBuilder until(long timestamp) {
|
||||
return until(new Date(timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists up the commits with the criteria built so far.
|
||||
*/
|
||||
public PagedIterable<GHCommit> list() {
|
||||
return new PagedIterable<GHCommit>() {
|
||||
public PagedIterator<GHCommit> iterator() {
|
||||
return new PagedIterator<GHCommit>(req.asIterator(repo.getApiTailUrl("commits"), GHCommit[].class)) {
|
||||
protected void wrapUp(GHCommit[] page) {
|
||||
for (GHCommit c : page)
|
||||
c.wrapUp(repo);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
11
src/main/java/org/kohsuke/github/GHCommitState.java
Normal file
11
src/main/java/org/kohsuke/github/GHCommitState.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
/**
|
||||
* Represents the state of commit
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GHCommitStatus
|
||||
*/
|
||||
public enum GHCommitState {
|
||||
PENDING, SUCCESS, ERROR, FAILURE
|
||||
}
|
||||
64
src/main/java/org/kohsuke/github/GHCommitStatus.java
Normal file
64
src/main/java/org/kohsuke/github/GHCommitStatus.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Represents a status of a commit.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GHRepository#getLastCommitStatus(String)
|
||||
* @see GHCommit#getLastStatus()
|
||||
*/
|
||||
public class GHCommitStatus extends GHObject {
|
||||
String state;
|
||||
String target_url,description;
|
||||
String context;
|
||||
GHUser creator;
|
||||
|
||||
private GitHub root;
|
||||
|
||||
/*package*/ GHCommitStatus wrapUp(GitHub root) {
|
||||
if (creator!=null) creator.wrapUp(root);
|
||||
this.root = root;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHCommitState getState() {
|
||||
for (GHCommitState s : GHCommitState.values()) {
|
||||
if (s.name().equalsIgnoreCase(state))
|
||||
return s;
|
||||
}
|
||||
throw new IllegalStateException("Unexpected state: "+state);
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL that this status is linked to.
|
||||
*
|
||||
* This is the URL specified when creating a commit status.
|
||||
*/
|
||||
public String getTargetUrl() {
|
||||
return target_url;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public GHUser getCreator() {
|
||||
return creator;
|
||||
}
|
||||
|
||||
public String getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This object has no HTML URL.
|
||||
*/
|
||||
@Override
|
||||
public URL getHtmlUrl() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
153
src/main/java/org/kohsuke/github/GHCompare.java
Normal file
153
src/main/java/org/kohsuke/github/GHCompare.java
Normal file
@@ -0,0 +1,153 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* The model user for comparing 2 commits in the GitHub API.
|
||||
*
|
||||
* @author Michael Clarke
|
||||
*/
|
||||
public class GHCompare {
|
||||
|
||||
private String url, html_url, permalink_url, diff_url, patch_url;
|
||||
public Status status;
|
||||
private int ahead_by, behind_by, total_commits;
|
||||
private Commit base_commit, merge_base_commit;
|
||||
private Commit[] commits;
|
||||
private GHCommit.File[] files;
|
||||
|
||||
private GHRepository owner;
|
||||
|
||||
public URL getUrl() {
|
||||
return GitHub.parseURL(url);
|
||||
}
|
||||
|
||||
public URL getHtmlUrl() {
|
||||
return GitHub.parseURL(html_url);
|
||||
}
|
||||
|
||||
public URL getPermalinkUrl() {
|
||||
return GitHub.parseURL(permalink_url);
|
||||
}
|
||||
|
||||
public URL getDiffUrl() {
|
||||
return GitHub.parseURL(diff_url);
|
||||
}
|
||||
|
||||
public URL getPatchUrl() {
|
||||
return GitHub.parseURL(patch_url);
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public int getAheadBy() {
|
||||
return ahead_by;
|
||||
}
|
||||
|
||||
public int getBehindBy() {
|
||||
return behind_by;
|
||||
}
|
||||
|
||||
public int getTotalCommits() {
|
||||
return total_commits;
|
||||
}
|
||||
|
||||
public Commit getBaseCommit() {
|
||||
return base_commit;
|
||||
}
|
||||
|
||||
public Commit getMergeBaseCommit() {
|
||||
return merge_base_commit;
|
||||
}
|
||||
|
||||
public Commit[] getCommits() {
|
||||
return commits;
|
||||
}
|
||||
|
||||
public GHCommit.File[] getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
public GHCompare wrap(GHRepository owner) {
|
||||
this.owner = owner;
|
||||
for (Commit commit : commits) {
|
||||
commit.wrapUp(owner);
|
||||
}
|
||||
merge_base_commit.wrapUp(owner);
|
||||
base_commit.wrapUp(owner);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare commits had a child commit element with additional details we want to capture.
|
||||
* This extenstion of GHCommit provides that.
|
||||
*/
|
||||
public static class Commit extends GHCommit {
|
||||
|
||||
private InnerCommit commit;
|
||||
|
||||
public InnerCommit getCommit() {
|
||||
return commit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class InnerCommit {
|
||||
private String url, sha, message;
|
||||
private User author, committer;
|
||||
private Tree tree;
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getSha() {
|
||||
return sha;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@WithBridgeMethods(value=User.class,castRequired=true)
|
||||
public GitUser getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
@WithBridgeMethods(value=User.class,castRequired=true)
|
||||
public GitUser getCommitter() {
|
||||
return committer;
|
||||
}
|
||||
|
||||
public Tree getTree() {
|
||||
return tree;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Tree {
|
||||
private String url, sha;
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getSha() {
|
||||
return sha;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link GitUser} instead.
|
||||
*/
|
||||
public static class User extends GitUser {
|
||||
}
|
||||
|
||||
public static enum Status {
|
||||
behind, ahead, identical, diverged
|
||||
}
|
||||
}
|
||||
243
src/main/java/org/kohsuke/github/GHContent.java
Normal file
243
src/main/java/org/kohsuke/github/GHContent.java
Normal file
@@ -0,0 +1,243 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
|
||||
/**
|
||||
* A Content of a repository.
|
||||
*
|
||||
* @author Alexandre COLLIGNON
|
||||
* @see GHRepository#getFileContent(String)
|
||||
*/
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public class GHContent {
|
||||
/*
|
||||
In normal use of this class, repository field is set via wrap(),
|
||||
but in the code search API, there's a nested 'repository' field that gets populated from JSON.
|
||||
*/
|
||||
private GHRepository repository;
|
||||
|
||||
private GitHub root;
|
||||
|
||||
private String type;
|
||||
private String encoding;
|
||||
private long size;
|
||||
private String sha;
|
||||
private String name;
|
||||
private String path;
|
||||
private String content;
|
||||
private String url; // this is the API url
|
||||
private String git_url; // this is the Blob url
|
||||
private String html_url; // this is the UI
|
||||
private String download_url;
|
||||
|
||||
public GHRepository getOwner() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getEncoding() {
|
||||
return encoding;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public String getSha() {
|
||||
return sha;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the decoded content that is stored at this location.
|
||||
*
|
||||
* <p>
|
||||
* Due to the nature of GitHub's API, you're not guaranteed that
|
||||
* the content will already be populated, so this may trigger
|
||||
* network activity, and can throw an IOException.
|
||||
*
|
||||
* @deprecated
|
||||
* Use {@link #read()}
|
||||
*/
|
||||
public String getContent() throws IOException {
|
||||
return new String(DatatypeConverter.parseBase64Binary(getEncodedContent()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the base64-encoded content that is stored at this location.
|
||||
*
|
||||
* <p>
|
||||
* Due to the nature of GitHub's API, you're not guaranteed that
|
||||
* the content will already be populated, so this may trigger
|
||||
* network activity, and can throw an IOException.
|
||||
*
|
||||
* @deprecated
|
||||
* Use {@link #read()}
|
||||
*/
|
||||
public String getEncodedContent() throws IOException {
|
||||
if (content!=null)
|
||||
return content;
|
||||
else
|
||||
return Base64.encodeBase64String(IOUtils.toByteArray(read()));
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getGitUrl() {
|
||||
return git_url;
|
||||
}
|
||||
|
||||
public String getHtmlUrl() {
|
||||
return html_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the actual content stored here.
|
||||
*/
|
||||
public InputStream read() throws IOException {
|
||||
return new Requester(root).asStream(getDownloadUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* URL to retrieve the raw content of the file. Null if this is a directory.
|
||||
*/
|
||||
public String getDownloadUrl() throws IOException {
|
||||
populate();
|
||||
return download_url;
|
||||
}
|
||||
|
||||
public boolean isFile() {
|
||||
return "file".equals(type);
|
||||
}
|
||||
|
||||
public boolean isDirectory() {
|
||||
return "dir".equals(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully populate the data by retrieving missing data.
|
||||
*
|
||||
* Depending on the original API call where this object is created, it may not contain everything.
|
||||
*/
|
||||
protected synchronized void populate() throws IOException {
|
||||
if (download_url!=null) return; // already populated
|
||||
root.retrieve().to(url, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* List immediate children of this directory.
|
||||
*/
|
||||
public PagedIterable<GHContent> listDirectoryContent() throws IOException {
|
||||
if (!isDirectory())
|
||||
throw new IllegalStateException(path+" is not a directory");
|
||||
|
||||
return new PagedIterable<GHContent>() {
|
||||
public PagedIterator<GHContent> iterator() {
|
||||
return new PagedIterator<GHContent>(root.retrieve().asIterator(url, GHContent[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHContent[] page) {
|
||||
GHContent.wrap(page, repository);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public GHContentUpdateResponse update(String newContent, String commitMessage) throws IOException {
|
||||
return update(newContent.getBytes(), commitMessage, null);
|
||||
}
|
||||
|
||||
public GHContentUpdateResponse update(String newContent, String commitMessage, String branch) throws IOException {
|
||||
return update(newContent.getBytes(), commitMessage, branch);
|
||||
}
|
||||
|
||||
public GHContentUpdateResponse update(byte[] newContentBytes, String commitMessage) throws IOException {
|
||||
return update(newContentBytes, commitMessage, null);
|
||||
}
|
||||
|
||||
public GHContentUpdateResponse update(byte[] newContentBytes, String commitMessage, String branch) throws IOException {
|
||||
String encodedContent = DatatypeConverter.printBase64Binary(newContentBytes);
|
||||
|
||||
Requester requester = new Requester(root)
|
||||
.with("path", path)
|
||||
.with("message", commitMessage)
|
||||
.with("sha", sha)
|
||||
.with("content", encodedContent)
|
||||
.method("PUT");
|
||||
|
||||
if (branch != null) {
|
||||
requester.with("branch", branch);
|
||||
}
|
||||
|
||||
GHContentUpdateResponse response = requester.to(getApiRoute(), GHContentUpdateResponse.class);
|
||||
|
||||
response.getContent().wrap(repository);
|
||||
response.getCommit().wrapUp(repository);
|
||||
|
||||
this.content = encodedContent;
|
||||
return response;
|
||||
}
|
||||
|
||||
public GHContentUpdateResponse delete(String message) throws IOException {
|
||||
return delete(message, null);
|
||||
}
|
||||
|
||||
public GHContentUpdateResponse delete(String commitMessage, String branch) throws IOException {
|
||||
Requester requester = new Requester(root)
|
||||
.with("path", path)
|
||||
.with("message", commitMessage)
|
||||
.with("sha", sha)
|
||||
.method("DELETE");
|
||||
|
||||
if (branch != null) {
|
||||
requester.with("branch", branch);
|
||||
}
|
||||
|
||||
GHContentUpdateResponse response = requester.to(getApiRoute(), GHContentUpdateResponse.class);
|
||||
|
||||
response.getCommit().wrapUp(repository);
|
||||
return response;
|
||||
}
|
||||
|
||||
private String getApiRoute() {
|
||||
return "/repos/" + repository.getOwnerName() + "/" + repository.getName() + "/contents/" + path;
|
||||
}
|
||||
|
||||
GHContent wrap(GHRepository owner) {
|
||||
this.repository = owner;
|
||||
this.root = owner.root;
|
||||
return this;
|
||||
}
|
||||
GHContent wrap(GitHub root) {
|
||||
this.root = root;
|
||||
if (repository!=null)
|
||||
repository.wrap(root);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public static GHContent[] wrap(GHContent[] contents, GHRepository repository) {
|
||||
for (GHContent unwrappedContent : contents) {
|
||||
unwrappedContent.wrap(repository);
|
||||
}
|
||||
return contents;
|
||||
}
|
||||
}
|
||||
74
src/main/java/org/kohsuke/github/GHContentSearchBuilder.java
Normal file
74
src/main/java/org/kohsuke/github/GHContentSearchBuilder.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
/**
|
||||
* Search code for {@link GHContent}.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GitHub#searchContent()
|
||||
*/
|
||||
public class GHContentSearchBuilder extends GHSearchBuilder<GHContent> {
|
||||
/*package*/ GHContentSearchBuilder(GitHub root) {
|
||||
super(root,ContentSearchResult.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search terms.
|
||||
*/
|
||||
public GHContentSearchBuilder q(String term) {
|
||||
super.q(term);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHContentSearchBuilder in(String v) {
|
||||
return q("in:"+v);
|
||||
}
|
||||
|
||||
public GHContentSearchBuilder language(String v) {
|
||||
return q("language:"+v);
|
||||
}
|
||||
|
||||
public GHContentSearchBuilder fork(String v) {
|
||||
return q("fork:"+v);
|
||||
}
|
||||
|
||||
public GHContentSearchBuilder size(String v) {
|
||||
return q("size:"+v);
|
||||
}
|
||||
|
||||
public GHContentSearchBuilder path(String v) {
|
||||
return q("path:"+v);
|
||||
}
|
||||
|
||||
public GHContentSearchBuilder filename(String v) {
|
||||
return q("filename:"+v);
|
||||
}
|
||||
|
||||
public GHContentSearchBuilder extension(String v) {
|
||||
return q("extension:"+v);
|
||||
}
|
||||
|
||||
public GHContentSearchBuilder user(String v) {
|
||||
return q("user:"+v);
|
||||
}
|
||||
|
||||
|
||||
public GHContentSearchBuilder repo(String v) {
|
||||
return q("repo:"+v);
|
||||
}
|
||||
|
||||
private static class ContentSearchResult extends SearchResult<GHContent> {
|
||||
private GHContent[] items;
|
||||
|
||||
@Override
|
||||
/*package*/ GHContent[] getItems(GitHub root) {
|
||||
for (GHContent item : items)
|
||||
item.wrap(root);
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getApiUrl() {
|
||||
return "/search/code";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
/**
|
||||
* The response that is returned when updating
|
||||
* repository content.
|
||||
**/
|
||||
public class GHContentUpdateResponse {
|
||||
private GHContent content;
|
||||
private GHCommit commit;
|
||||
|
||||
public GHContent getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public GHCommit getCommit() {
|
||||
return commit;
|
||||
}
|
||||
}
|
||||
46
src/main/java/org/kohsuke/github/GHDeployKey.java
Normal file
46
src/main/java/org/kohsuke/github/GHDeployKey.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.commons.lang.builder.ToStringBuilder;
|
||||
|
||||
public class GHDeployKey {
|
||||
|
||||
protected String url, key, title;
|
||||
protected boolean verified;
|
||||
protected int id;
|
||||
private GHRepository owner;
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public boolean isVerified() {
|
||||
return verified;
|
||||
}
|
||||
|
||||
public GHDeployKey wrap(GHRepository repo) {
|
||||
this.owner = repo;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this).append("title",title).append("id",id).append("key",key).toString();
|
||||
}
|
||||
|
||||
public void delete() throws IOException {
|
||||
new Requester(owner.root).method("DELETE").to(String.format("/repos/%s/%s/keys/%d", owner.getOwnerName(), owner.getName(), id));
|
||||
}
|
||||
}
|
||||
61
src/main/java/org/kohsuke/github/GHDeployment.java
Normal file
61
src/main/java/org/kohsuke/github/GHDeployment.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
public class GHDeployment extends GHObject {
|
||||
private GHRepository owner;
|
||||
private GitHub root;
|
||||
protected String sha;
|
||||
protected String ref;
|
||||
protected String task;
|
||||
protected Object payload;
|
||||
protected String environment;
|
||||
protected String description;
|
||||
protected String statuses_url;
|
||||
protected String repository_url;
|
||||
protected GHUser creator;
|
||||
|
||||
|
||||
GHDeployment wrap(GHRepository owner) {
|
||||
this.owner = owner;
|
||||
this.root = owner.root;
|
||||
if(creator != null) creator.wrapUp(root);
|
||||
return this;
|
||||
}
|
||||
|
||||
public URL getStatusesUrl() {
|
||||
return GitHub.parseURL(statuses_url);
|
||||
}
|
||||
|
||||
public URL getRepositoryUrl() {
|
||||
return GitHub.parseURL(repository_url);
|
||||
}
|
||||
|
||||
public String getTask() {
|
||||
return task;
|
||||
}
|
||||
public String getPayload() {
|
||||
return (String) payload;
|
||||
}
|
||||
public String getEnvironment() {
|
||||
return environment;
|
||||
}
|
||||
public GHUser getCreator() {
|
||||
return creator;
|
||||
}
|
||||
public String getRef() {
|
||||
return ref;
|
||||
}
|
||||
public String getSha(){
|
||||
return sha;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This object has no HTML URL.
|
||||
*/
|
||||
@Override
|
||||
public URL getHtmlUrl() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
55
src/main/java/org/kohsuke/github/GHDeploymentBuilder.java
Normal file
55
src/main/java/org/kohsuke/github/GHDeploymentBuilder.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
//Based on https://developer.github.com/v3/repos/deployments/#create-a-deployment
|
||||
public class GHDeploymentBuilder {
|
||||
private final GHRepository repo;
|
||||
private final Requester builder;
|
||||
|
||||
public GHDeploymentBuilder(GHRepository repo) {
|
||||
this.repo = repo;
|
||||
this.builder = new Requester(repo.root);
|
||||
}
|
||||
|
||||
public GHDeploymentBuilder(GHRepository repo, String ref) {
|
||||
this(repo);
|
||||
ref(ref);
|
||||
}
|
||||
|
||||
public GHDeploymentBuilder ref(String branch) {
|
||||
builder.with("ref",branch);
|
||||
return this;
|
||||
}
|
||||
public GHDeploymentBuilder task(String task) {
|
||||
builder.with("task",task);
|
||||
return this;
|
||||
}
|
||||
public GHDeploymentBuilder autoMerge(boolean autoMerge) {
|
||||
builder.with("auto_merge",autoMerge);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHDeploymentBuilder requiredContexts(List<String> requiredContexts) {
|
||||
builder.with("required_contexts",requiredContexts);
|
||||
return this;
|
||||
}
|
||||
public GHDeploymentBuilder payload(String payload) {
|
||||
builder.with("payload",payload);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHDeploymentBuilder environment(String environment) {
|
||||
builder.with("environment",environment);
|
||||
return this;
|
||||
}
|
||||
public GHDeploymentBuilder description(String description) {
|
||||
builder.with("description",description);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHDeployment create() throws IOException {
|
||||
return builder.to(repo.getApiTailUrl("deployments"),GHDeployment.class).wrap(repo);
|
||||
}
|
||||
}
|
||||
8
src/main/java/org/kohsuke/github/GHDeploymentState.java
Normal file
8
src/main/java/org/kohsuke/github/GHDeploymentState.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
/**
|
||||
* Represents the state of deployment
|
||||
*/
|
||||
public enum GHDeploymentState {
|
||||
PENDING, SUCCESS, ERROR, FAILURE
|
||||
}
|
||||
42
src/main/java/org/kohsuke/github/GHDeploymentStatus.java
Normal file
42
src/main/java/org/kohsuke/github/GHDeploymentStatus.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
public class GHDeploymentStatus extends GHObject {
|
||||
private GHRepository owner;
|
||||
private GitHub root;
|
||||
protected GHUser creator;
|
||||
protected String state;
|
||||
protected String description;
|
||||
protected String target_url;
|
||||
protected String deployment_url;
|
||||
protected String repository_url;
|
||||
public GHDeploymentStatus wrap(GHRepository owner) {
|
||||
this.owner = owner;
|
||||
this.root = owner.root;
|
||||
if(creator != null) creator.wrapUp(root);
|
||||
return this;
|
||||
}
|
||||
public URL getTargetUrl() {
|
||||
return GitHub.parseURL(target_url);
|
||||
}
|
||||
|
||||
public URL getDeploymentUrl() {
|
||||
return GitHub.parseURL(deployment_url);
|
||||
}
|
||||
|
||||
public URL getRepositoryUrl() {
|
||||
return GitHub.parseURL(repository_url);
|
||||
}
|
||||
public GHDeploymentState getState() {
|
||||
return GHDeploymentState.valueOf(state.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This object has no HTML URL.
|
||||
*/
|
||||
@Override
|
||||
public URL getHtmlUrl() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class GHDeploymentStatusBuilder {
|
||||
private final Requester builder;
|
||||
private GHRepository repo;
|
||||
private int deploymentId;
|
||||
|
||||
public GHDeploymentStatusBuilder(GHRepository repo, int deploymentId, GHDeploymentState state) {
|
||||
this.repo = repo;
|
||||
this.deploymentId = deploymentId;
|
||||
this.builder = new Requester(repo.root);
|
||||
this.builder.with("state",state.toString().toLowerCase());
|
||||
}
|
||||
|
||||
public GHDeploymentStatusBuilder description(String description) {
|
||||
this.builder.with("description",description);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHDeploymentStatusBuilder targetUrl(String targetUrl) {
|
||||
this.builder.with("target_url",targetUrl);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHDeploymentStatus create() throws IOException {
|
||||
return builder.to(repo.getApiTailUrl("deployments")+"/"+deploymentId+"/statuses",GHDeploymentStatus.class).wrap(repo);
|
||||
}
|
||||
}
|
||||
@@ -23,19 +23,47 @@
|
||||
*/
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
* Represents an email of GitHub.
|
||||
*
|
||||
* @author Kelly Campbell
|
||||
*/
|
||||
class JsonPullRequests {
|
||||
public List<GHPullRequest> pulls;
|
||||
public class GHEmail {
|
||||
|
||||
public List<GHPullRequest> wrap(GHRepository owner) {
|
||||
for (GHPullRequest pull : pulls) {
|
||||
pull.owner = owner;
|
||||
pull.root = owner.root;
|
||||
protected String email;
|
||||
protected boolean primary;
|
||||
protected boolean verified;
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public boolean isPrimary() {
|
||||
return primary;
|
||||
}
|
||||
|
||||
public boolean isVerified() {
|
||||
return verified;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Email:"+email;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return email.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof GHEmail) {
|
||||
GHEmail that = (GHEmail) obj;
|
||||
return this.email.equals(that.email);
|
||||
}
|
||||
return pulls;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,14 @@ package org.kohsuke.github;
|
||||
* See http://developer.github.com/v3/events/types/
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GHEventInfo
|
||||
*/
|
||||
public enum GHEvent {
|
||||
COMMIT_COMMENT,
|
||||
CREATE,
|
||||
DELETE,
|
||||
DEPLOYMENT,
|
||||
DEPLOYMENT_STATUS,
|
||||
DOWNLOAD,
|
||||
FOLLOW,
|
||||
FORK,
|
||||
@@ -20,9 +23,15 @@ public enum GHEvent {
|
||||
ISSUE_COMMENT,
|
||||
ISSUES,
|
||||
MEMBER,
|
||||
PAGE_BUILD,
|
||||
PUBLIC,
|
||||
PULL_REQUEST,
|
||||
PULL_REQUEST_REVIEW_COMMENT,
|
||||
PUSH,
|
||||
RELEASE,
|
||||
REPOSITORY, // only valid for org hooks
|
||||
STATUS,
|
||||
TEAM_ADD,
|
||||
WATCH
|
||||
WATCH,
|
||||
PING
|
||||
}
|
||||
|
||||
89
src/main/java/org/kohsuke/github/GHEventInfo.java
Normal file
89
src/main/java/org/kohsuke/github/GHEventInfo.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
/**
|
||||
* Represents an event.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class GHEventInfo {
|
||||
private GitHub root;
|
||||
|
||||
// we don't want to expose Jackson dependency to the user. This needs databinding
|
||||
private ObjectNode payload;
|
||||
|
||||
private String created_at;
|
||||
private String type;
|
||||
|
||||
// these are all shallow objects
|
||||
private GHEventRepository repo;
|
||||
private GHUser actor;
|
||||
private GHOrganization org;
|
||||
|
||||
/**
|
||||
* Inside the event JSON model, GitHub uses a slightly different format.
|
||||
*/
|
||||
public static class GHEventRepository {
|
||||
private int id;
|
||||
private String url; // repository API URL
|
||||
private String name; // owner/repo
|
||||
}
|
||||
|
||||
public GHEvent getType() {
|
||||
String t = type;
|
||||
if (t.endsWith("Event")) t=t.substring(0,t.length()-5);
|
||||
for (GHEvent e : GHEvent.values()) {
|
||||
if (e.name().replace("_","").equalsIgnoreCase(t))
|
||||
return e;
|
||||
}
|
||||
return null; // unknown event type
|
||||
}
|
||||
|
||||
/*package*/ GHEventInfo wrapUp(GitHub root) {
|
||||
this.root = root;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getCreatedAt() {
|
||||
return GitHub.parseDate(created_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* Repository where the change was made.
|
||||
*/
|
||||
public GHRepository getRepository() throws IOException {
|
||||
return root.getRepository(repo.name);
|
||||
}
|
||||
|
||||
public GHUser getActor() throws IOException {
|
||||
return root.getUser(actor.getLogin());
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick way to just get the actor of the login.
|
||||
*/
|
||||
public String getActorLogin() throws IOException {
|
||||
return actor.getLogin();
|
||||
}
|
||||
|
||||
public GHOrganization getOrganization() throws IOException {
|
||||
return (org==null || org.getLogin()==null) ? null : root.getOrganization(org.getLogin());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the payload.
|
||||
*
|
||||
* @param type
|
||||
* Specify one of the {@link GHEventPayload} subtype that defines a type-safe access to the payload.
|
||||
* This must match the {@linkplain #getType() event type}.
|
||||
*/
|
||||
public <T extends GHEventPayload> T getPayload(Class<T> type) throws IOException {
|
||||
T v = GitHub.MAPPER.readValue(payload.traverse(), type);
|
||||
v.wrapUp(root);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
212
src/main/java/org/kohsuke/github/GHEventPayload.java
Normal file
212
src/main/java/org/kohsuke/github/GHEventPayload.java
Normal file
@@ -0,0 +1,212 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Base type for types used in databinding of the event payload.
|
||||
*
|
||||
* @see GitHub#parseEventPayload(Reader, Class)
|
||||
* @see GHEventInfo#getPayload(Class)
|
||||
*/
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public abstract class GHEventPayload {
|
||||
protected GitHub root;
|
||||
|
||||
/*package*/ GHEventPayload() {
|
||||
}
|
||||
|
||||
/*package*/ void wrapUp(GitHub root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
/**
|
||||
* A pull request status has changed.
|
||||
*
|
||||
* @see <a href="http://developer.github.com/v3/activity/events/types/#pullrequestevent">authoritative source</a>
|
||||
*/
|
||||
public static class PullRequest extends GHEventPayload {
|
||||
private String action;
|
||||
private int number;
|
||||
private GHPullRequest pull_request;
|
||||
private GHRepository repository;
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public GHPullRequest getPullRequest() {
|
||||
pull_request.root = root;
|
||||
return pull_request;
|
||||
}
|
||||
|
||||
public GHRepository getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
void wrapUp(GitHub root) {
|
||||
super.wrapUp(root);
|
||||
if (pull_request==null)
|
||||
throw new IllegalStateException("Expected pull_request payload, but got something else. Maybe we've got another type of event?");
|
||||
if (repository!=null) {
|
||||
repository.wrap(root);
|
||||
pull_request.wrap(repository);
|
||||
} else {
|
||||
pull_request.wrapUp(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A comment was added to an issue
|
||||
*
|
||||
* @see <a href="http://developer.github.com/v3/activity/events/types/#issuecommentevent">authoritative source</a>
|
||||
*/
|
||||
public static class IssueComment extends GHEventPayload {
|
||||
private String action;
|
||||
private GHIssueComment comment;
|
||||
private GHIssue issue;
|
||||
private GHRepository repository;
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public GHIssueComment getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(GHIssueComment comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
public GHIssue getIssue() {
|
||||
return issue;
|
||||
}
|
||||
|
||||
public void setIssue(GHIssue issue) {
|
||||
this.issue = issue;
|
||||
}
|
||||
|
||||
public GHRepository getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
public void setRepository(GHRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
void wrapUp(GitHub root) {
|
||||
super.wrapUp(root);
|
||||
if (repository != null) {
|
||||
repository.wrap(root);
|
||||
issue.wrap(repository);
|
||||
} else {
|
||||
issue.wrap(root);
|
||||
}
|
||||
comment.wrapUp(issue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A commit was pushed.
|
||||
*
|
||||
* @see <a href="http://developer.github.com/v3/activity/events/types/#pushevent">authoritative source</a>
|
||||
*/
|
||||
public static class Push extends GHEventPayload {
|
||||
private String head, before;
|
||||
private String ref;
|
||||
private int size;
|
||||
private List<PushCommit> commits;
|
||||
private GHRepository repository;
|
||||
|
||||
/**
|
||||
* The SHA of the HEAD commit on the repository
|
||||
*/
|
||||
public String getHead() {
|
||||
return head;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is undocumented, but it looks like this captures the commit that the ref was pointing to
|
||||
* before the push.
|
||||
*/
|
||||
public String getBefore() {
|
||||
return before;
|
||||
}
|
||||
|
||||
/**
|
||||
* The full Git ref that was pushed. Example: “refs/heads/master”
|
||||
*/
|
||||
public String getRef() {
|
||||
return ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of commits in the push.
|
||||
* Is this always the same as {@code getCommits().size()}?
|
||||
*/
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of pushed commits.
|
||||
*/
|
||||
public List<PushCommit> getCommits() {
|
||||
return commits;
|
||||
}
|
||||
|
||||
public GHRepository getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
void wrapUp(GitHub root) {
|
||||
super.wrapUp(root);
|
||||
if (repository!=null)
|
||||
repository.wrap(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit in a push
|
||||
*/
|
||||
public static class PushCommit {
|
||||
private GitUser author;
|
||||
private String url, sha, message;
|
||||
private boolean distinct;
|
||||
|
||||
public GitUser getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
/**
|
||||
* Points to the commit API resource.
|
||||
*/
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getSha() {
|
||||
return sha;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this commit is distinct from any that have been pushed before.
|
||||
*/
|
||||
public boolean isDistinct() {
|
||||
return distinct;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
179
src/main/java/org/kohsuke/github/GHGist.java
Normal file
179
src/main/java/org/kohsuke/github/GHGist.java
Normal file
@@ -0,0 +1,179 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Gist
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GHUser#listGists()
|
||||
* @see GitHub#getGist(String)
|
||||
* @see GitHub#createGist()
|
||||
* @see <a href="https://developer.github.com/v3/gists/">documentation</a>
|
||||
*/
|
||||
public class GHGist extends GHObject {
|
||||
/*package almost final*/ GHUser owner;
|
||||
/*package almost final*/ GitHub root;
|
||||
|
||||
private String forks_url, commits_url, id, git_pull_url, git_push_url, html_url;
|
||||
|
||||
@JsonProperty("public")
|
||||
private boolean _public;
|
||||
|
||||
private String description;
|
||||
|
||||
private int comments;
|
||||
|
||||
private String comments_url;
|
||||
|
||||
private Map<String,GHGistFile> files = new HashMap<String, GHGistFile>();
|
||||
|
||||
/**
|
||||
* User that owns this Gist.
|
||||
*/
|
||||
public GHUser getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public String getForksUrl() {
|
||||
return forks_url;
|
||||
}
|
||||
|
||||
public String getCommitsUrl() {
|
||||
return commits_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL like https://gist.github.com/gists/12345.git
|
||||
*/
|
||||
public String getGitPullUrl() {
|
||||
return git_pull_url;
|
||||
}
|
||||
|
||||
public String getGitPushUrl() {
|
||||
return git_push_url;
|
||||
}
|
||||
|
||||
public URL getHtmlUrl() {
|
||||
return GitHub.parseURL(html_url);
|
||||
}
|
||||
|
||||
public boolean isPublic() {
|
||||
return _public;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public int getCommentCount() {
|
||||
return comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* API URL of listing comments.
|
||||
*/
|
||||
public String getCommentsUrl() {
|
||||
return comments_url;
|
||||
}
|
||||
|
||||
public GHGistFile getFile(String name) {
|
||||
return files.get(name);
|
||||
}
|
||||
|
||||
public Map<String,GHGistFile> getFiles() {
|
||||
return Collections.unmodifiableMap(files);
|
||||
}
|
||||
|
||||
/*package*/ GHGist wrapUp(GHUser owner) {
|
||||
this.owner = owner;
|
||||
this.root = owner.root;
|
||||
wrapUp();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when caller obtains {@link GHGist} without knowing its owner.
|
||||
* A partially constructed owner object is interned.
|
||||
*/
|
||||
/*package*/ GHGist wrapUp(GitHub root) throws IOException {
|
||||
this.owner = root.getUser(owner);
|
||||
this.root = root;
|
||||
wrapUp();
|
||||
return this;
|
||||
}
|
||||
|
||||
private void wrapUp() {
|
||||
for (Entry<String, GHGistFile> e : files.entrySet()) {
|
||||
e.getValue().fileName = e.getKey();
|
||||
}
|
||||
}
|
||||
String getApiTailUrl(String tail) {
|
||||
return "/gists/" + id + '/' + tail;
|
||||
}
|
||||
|
||||
public void star() throws IOException {
|
||||
new Requester(root).method("PUT").to(getApiTailUrl("star"));
|
||||
}
|
||||
|
||||
public void unstar() throws IOException {
|
||||
new Requester(root).method("DELETE").to(getApiTailUrl("star"));
|
||||
}
|
||||
|
||||
public boolean isStarred() throws IOException {
|
||||
return root.retrieve().asHttpStatusCode(getApiTailUrl("star"))/100==2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forks this gist into your own.
|
||||
*/
|
||||
public GHGist fork() throws IOException {
|
||||
return new Requester(root).to(getApiTailUrl("forks"),GHGist.class).wrapUp(root);
|
||||
}
|
||||
|
||||
public PagedIterable<GHGist> listForks() {
|
||||
return new PagedIterable<GHGist>() {
|
||||
public PagedIterator<GHGist> iterator() {
|
||||
return new PagedIterator<GHGist>(root.retrieve().asIterator(getApiTailUrl("forks"), GHGist[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHGist[] page) {
|
||||
try {
|
||||
for (GHGist c : page)
|
||||
c.wrapUp(root);
|
||||
} catch (IOException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this gist.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
new Requester(root).method("DELETE").to("/gists/" + id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
GHGist ghGist = (GHGist) o;
|
||||
return id.equals(ghGist.id);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
}
|
||||
48
src/main/java/org/kohsuke/github/GHGistBuilder.java
Normal file
48
src/main/java/org/kohsuke/github/GHGistBuilder.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* Builder pattern for creating a new Gist.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GitHub#createGist()
|
||||
*/
|
||||
public class GHGistBuilder {
|
||||
private final GitHub root;
|
||||
private final Requester req;
|
||||
private final LinkedHashMap<String,Object> files = new LinkedHashMap<String, Object>();
|
||||
|
||||
public GHGistBuilder(GitHub root) {
|
||||
this.root = root;
|
||||
req = new Requester(root);
|
||||
}
|
||||
|
||||
public GHGistBuilder description(String desc) {
|
||||
req.with("description",desc);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHGistBuilder public_(boolean v) {
|
||||
req.with("public",v);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new file.
|
||||
*/
|
||||
public GHGistBuilder file(String fileName, String content) {
|
||||
files.put(fileName, Collections.singletonMap("content", content));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Gist based on the parameters specified thus far.
|
||||
*/
|
||||
public GHGist create() throws IOException {
|
||||
req._with("files",files);
|
||||
return req.to("/gists",GHGist.class).wrapUp(root);
|
||||
}
|
||||
}
|
||||
60
src/main/java/org/kohsuke/github/GHGistFile.java
Normal file
60
src/main/java/org/kohsuke/github/GHGistFile.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
/**
|
||||
* A file inside {@link GHGist}
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GHGist#getFile(String)
|
||||
* @see GHGist#getFiles()
|
||||
*/
|
||||
public class GHGistFile {
|
||||
/*package almost final*/ String fileName;
|
||||
|
||||
private int size;
|
||||
private String raw_url, type, language, content;
|
||||
private boolean truncated;
|
||||
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* File size in bytes.
|
||||
*/
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL that serves this file as-is.
|
||||
*/
|
||||
public String getRawUrl() {
|
||||
return raw_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Content type of this Gist, such as "text/plain"
|
||||
*/
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Content of this file.
|
||||
*/
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* (?) indicates if {@link #getContent()} contains a truncated content.
|
||||
*/
|
||||
public boolean isTruncated() {
|
||||
return truncated;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
@@ -10,22 +11,11 @@ import java.util.Map;
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public final class GHHook {
|
||||
/**
|
||||
* Repository that the hook belongs to.
|
||||
*/
|
||||
/*package*/ transient GHRepository repository;
|
||||
|
||||
String created_at, updated_at, name;
|
||||
public abstract class GHHook extends GHObject {
|
||||
String name;
|
||||
List<String> events;
|
||||
boolean active;
|
||||
Map<String,String> config;
|
||||
int id;
|
||||
|
||||
/*package*/ GHHook wrap(GHRepository owner) {
|
||||
this.repository = owner;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
@@ -34,7 +24,7 @@ public final class GHHook {
|
||||
public EnumSet<GHEvent> getEvents() {
|
||||
EnumSet<GHEvent> s = EnumSet.noneOf(GHEvent.class);
|
||||
for (String e : events)
|
||||
Enum.valueOf(GHEvent.class,e.toUpperCase(Locale.ENGLISH));
|
||||
s.add(Enum.valueOf(GHEvent.class,e.toUpperCase(Locale.ENGLISH)));
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -46,15 +36,22 @@ public final class GHHook {
|
||||
return Collections.unmodifiableMap(config);
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this hook.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
new Poster(repository.root,ApiVersion.V3).withCredential()
|
||||
.to(String.format("/repos/%s/%s/hooks/%d",repository.getOwnerName(),repository.getName(),id),null,"DELETE");
|
||||
new Requester(getRoot()).method("DELETE").to(getApiRoute());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This object has no HTML URL.
|
||||
*/
|
||||
@Override
|
||||
public URL getHtmlUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
abstract GitHub getRoot();
|
||||
|
||||
abstract String getApiRoute();
|
||||
}
|
||||
|
||||
130
src/main/java/org/kohsuke/github/GHHooks.java
Normal file
130
src/main/java/org/kohsuke/github/GHHooks.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Utility class for creating and retrieving webhooks; removes duplication between GHOrganization and GHRepository
|
||||
* functionality
|
||||
*/
|
||||
class GHHooks {
|
||||
static abstract class Context {
|
||||
private final GitHub root;
|
||||
|
||||
private Context(GitHub root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
public List<GHHook> getHooks() throws IOException {
|
||||
List<GHHook> list = new ArrayList<GHHook>(Arrays.asList(
|
||||
root.retrieve().to(collection(), collectionClass())));
|
||||
for (GHHook h : list)
|
||||
wrap(h);
|
||||
return list;
|
||||
}
|
||||
|
||||
public GHHook getHook(int id) throws IOException {
|
||||
GHHook hook = root.retrieve().to(collection() + "/" + id, clazz());
|
||||
return wrap(hook);
|
||||
}
|
||||
|
||||
public GHHook createHook(String name, Map<String, String> config, Collection<GHEvent> events, boolean active) throws IOException {
|
||||
List<String> ea = null;
|
||||
if (events!=null) {
|
||||
ea = new ArrayList<String>();
|
||||
for (GHEvent e : events)
|
||||
ea.add(e.name().toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
GHHook hook = new Requester(root)
|
||||
.with("name", name)
|
||||
.with("active", active)
|
||||
._with("config", config)
|
||||
._with("events", ea)
|
||||
.to(collection(), clazz());
|
||||
|
||||
return wrap(hook);
|
||||
}
|
||||
|
||||
abstract String collection();
|
||||
|
||||
abstract Class<? extends GHHook[]> collectionClass();
|
||||
|
||||
abstract Class<? extends GHHook> clazz();
|
||||
|
||||
abstract GHHook wrap(GHHook hook);
|
||||
}
|
||||
|
||||
private static class RepoContext extends Context {
|
||||
private final GHRepository repository;
|
||||
private final GHUser owner;
|
||||
|
||||
private RepoContext(GHRepository repository, GHUser owner) {
|
||||
super(repository.root);
|
||||
this.repository = repository;
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
String collection() {
|
||||
return String.format("/repos/%s/%s/hooks", owner.getLogin(), repository.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
Class<? extends GHHook[]> collectionClass() {
|
||||
return GHRepoHook[].class;
|
||||
}
|
||||
|
||||
@Override
|
||||
Class<? extends GHHook> clazz() {
|
||||
return GHRepoHook.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
GHHook wrap(GHHook hook) {
|
||||
return ((GHRepoHook)hook).wrap(repository);
|
||||
}
|
||||
}
|
||||
|
||||
private static class OrgContext extends Context {
|
||||
private final GHOrganization organization;
|
||||
|
||||
private OrgContext(GHOrganization organization) {
|
||||
super(organization.root);
|
||||
this.organization = organization;
|
||||
}
|
||||
|
||||
@Override
|
||||
String collection() {
|
||||
return String.format("/orgs/%s/hooks", organization.getLogin());
|
||||
}
|
||||
|
||||
@Override
|
||||
Class<? extends GHHook[]> collectionClass() {
|
||||
return GHOrgHook[].class;
|
||||
}
|
||||
|
||||
@Override
|
||||
Class<? extends GHHook> clazz() {
|
||||
return GHOrgHook.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
GHHook wrap(GHHook hook) {
|
||||
return ((GHOrgHook)hook).wrap(organization);
|
||||
}
|
||||
}
|
||||
|
||||
static Context repoContext(GHRepository repository, GHUser owner) {
|
||||
return new RepoContext(repository, owner);
|
||||
}
|
||||
|
||||
static Context orgContext(GHOrganization organization) {
|
||||
return new OrgContext(organization);
|
||||
}
|
||||
}
|
||||
@@ -24,27 +24,69 @@
|
||||
|
||||
package org.kohsuke.github;
|
||||
|
||||
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Represents an issue on GitHub.
|
||||
*
|
||||
* @author Eric Maupin
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GHRepository#getIssue(int)
|
||||
* @see GitHub#searchIssues()
|
||||
* @see GHIssueSearchBuilder
|
||||
*/
|
||||
public class GHIssue {
|
||||
public class GHIssue extends GHObject {
|
||||
GitHub root;
|
||||
GHRepository owner;
|
||||
|
||||
// API v3
|
||||
protected GHUser assignee;
|
||||
protected String state;
|
||||
protected int number;
|
||||
protected String closed_at;
|
||||
protected int comments;
|
||||
protected String body;
|
||||
// for backward compatibility with < 1.63, this collection needs to hold instances of Label, not GHLabel
|
||||
protected List<Label> labels;
|
||||
protected GHUser user;
|
||||
protected String title, html_url;
|
||||
protected GHIssue.PullRequest pull_request;
|
||||
protected GHMilestone milestone;
|
||||
protected GHUser closed_by;
|
||||
|
||||
private String gravatar_id,body,title,state,created_at,updated_at,html_url;
|
||||
private List<String> labels;
|
||||
private int number,votes,comments;
|
||||
private int position;
|
||||
/**
|
||||
* @deprecated use {@link GHLabel}
|
||||
*/
|
||||
public static class Label extends GHLabel {
|
||||
}
|
||||
|
||||
/*package*/ GHIssue wrap(GHRepository owner) {
|
||||
this.owner = owner;
|
||||
if(milestone != null) milestone.wrap(owner);
|
||||
return wrap(owner.root);
|
||||
}
|
||||
|
||||
/*package*/ GHIssue wrap(GitHub root) {
|
||||
this.root = root;
|
||||
if(assignee != null) assignee.wrapUp(root);
|
||||
if(user != null) user.wrapUp(root);
|
||||
if(closed_by != null) closed_by.wrapUp(root);
|
||||
return this;
|
||||
}
|
||||
|
||||
/*package*/ static GHIssue[] wrap(GHIssue[] issues, GHRepository owner) {
|
||||
for (GHIssue i : issues)
|
||||
i.wrap(owner);
|
||||
return issues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repository to which the issue belongs.
|
||||
@@ -71,7 +113,7 @@ public class GHIssue {
|
||||
* The HTML page of this issue,
|
||||
* like https://github.com/jenkinsci/jenkins/issues/100
|
||||
*/
|
||||
public URL getUrl() {
|
||||
public URL getHtmlUrl() {
|
||||
return GitHub.parseURL(html_url);
|
||||
}
|
||||
|
||||
@@ -80,50 +122,166 @@ public class GHIssue {
|
||||
}
|
||||
|
||||
public GHIssueState getState() {
|
||||
return Enum.valueOf(GHIssueState.class, state);
|
||||
return Enum.valueOf(GHIssueState.class, state.toUpperCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
public Collection<String> getLabels() {
|
||||
return Collections.unmodifiableList(labels);
|
||||
public Collection<GHLabel> getLabels() throws IOException {
|
||||
if(labels == null){
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.<GHLabel>unmodifiableList(labels);
|
||||
}
|
||||
|
||||
public Date getCreatedAt() {
|
||||
return GitHub.parseDate(created_at);
|
||||
public Date getClosedAt() {
|
||||
return GitHub.parseDate(closed_at);
|
||||
}
|
||||
|
||||
public Date getUpdatedAt() {
|
||||
return GitHub.parseDate(updated_at);
|
||||
public URL getApiURL(){
|
||||
return GitHub.parseURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the issue by adding a comment.
|
||||
*
|
||||
* @return
|
||||
* Newly posted comment.
|
||||
*/
|
||||
public void comment(String message) throws IOException {
|
||||
new Poster(root).withCredential().with("comment",message).to(getApiRoute("comment"));
|
||||
@WithBridgeMethods(void.class)
|
||||
public GHIssueComment comment(String message) throws IOException {
|
||||
GHIssueComment r = new Requester(root).with("body",message).to(getIssuesApiRoute() + "/comments", GHIssueComment.class);
|
||||
return r.wrapUp(this);
|
||||
}
|
||||
|
||||
private void edit(String key, Object value) throws IOException {
|
||||
new Requester(root)._with(key, value).method("PATCH").to(getApiRoute());
|
||||
}
|
||||
|
||||
private void editIssue(String key, Object value) throws IOException {
|
||||
new Requester(root)._with(key, value).method("PATCH").to(getIssuesApiRoute());
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this issue.
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
new Poster(root).withCredential().to(getApiRoute("close"));
|
||||
edit("state", "closed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reopens this issue.
|
||||
*/
|
||||
public void reopen() throws IOException {
|
||||
new Poster(root).withCredential().to(getApiRoute("reopen"));
|
||||
edit("state", "open");
|
||||
}
|
||||
|
||||
public void setTitle(String title) throws IOException {
|
||||
edit("title",title);
|
||||
}
|
||||
|
||||
public void setBody(String body) throws IOException {
|
||||
edit("body",body);
|
||||
}
|
||||
|
||||
public void assignTo(GHUser user) throws IOException {
|
||||
editIssue("assignee", user.getLogin());
|
||||
}
|
||||
|
||||
public void setLabels(String... labels) throws IOException {
|
||||
editIssue("labels",labels);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains all the comments associated with this issue.
|
||||
*
|
||||
* @see #listComments()
|
||||
*/
|
||||
public List<GHIssueComment> getComments() throws IOException {
|
||||
return root.retrieve(getApiRoute("comments"), JsonIssueComments.class).wrap(this);
|
||||
return listComments().asList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains all the comments associated with this issue.
|
||||
*/
|
||||
public PagedIterable<GHIssueComment> listComments() throws IOException {
|
||||
return new PagedIterable<GHIssueComment>() {
|
||||
public PagedIterator<GHIssueComment> iterator() {
|
||||
return new PagedIterator<GHIssueComment>(root.retrieve().asIterator(getIssuesApiRoute() + "/comments", GHIssueComment[].class)) {
|
||||
protected void wrapUp(GHIssueComment[] page) {
|
||||
for (GHIssueComment c : page)
|
||||
c.wrapUp(GHIssue.this);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private String getApiRoute(String verb) {
|
||||
return "/issues/"+verb+"/"+owner.getOwnerName()+"/"+owner.getName()+"/"+number;
|
||||
protected String getApiRoute() {
|
||||
return getIssuesApiRoute();
|
||||
}
|
||||
}
|
||||
|
||||
protected String getIssuesApiRoute() {
|
||||
return "/repos/"+owner.getOwnerName()+"/"+owner.getName()+"/issues/"+number;
|
||||
}
|
||||
|
||||
public GHUser getAssignee() {
|
||||
return assignee;
|
||||
}
|
||||
|
||||
/**
|
||||
* User who submitted the issue.
|
||||
*/
|
||||
public GHUser getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports who has closed the issue.
|
||||
*
|
||||
* <p>
|
||||
* Note that GitHub doesn't always seem to report this information
|
||||
* even for an issue that's already closed. See
|
||||
* https://github.com/kohsuke/github-api/issues/60.
|
||||
*/
|
||||
public GHUser getClosedBy() {
|
||||
if(!"closed".equals(state)) return null;
|
||||
if(closed_by != null) return closed_by;
|
||||
|
||||
//TODO closed_by = owner.getIssue(number).getClosed_by();
|
||||
return closed_by;
|
||||
}
|
||||
|
||||
public int getCommentsCount(){
|
||||
return comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns non-null if this issue is a shadow of a pull request.
|
||||
*/
|
||||
public PullRequest getPullRequest() {
|
||||
return pull_request;
|
||||
}
|
||||
|
||||
public boolean isPullRequest() {
|
||||
return pull_request!=null;
|
||||
}
|
||||
|
||||
public GHMilestone getMilestone() {
|
||||
return milestone;
|
||||
}
|
||||
|
||||
public static class PullRequest{
|
||||
private String diff_url, patch_url, html_url;
|
||||
|
||||
public URL getDiffUrl() {
|
||||
return GitHub.parseURL(diff_url);
|
||||
}
|
||||
|
||||
public URL getPatchUrl() {
|
||||
return GitHub.parseURL(patch_url);
|
||||
}
|
||||
|
||||
public URL getUrl() {
|
||||
return GitHub.parseURL(html_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
src/main/java/org/kohsuke/github/GHIssueBuilder.java
Normal file
59
src/main/java/org/kohsuke/github/GHIssueBuilder.java
Normal file
@@ -0,0 +1,59 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class GHIssueBuilder {
|
||||
private final GHRepository repo;
|
||||
private final Requester builder;
|
||||
private List<String> labels = new ArrayList<String>();
|
||||
|
||||
GHIssueBuilder(GHRepository repo, String title) {
|
||||
this.repo = repo;
|
||||
this.builder = new Requester(repo.root);
|
||||
builder.with("title",title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the main text of an issue, which is arbitrary multi-line text.
|
||||
*/
|
||||
public GHIssueBuilder body(String str) {
|
||||
builder.with("body",str);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHIssueBuilder assignee(GHUser user) {
|
||||
if (user!=null)
|
||||
builder.with("assignee",user.getLogin());
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHIssueBuilder assignee(String user) {
|
||||
if (user!=null)
|
||||
builder.with("assignee",user);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHIssueBuilder milestone(GHMilestone milestone) {
|
||||
if (milestone!=null)
|
||||
builder.with("milestone",milestone.getNumber());
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHIssueBuilder label(String label) {
|
||||
if (label!=null)
|
||||
labels.add(label);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new issue.
|
||||
*/
|
||||
public GHIssue create() throws IOException {
|
||||
return builder.with("labels",labels).to(repo.getApiTailUrl("issues"),GHIssue.class).wrap(repo);
|
||||
}
|
||||
}
|
||||
@@ -24,18 +24,23 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Comment to the issue
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class GHIssueComment {
|
||||
public class GHIssueComment extends GHObject {
|
||||
GHIssue owner;
|
||||
|
||||
private String body, gravatar_id, user, created_at, updated_at;
|
||||
private int id;
|
||||
private String body, gravatar_id;
|
||||
private GHUser user;
|
||||
|
||||
/*package*/ GHIssueComment wrapUp(GHIssue owner) {
|
||||
this.owner = owner;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the issue to which this comment is associated.
|
||||
@@ -51,29 +56,45 @@ public class GHIssueComment {
|
||||
return body;
|
||||
}
|
||||
|
||||
public Date getCreatedAt() {
|
||||
return GitHub.parseDate(created_at);
|
||||
}
|
||||
|
||||
public Date getUpdatedAt() {
|
||||
return GitHub.parseDate(updated_at);
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of the user who posted this comment.
|
||||
*/
|
||||
@Deprecated
|
||||
public String getUserName() {
|
||||
return user;
|
||||
return user.getLogin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user who posted this comment.
|
||||
*/
|
||||
public GHUser getUser() throws IOException {
|
||||
return owner.root.getUser(user);
|
||||
return owner.root.getUser(user.getLogin());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This object has no HTML URL.
|
||||
*/
|
||||
@Override
|
||||
public URL getHtmlUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the body of the issue comment.
|
||||
*/
|
||||
public void update(String body) throws IOException {
|
||||
new Requester(owner.root).with("body", body).method("PATCH").to(getApiRoute(), GHIssueComment.class);
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this issue comment.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
new Requester(owner.root).method("DELETE").to(getApiRoute());
|
||||
}
|
||||
|
||||
private String getApiRoute() {
|
||||
return "/repos/"+owner.getRepository().getOwnerName()+"/"+owner.getRepository().getName()+"/issues/comments/" + id;
|
||||
}
|
||||
}
|
||||
|
||||
66
src/main/java/org/kohsuke/github/GHIssueSearchBuilder.java
Normal file
66
src/main/java/org/kohsuke/github/GHIssueSearchBuilder.java
Normal file
@@ -0,0 +1,66 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Search issues.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GitHub#searchIssues()
|
||||
*/
|
||||
public class GHIssueSearchBuilder extends GHSearchBuilder<GHIssue> {
|
||||
/*package*/ GHIssueSearchBuilder(GitHub root) {
|
||||
super(root,IssueSearchResult.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search terms.
|
||||
*/
|
||||
public GHIssueSearchBuilder q(String term) {
|
||||
super.q(term);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHIssueSearchBuilder mentions(GHUser u) {
|
||||
return mentions(u.getLogin());
|
||||
}
|
||||
|
||||
public GHIssueSearchBuilder mentions(String login) {
|
||||
return q("mentions:"+login);
|
||||
}
|
||||
|
||||
public GHIssueSearchBuilder isOpen() {
|
||||
return q("is:open");
|
||||
}
|
||||
|
||||
public GHIssueSearchBuilder isClosed() {
|
||||
return q("is:closed");
|
||||
}
|
||||
|
||||
public GHIssueSearchBuilder isMerged() {
|
||||
return q("is:merged");
|
||||
}
|
||||
|
||||
public GHIssueSearchBuilder sort(Sort sort) {
|
||||
req.with("sort",sort.toString().toLowerCase(Locale.ENGLISH));
|
||||
return this;
|
||||
}
|
||||
|
||||
public enum Sort { COMMENTS, CREATED, UPDATED }
|
||||
|
||||
private static class IssueSearchResult extends SearchResult<GHIssue> {
|
||||
private GHIssue[] items;
|
||||
|
||||
@Override
|
||||
/*package*/ GHIssue[] getItems(GitHub root) {
|
||||
for (GHIssue i : items)
|
||||
i.wrap(root);
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getApiUrl() {
|
||||
return "/search/issues";
|
||||
}
|
||||
}
|
||||
48
src/main/java/org/kohsuke/github/GHKey.java
Normal file
48
src/main/java/org/kohsuke/github/GHKey.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import org.apache.commons.lang.builder.ToStringBuilder;
|
||||
|
||||
/**
|
||||
* SSH public key.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class GHKey {
|
||||
/*package almost final*/ GitHub root;
|
||||
|
||||
protected String url, key, title;
|
||||
protected boolean verified;
|
||||
protected int id;
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Something like "https://api.github.com/user/keys/73593"
|
||||
*/
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public boolean isVerified() {
|
||||
return verified;
|
||||
}
|
||||
|
||||
/*package*/ GHKey wrap(GitHub root) {
|
||||
this.root = root;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this).append("title",title).append("id",id).append("key",key).toString();
|
||||
}
|
||||
}
|
||||
37
src/main/java/org/kohsuke/github/GHLabel.java
Normal file
37
src/main/java/org/kohsuke/github/GHLabel.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GHIssue#getLabels()
|
||||
* @see GHRepository#listLabels()
|
||||
*/
|
||||
public class GHLabel {
|
||||
private String url, name, color;
|
||||
private GHRepository repo;
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Color code without leading '#', such as 'f29513'
|
||||
*/
|
||||
public String getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/*package*/ GHLabel wrapUp(GHRepository repo) {
|
||||
this.repo = repo;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void delete() throws IOException {
|
||||
repo.root.retrieve().method("DELETE").to(url);
|
||||
}
|
||||
}
|
||||
86
src/main/java/org/kohsuke/github/GHMilestone.java
Normal file
86
src/main/java/org/kohsuke/github/GHMilestone.java
Normal file
@@ -0,0 +1,86 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Yusuke Kokubo
|
||||
*
|
||||
*/
|
||||
public class GHMilestone extends GHObject {
|
||||
GitHub root;
|
||||
GHRepository owner;
|
||||
|
||||
GHUser creator;
|
||||
private String state, due_on, title, description, html_url;
|
||||
private int closed_issues, open_issues, number;
|
||||
|
||||
public GitHub getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
public GHRepository getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public GHUser getCreator() {
|
||||
return creator;
|
||||
}
|
||||
|
||||
public Date getDueOn() {
|
||||
if (due_on == null) return null;
|
||||
return GitHub.parseDate(due_on);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public int getClosedIssues() {
|
||||
return closed_issues;
|
||||
}
|
||||
|
||||
public int getOpenIssues() {
|
||||
return open_issues;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public URL getHtmlUrl() {
|
||||
return GitHub.parseURL(html_url);
|
||||
}
|
||||
|
||||
public GHMilestoneState getState() {
|
||||
return Enum.valueOf(GHMilestoneState.class, state.toUpperCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this issue.
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
edit("state", "closed");
|
||||
}
|
||||
|
||||
private void edit(String key, Object value) throws IOException {
|
||||
new Requester(root)._with(key, value).method("PATCH").to(getApiRoute());
|
||||
}
|
||||
|
||||
protected String getApiRoute() {
|
||||
return "/repos/"+owner.getOwnerName()+"/"+owner.getName()+"/milestones/"+number;
|
||||
}
|
||||
|
||||
public GHMilestone wrap(GHRepository repo) {
|
||||
this.owner = repo;
|
||||
this.root = repo.root;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
11
src/main/java/org/kohsuke/github/GHMilestoneState.java
Normal file
11
src/main/java/org/kohsuke/github/GHMilestoneState.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Yusuke Kokubo
|
||||
*
|
||||
*/
|
||||
public enum GHMilestoneState {
|
||||
OPEN,
|
||||
CLOSED
|
||||
}
|
||||
185
src/main/java/org/kohsuke/github/GHMyself.java
Normal file
185
src/main/java/org/kohsuke/github/GHMyself.java
Normal file
@@ -0,0 +1,185 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Represents the account that's logging into GitHub.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class GHMyself extends GHUser {
|
||||
|
||||
/**
|
||||
* Type of repositories returned during listing.
|
||||
*/
|
||||
public enum RepositoryListFilter {
|
||||
/**
|
||||
* All public and private repositories that current user has access or collaborates to
|
||||
*/
|
||||
ALL,
|
||||
/**
|
||||
* Public and private repositories owned by current user
|
||||
*/
|
||||
OWNER,
|
||||
/**
|
||||
* Public repositories that current user has access or collaborates to
|
||||
*/
|
||||
PUBLIC,
|
||||
/**
|
||||
* Private repositories that current user has access or collaborates to
|
||||
*/
|
||||
PRIVATE,
|
||||
/**
|
||||
* Public and private repositories that current user is a member
|
||||
*/
|
||||
MEMBER;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Use {@link #getEmails2()}
|
||||
*/
|
||||
public List<String> getEmails() throws IOException {
|
||||
List<GHEmail> src = getEmails2();
|
||||
List<String> r = new ArrayList<String>(src.size());
|
||||
for (GHEmail e : src) {
|
||||
r.add(e.getEmail());
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the read-only list of e-mail addresses configured for you.
|
||||
*
|
||||
* This corresponds to the stuff you configure in https://github.com/settings/emails,
|
||||
* and not to be confused with {@link #getEmail()} that shows your public e-mail address
|
||||
* set in https://github.com/settings/profile
|
||||
*
|
||||
* @return
|
||||
* Always non-null.
|
||||
*/
|
||||
public List<GHEmail> getEmails2() throws IOException {
|
||||
GHEmail[] addresses = root.retrieve().to("/user/emails", GHEmail[].class);
|
||||
return Collections.unmodifiableList(Arrays.asList(addresses));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the read-only list of all the pulic keys of the current user.
|
||||
*
|
||||
* NOTE: When using OAuth authenticaiton, the READ/WRITE User scope is
|
||||
* required by the GitHub APIs, otherwise you will get a 404 NOT FOUND.
|
||||
*
|
||||
* @return
|
||||
* Always non-null.
|
||||
*/
|
||||
public List<GHKey> getPublicKeys() throws IOException {
|
||||
return Collections.unmodifiableList(Arrays.asList(root.retrieve().to("/user/keys", GHKey[].class)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the read-only list of all the public verified keys of the current user.
|
||||
*
|
||||
* Differently from the getPublicKeys() method, the retrieval of the user's
|
||||
* verified public keys does not require any READ/WRITE OAuth Scope to the
|
||||
* user's profile.
|
||||
*
|
||||
* @return
|
||||
* Always non-null.
|
||||
*/
|
||||
public List<GHVerifiedKey> getPublicVerifiedKeys() throws IOException {
|
||||
return Collections.unmodifiableList(Arrays.asList(root.retrieve().to(
|
||||
"/users/" + getLogin() + "/keys", GHVerifiedKey[].class)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the organization that this user belongs to.
|
||||
*/
|
||||
public GHPersonSet<GHOrganization> getAllOrganizations() throws IOException {
|
||||
GHPersonSet<GHOrganization> orgs = new GHPersonSet<GHOrganization>();
|
||||
Set<String> names = new HashSet<String>();
|
||||
for (GHOrganization o : root.retrieve().to("/user/orgs", GHOrganization[].class)) {
|
||||
if (names.add(o.getLogin())) // in case of rumoured duplicates in the data
|
||||
orgs.add(root.getOrganization(o.getLogin()));
|
||||
}
|
||||
return orgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the all repositories this user owns (public and private).
|
||||
*/
|
||||
public synchronized Map<String,GHRepository> getAllRepositories() throws IOException {
|
||||
Map<String,GHRepository> repositories = new TreeMap<String, GHRepository>();
|
||||
for (GHRepository r : listAllRepositories()) {
|
||||
repositories.put(r.getName(),r);
|
||||
}
|
||||
return Collections.unmodifiableMap(repositories);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists up all repositories this user owns (public and private).
|
||||
*
|
||||
* Unlike {@link #getAllRepositories()}, this does not wait until all the repositories are returned.
|
||||
* Repositories are returned by GitHub API with a 30 items per page.
|
||||
*/
|
||||
@Override
|
||||
public PagedIterable<GHRepository> listRepositories() {
|
||||
return listRepositories(30);
|
||||
}
|
||||
|
||||
/**
|
||||
* List repositories that are accessible to the authenticated user (public and private) using the specified page size.
|
||||
*
|
||||
* This includes repositories owned by the authenticated user, repositories that belong to other users
|
||||
* where the authenticated user is a collaborator, and other organizations' repositories that the authenticated
|
||||
* user has access to through an organization membership.
|
||||
*
|
||||
* @param pageSize size for each page of items returned by GitHub. Maximum page size is 100.
|
||||
*
|
||||
* Unlike {@link #getRepositories()}, this does not wait until all the repositories are returned.
|
||||
*/
|
||||
public PagedIterable<GHRepository> listRepositories(final int pageSize) {
|
||||
return listRepositories(pageSize, RepositoryListFilter.ALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* List repositories of a certain type that are accessible by current authenticated user using the specified page size.
|
||||
*
|
||||
* @param pageSize size for each page of items returned by GitHub. Maximum page size is 100.
|
||||
* @param repoType type of repository returned in the listing
|
||||
*/
|
||||
public PagedIterable<GHRepository> listRepositories(final int pageSize, final RepositoryListFilter repoType) {
|
||||
return new PagedIterable<GHRepository>() {
|
||||
public PagedIterator<GHRepository> iterator() {
|
||||
return new PagedIterator<GHRepository>(root.retrieve().asIterator("/user/repos?per_page=" + pageSize +
|
||||
"&type=" + repoType.name().toLowerCase(), GHRepository[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHRepository[] page) {
|
||||
for (GHRepository c : page)
|
||||
c.wrap(root);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Use {@link #listRepositories()}
|
||||
*/
|
||||
public PagedIterable<GHRepository> listAllRepositories() {
|
||||
return listRepositories();
|
||||
}
|
||||
|
||||
// public void addEmails(Collection<String> emails) throws IOException {
|
||||
//// new Requester(root,ApiVersion.V3).withCredential().to("/user/emails");
|
||||
// root.retrieveWithAuth3()
|
||||
// }
|
||||
}
|
||||
207
src/main/java/org/kohsuke/github/GHNotificationStream.java
Normal file
207
src/main/java/org/kohsuke/github/GHNotificationStream.java
Normal file
@@ -0,0 +1,207 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* Listens to GitHub notification stream.
|
||||
*
|
||||
* <p>
|
||||
* This class supports two modes of retrieving notifications that can
|
||||
* be controlled via {@link #nonBlocking(boolean)}.
|
||||
*
|
||||
* <p>
|
||||
* In the blocking mode, which is the default, iterator will be infinite.
|
||||
* The call to {@link Iterator#next()} will block until a new notification
|
||||
* arrives. This is useful for application that runs perpetually and reacts
|
||||
* to notifications.
|
||||
*
|
||||
* <p>
|
||||
* In the non-blocking mode, the iterator will only report the set of
|
||||
* notifications initially retrieved from GitHub, then quit. This is useful
|
||||
* for a batch application to process the current set of notifications.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GitHub#listNotifications()
|
||||
* @see GHRepository#listNotifications()
|
||||
*/
|
||||
public class GHNotificationStream implements Iterable<GHThread> {
|
||||
private final GitHub root;
|
||||
|
||||
private Boolean all, participating;
|
||||
private String since;
|
||||
private String apiUrl;
|
||||
private boolean nonBlocking = false;
|
||||
|
||||
/*package*/ GHNotificationStream(GitHub root, String apiUrl) {
|
||||
this.root = root;
|
||||
this.apiUrl = apiUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the stream include notifications that are already read?
|
||||
*/
|
||||
public GHNotificationStream read(boolean v) {
|
||||
all = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the stream be restricted to notifications in which the user
|
||||
* is directly participating or mentioned?
|
||||
*/
|
||||
public GHNotificationStream participating(boolean v) {
|
||||
participating = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHNotificationStream since(long timestamp) {
|
||||
return since(new Date(timestamp));
|
||||
}
|
||||
|
||||
public GHNotificationStream since(Date dt) {
|
||||
since = GitHub.printDate(dt);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to true, {@link #iterator()} will stop iterating instead of blocking and
|
||||
* waiting for the updates to arrive.
|
||||
*/
|
||||
public GHNotificationStream nonBlocking(boolean v) {
|
||||
this.nonBlocking = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an infinite blocking {@link Iterator} that returns
|
||||
* {@link GHThread} as notifications arrive.
|
||||
*/
|
||||
public Iterator<GHThread> iterator() {
|
||||
// capture the configuration setting here
|
||||
final Requester req = new Requester(root).method("GET")
|
||||
.with("all", all).with("participating", participating).with("since", since);
|
||||
|
||||
return new Iterator<GHThread>() {
|
||||
/**
|
||||
* Stuff we've fetched but haven't returned to the caller.
|
||||
* Newer ones first.
|
||||
*/
|
||||
private GHThread[] threads = EMPTY_ARRAY;
|
||||
|
||||
/**
|
||||
* Next element in {@link #threads} to return. This counts down.
|
||||
*/
|
||||
private int idx=-1;
|
||||
|
||||
/**
|
||||
* threads whose updated_at is older than this should be ignored.
|
||||
*/
|
||||
private long lastUpdated = -1;
|
||||
|
||||
/**
|
||||
* Next request should have "If-Modified-Since" header with this value.
|
||||
*/
|
||||
private String lastModified;
|
||||
|
||||
/**
|
||||
* When is the next polling allowed?
|
||||
*/
|
||||
private long nextCheckTime = -1;
|
||||
|
||||
private GHThread next;
|
||||
|
||||
public GHThread next() {
|
||||
if (next==null) {
|
||||
next = fetch();
|
||||
if (next==null)
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
GHThread r = next;
|
||||
next = null;
|
||||
return r;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
if (next==null)
|
||||
next = fetch();
|
||||
return next!=null;
|
||||
}
|
||||
|
||||
GHThread fetch() {
|
||||
try {
|
||||
while (true) {// loop until we get new threads to return
|
||||
|
||||
// if we have fetched un-returned threads, use them first
|
||||
while (idx>=0) {
|
||||
GHThread n = threads[idx--];
|
||||
long nt = n.getUpdatedAt().getTime();
|
||||
if (nt >= lastUpdated) {
|
||||
lastUpdated = nt;
|
||||
return n.wrap(root);
|
||||
}
|
||||
}
|
||||
|
||||
if (nonBlocking && nextCheckTime>=0)
|
||||
return null; // nothing more to report, and we aren't blocking
|
||||
|
||||
// observe the polling interval before making the call
|
||||
while (true) {
|
||||
long now = System.currentTimeMillis();
|
||||
if (nextCheckTime < now) break;
|
||||
long waitTime = Math.max(Math.min(nextCheckTime - now, 1000), 60 * 1000);
|
||||
Thread.sleep(waitTime);
|
||||
}
|
||||
|
||||
req.setHeader("If-Modified-Since", lastModified);
|
||||
|
||||
threads = req.to(apiUrl, GHThread[].class);
|
||||
if (threads==null) {
|
||||
threads = EMPTY_ARRAY; // if unmodified, we get empty array
|
||||
} else {
|
||||
// we get a new batch, but we want to ignore the ones that we've seen
|
||||
lastUpdated++;
|
||||
}
|
||||
idx = threads.length-1;
|
||||
|
||||
nextCheckTime = calcNextCheckTime();
|
||||
lastModified = req.getResponseHeader("Last-Modified");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private long calcNextCheckTime() {
|
||||
String v = req.getResponseHeader("X-Poll-Interval");
|
||||
if (v==null) v="60";
|
||||
return System.currentTimeMillis()+Integer.parseInt(v)*1000;
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void markAsRead() throws IOException {
|
||||
markAsRead(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks all the notifications as read.
|
||||
*/
|
||||
public void markAsRead(long timestamp) throws IOException {
|
||||
final Requester req = new Requester(root).method("PUT");
|
||||
if (timestamp>=0)
|
||||
req.with("last_read_at", GitHub.printDate(new Date(timestamp)));
|
||||
req.asHttpStatusCode(apiUrl);
|
||||
}
|
||||
|
||||
private static final GHThread[] EMPTY_ARRAY = new GHThread[0];
|
||||
}
|
||||
69
src/main/java/org/kohsuke/github/GHObject.java
Normal file
69
src/main/java/org/kohsuke/github/GHObject.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Most (all?) domain objects in GitHub seems to have these 4 properties.
|
||||
*/
|
||||
public abstract class GHObject {
|
||||
protected String url;
|
||||
protected int id;
|
||||
protected String created_at;
|
||||
protected String updated_at;
|
||||
|
||||
/*package*/ GHObject() {
|
||||
}
|
||||
|
||||
/**
|
||||
* When was this resource created?
|
||||
*/
|
||||
@WithBridgeMethods(value=String.class, adapterMethod="createdAtStr")
|
||||
public Date getCreatedAt() throws IOException {
|
||||
return GitHub.parseDate(created_at);
|
||||
}
|
||||
|
||||
private Object createdAtStr(Date id, Class type) {
|
||||
return created_at;
|
||||
}
|
||||
|
||||
/**
|
||||
* API URL of this object.
|
||||
*/
|
||||
@WithBridgeMethods(value=String.class, adapterMethod="urlToString")
|
||||
public URL getUrl() {
|
||||
return GitHub.parseURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* URL of this object for humans, which renders some HTML.
|
||||
*/
|
||||
@WithBridgeMethods(value=String.class, adapterMethod="urlToString")
|
||||
public abstract URL getHtmlUrl();
|
||||
|
||||
/**
|
||||
* When was this resource last updated?
|
||||
*/
|
||||
public Date getUpdatedAt() throws IOException {
|
||||
return GitHub.parseDate(updated_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unique ID number of this resource.
|
||||
*/
|
||||
@WithBridgeMethods(value=String.class, adapterMethod="intToString")
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private Object intToString(int id, Class type) {
|
||||
return String.valueOf(id);
|
||||
}
|
||||
|
||||
private Object urlToString(URL url, Class type) {
|
||||
return url==null ? null : url.toString();
|
||||
}
|
||||
}
|
||||
27
src/main/java/org/kohsuke/github/GHOrgHook.java
Normal file
27
src/main/java/org/kohsuke/github/GHOrgHook.java
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* © Copyright 2015 - SourceClear Inc
|
||||
*/
|
||||
|
||||
package org.kohsuke.github;
|
||||
|
||||
class GHOrgHook extends GHHook {
|
||||
/**
|
||||
* Organization that the hook belongs to.
|
||||
*/
|
||||
/*package*/ transient GHOrganization organization;
|
||||
|
||||
/*package*/ GHOrgHook wrap(GHOrganization owner) {
|
||||
this.organization = owner;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
GitHub getRoot() {
|
||||
return organization.root;
|
||||
}
|
||||
|
||||
@Override
|
||||
String getApiRoute() {
|
||||
return String.format("/orgs/%s/hooks/%d", organization.getLogin(), id);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,24 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import com.gargoylesoftware.htmlunit.WebClient;
|
||||
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
|
||||
import com.gargoylesoftware.htmlunit.html.HtmlPage;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.AbstractList;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.kohsuke.github.ApiVersion.*;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class GHOrganization extends GHPerson {
|
||||
/*package*/ GHOrganization wrapUp(GitHub root) {
|
||||
return (GHOrganization)super.wrapUp(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new repository.
|
||||
*
|
||||
@@ -26,12 +26,17 @@ public class GHOrganization extends GHPerson {
|
||||
* Newly created repository.
|
||||
*/
|
||||
public GHRepository createRepository(String name, String description, String homepage, String team, boolean isPublic) throws IOException {
|
||||
return createRepository(name,description,homepage,getTeams().get(team),isPublic);
|
||||
GHTeam t = getTeams().get(team);
|
||||
if (t==null)
|
||||
throw new IllegalArgumentException("No such team: "+team);
|
||||
return createRepository(name, description, homepage, t, isPublic);
|
||||
}
|
||||
|
||||
public GHRepository createRepository(String name, String description, String homepage, GHTeam team, boolean isPublic) throws IOException {
|
||||
if (team==null)
|
||||
throw new IllegalArgumentException("Invalid team");
|
||||
// such API doesn't exist, so fall back to HTML scraping
|
||||
return new Poster(root,V3).withCredential()
|
||||
return new Requester(root)
|
||||
.with("name", name).with("description", description).with("homepage", homepage)
|
||||
.with("public", isPublic).with("team_id",team.getId()).to("/orgs/"+login+"/repos", GHRepository.class).wrap(root);
|
||||
}
|
||||
@@ -40,15 +45,70 @@ public class GHOrganization extends GHPerson {
|
||||
* Teams by their names.
|
||||
*/
|
||||
public Map<String,GHTeam> getTeams() throws IOException {
|
||||
return root.retrieveWithAuth("/organizations/"+login+"/teams",JsonTeams.class).toMap(this);
|
||||
Map<String,GHTeam> r = new TreeMap<String, GHTeam>();
|
||||
for (GHTeam t : listTeams()) {
|
||||
r.put(t.getName(),t);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GHRepository getRepository(String name) throws IOException {
|
||||
/**
|
||||
* List up all the teams.
|
||||
*/
|
||||
public PagedIterable<GHTeam> listTeams() throws IOException {
|
||||
return new PagedIterable<GHTeam>() {
|
||||
public PagedIterator<GHTeam> iterator() {
|
||||
return new PagedIterator<GHTeam>(root.retrieve().asIterator(String.format("/orgs/%s/teams", login), GHTeam[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHTeam[] page) {
|
||||
for (GHTeam c : page)
|
||||
c.wrapUp(GHOrganization.this);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a team that has the given name in its {@link GHTeam#getName()}
|
||||
*/
|
||||
public GHTeam getTeamByName(String name) throws IOException {
|
||||
for (GHTeam t : listTeams()) {
|
||||
if(t.getName().equals(name))
|
||||
return t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this organization has the specified user as a member.
|
||||
*/
|
||||
public boolean hasMember(GHUser user) {
|
||||
try {
|
||||
return root.retrieveWithAuth3("/repos/" + login + '/' + name, GHRepository.class).wrap(root);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
root.retrieve().to("/orgs/" + login + "/members/" + user.getLogin());
|
||||
return true;
|
||||
} catch (IOException ignore) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a member of the organisation - which will remove them from
|
||||
* all teams, and remove their access to the organization’s repositories.
|
||||
*/
|
||||
public void remove(GHUser user) throws IOException {
|
||||
root.retrieve().method("DELETE").to("/orgs/" + login + "/members/" + user.getLogin());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this organization has the specified user as a public member.
|
||||
*/
|
||||
public boolean hasPublicMember(GHUser user) {
|
||||
try {
|
||||
root.retrieve().to("/orgs/" + login + "/public_members/" + user.getLogin());
|
||||
return true;
|
||||
} catch (IOException ignore) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,30 +116,48 @@ public class GHOrganization extends GHPerson {
|
||||
* Publicizes the membership.
|
||||
*/
|
||||
public void publicize(GHUser u) throws IOException {
|
||||
root.retrieveWithAuth3("/orgs/" + login + "/public_members/" + u.getLogin(), null, "PUT");
|
||||
root.retrieve().method("PUT").to("/orgs/" + login + "/public_members/" + u.getLogin(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #listMembers()}
|
||||
*/
|
||||
public List<GHUser> getMembers() throws IOException {
|
||||
return listMembers().asList();
|
||||
}
|
||||
|
||||
/**
|
||||
* All the members of this organization.
|
||||
*/
|
||||
public List<GHUser> getMembers() throws IOException {
|
||||
return new AbstractList<GHUser>() {
|
||||
// these are shallow objects with only some limited values filled out
|
||||
// TODO: it's better to allow objects to fill themselves in later when missing values are requested
|
||||
final GHUser[] shallow = root.retrieveWithAuth3("/orgs/" + login + "/members", GHUser[].class);
|
||||
public PagedIterable<GHUser> listMembers() throws IOException {
|
||||
return listMembers("members");
|
||||
}
|
||||
|
||||
@Override
|
||||
public GHUser get(int index) {
|
||||
try {
|
||||
return root.getUser(shallow[index].getLogin());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* All the public members of this organization.
|
||||
*/
|
||||
public PagedIterable<GHUser> listPublicMembers() throws IOException {
|
||||
return listMembers("public_members");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return shallow.length;
|
||||
private PagedIterable<GHUser> listMembers(String suffix) throws IOException {
|
||||
return listMembers(suffix, null);
|
||||
}
|
||||
|
||||
public PagedIterable<GHUser> listMembersWithFilter(String filter) throws IOException {
|
||||
return listMembers("members", filter);
|
||||
}
|
||||
|
||||
private PagedIterable<GHUser> listMembers(final String suffix, final String filter) throws IOException {
|
||||
return new PagedIterable<GHUser>() {
|
||||
public PagedIterator<GHUser> iterator() {
|
||||
String filterParams = (filter == null) ? "" : ("?filter=" + filter);
|
||||
return new PagedIterator<GHUser>(root.retrieve().asIterator(String.format("/orgs/%s/%s%s", login, suffix, filterParams), GHUser[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHUser[] users) {
|
||||
GHUser.wrap(users, root);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -88,7 +166,7 @@ public class GHOrganization extends GHPerson {
|
||||
* Conceals the membership.
|
||||
*/
|
||||
public void conceal(GHUser u) throws IOException {
|
||||
root.retrieveWithAuth3("/orgs/" + login + "/public_members/" + u.getLogin(), null, "DELETE");
|
||||
root.retrieve().method("DELETE").to("/orgs/" + login + "/public_members/" + u.getLogin(), null);
|
||||
}
|
||||
|
||||
public enum Permission { ADMIN, PUSH, PULL }
|
||||
@@ -97,11 +175,13 @@ public class GHOrganization extends GHPerson {
|
||||
* Creates a new team and assigns the repositories.
|
||||
*/
|
||||
public GHTeam createTeam(String name, Permission p, Collection<GHRepository> repositories) throws IOException {
|
||||
Poster post = new Poster(root).withCredential().with("team[name]", name).with("team[permission]", p.name().toLowerCase());
|
||||
Requester post = new Requester(root).with("name", name).with("permission", p.name().toLowerCase());
|
||||
List<String> repo_names = new ArrayList<String>();
|
||||
for (GHRepository r : repositories) {
|
||||
post.with("team[repo_names][]",r.getOwnerName()+'/'+r.getName());
|
||||
repo_names.add(r.getName());
|
||||
}
|
||||
return post.to("/organizations/"+login+"/teams",JsonTeam.class).wrap(this);
|
||||
post.with("repo_names",repo_names);
|
||||
return post.method("POST").to("/orgs/" + login + "/teams", GHTeam.class).wrapUp(this);
|
||||
}
|
||||
|
||||
public GHTeam createTeam(String name, Permission p, GHRepository... repositories) throws IOException {
|
||||
@@ -110,15 +190,18 @@ public class GHOrganization extends GHPerson {
|
||||
|
||||
/**
|
||||
* List up repositories that has some open pull requests.
|
||||
*
|
||||
* This used to be an efficient method that didn't involve traversing every repository, but now
|
||||
* it doesn't do any optimization.
|
||||
*/
|
||||
public List<GHRepository> getRepositoriesWithOpenPullRequests() throws IOException {
|
||||
WebClient wc = root.createWebClient();
|
||||
HtmlPage pg = (HtmlPage)wc.getPage("https://github.com/organizations/"+login+"/dashboard/pulls");
|
||||
List<GHRepository> r = new ArrayList<GHRepository>();
|
||||
for (HtmlAnchor e : pg.getElementById("js-issue-list").<HtmlAnchor>selectNodes(".//UL[@class='smallnav']/LI[not(@class='zeroed')]/A")) {
|
||||
String a = e.getHrefAttribute();
|
||||
String name = a.substring(a.lastIndexOf('/')+1);
|
||||
r.add(getRepository(name));
|
||||
for (GHRepository repository : listRepositories()) {
|
||||
repository.wrap(root);
|
||||
List<GHPullRequest> pullRequests = repository.getPullRequests(GHIssueState.OPEN);
|
||||
if (pullRequests.size() > 0) {
|
||||
r.add(repository);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
@@ -133,4 +216,78 @@ public class GHOrganization extends GHPerson {
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists events performed by a user (this includes private events if the caller is authenticated.
|
||||
*/
|
||||
public PagedIterable<GHEventInfo> listEvents() throws IOException {
|
||||
return new PagedIterable<GHEventInfo>() {
|
||||
public PagedIterator<GHEventInfo> iterator() {
|
||||
return new PagedIterator<GHEventInfo>(root.retrieve().asIterator(String.format("/orgs/%s/events", login), GHEventInfo[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHEventInfo[] page) {
|
||||
for (GHEventInfo c : page)
|
||||
c.wrapUp(root);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists up all the repositories using the specified page size.
|
||||
*
|
||||
* @param pageSize size for each page of items returned by GitHub. Maximum page size is 100.
|
||||
*
|
||||
* Unlike {@link #getRepositories()}, this does not wait until all the repositories are returned.
|
||||
*/
|
||||
@Override
|
||||
public PagedIterable<GHRepository> listRepositories(final int pageSize) {
|
||||
return new PagedIterable<GHRepository>() {
|
||||
public PagedIterator<GHRepository> iterator() {
|
||||
return new PagedIterator<GHRepository>(root.retrieve().asIterator("/orgs/" + login + "/repos?per_page=" + pageSize, GHRepository[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHRepository[] page) {
|
||||
for (GHRepository c : page)
|
||||
c.wrap(root);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the currently configured hooks.
|
||||
*/
|
||||
public List<GHHook> getHooks() throws IOException {
|
||||
return GHHooks.orgContext(this).getHooks();
|
||||
}
|
||||
|
||||
public GHHook getHook(int id) throws IOException {
|
||||
return GHHooks.orgContext(this).getHook(id);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* See https://api.github.com/hooks for possible names and their configuration scheme.
|
||||
* TODO: produce type-safe binding
|
||||
*
|
||||
* @param name
|
||||
* Type of the hook to be created. See https://api.github.com/hooks for possible names.
|
||||
* @param config
|
||||
* The configuration hash.
|
||||
* @param events
|
||||
* Can be null. Types of events to hook into.
|
||||
*/
|
||||
public GHHook createHook(String name, Map<String,String> config, Collection<GHEvent> events, boolean active) throws IOException {
|
||||
return GHHooks.orgContext(this).createHook(name, config, events, active);
|
||||
}
|
||||
|
||||
public GHHook createWebHook(URL url, Collection<GHEvent> events) throws IOException {
|
||||
return createHook("web", Collections.singletonMap("url", url.toExternalForm()),events,true);
|
||||
}
|
||||
|
||||
public GHHook createWebHook(URL url) throws IOException {
|
||||
return createWebHook(url, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,12 @@ package org.kohsuke.github;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
@@ -11,38 +16,116 @@ import java.util.TreeMap;
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public abstract class GHPerson {
|
||||
public abstract class GHPerson extends GHObject {
|
||||
/*package almost final*/ GitHub root;
|
||||
|
||||
// common
|
||||
protected String login,location,blog,email,name,created_at,company;
|
||||
protected int id;
|
||||
protected String gravatar_id; // appears in V3 as well but presumably subsumed by avatar_url?
|
||||
// core data fields that exist even for "small" user data (such as the user info in pull request)
|
||||
protected String login, avatar_url, gravatar_id;
|
||||
|
||||
// V2
|
||||
protected int public_gist_count,public_repo_count,followers_count,following_count;
|
||||
|
||||
// V3
|
||||
protected String avatar_url,html_url;
|
||||
// other fields (that only show up in full data)
|
||||
protected String location,blog,email,name,company;
|
||||
protected String html_url;
|
||||
protected int followers,following,public_repos,public_gists;
|
||||
|
||||
/*package*/ GHPerson wrapUp(GitHub root) {
|
||||
this.root = root;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the repositories this user owns.
|
||||
* Fully populate the data by retrieving missing data.
|
||||
*
|
||||
* Depending on the original API call where this object is created, it may not contain everything.
|
||||
*/
|
||||
protected synchronized void populate() throws IOException {
|
||||
if (created_at!=null) return; // already populated
|
||||
|
||||
root.retrieve().to(url, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the public repositories this user owns.
|
||||
*
|
||||
* <p>
|
||||
* To list your own repositories, including private repositories,
|
||||
* use {@link GHMyself#listRepositories()}
|
||||
*/
|
||||
public synchronized Map<String,GHRepository> getRepositories() throws IOException {
|
||||
Map<String,GHRepository> repositories = new TreeMap<String, GHRepository>();
|
||||
for (int i=1; ; i++) {
|
||||
GHRepository[] array = root.retrieve3("/users/" + login + "/repos?per_page=100&page=" + i, GHRepository[].class);
|
||||
for (GHRepository r : array) {
|
||||
r.root = root;
|
||||
repositories.put(r.getName(),r);
|
||||
}
|
||||
if (array.length==0) break;
|
||||
for (GHRepository r : listRepositories()) {
|
||||
repositories.put(r.getName(),r);
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(repositories);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists up all the repositories using a 30 items page size.
|
||||
*
|
||||
* Unlike {@link #getRepositories()}, this does not wait until all the repositories are returned.
|
||||
*/
|
||||
public PagedIterable<GHRepository> listRepositories() {
|
||||
return listRepositories(30);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists up all the repositories using the specified page size.
|
||||
*
|
||||
* @param pageSize size for each page of items returned by GitHub. Maximum page size is 100.
|
||||
*
|
||||
* Unlike {@link #getRepositories()}, this does not wait until all the repositories are returned.
|
||||
*/
|
||||
public PagedIterable<GHRepository> listRepositories(final int pageSize) {
|
||||
return new PagedIterable<GHRepository>() {
|
||||
public PagedIterator<GHRepository> iterator() {
|
||||
return new PagedIterator<GHRepository>(root.retrieve().asIterator("/users/" + login + "/repos?per_page=" + pageSize, GHRepository[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHRepository[] page) {
|
||||
for (GHRepository c : page)
|
||||
c.wrap(root);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads repository list in a pagenated fashion.
|
||||
*
|
||||
* <p>
|
||||
* For a person with a lot of repositories, GitHub returns the list of repositories in a pagenated fashion.
|
||||
* Unlike {@link #getRepositories()}, this method allows the caller to start processing data as it arrives.
|
||||
*
|
||||
* Every {@link Iterator#next()} call results in I/O. Exceptions that occur during the processing is wrapped
|
||||
* into {@link Error}.
|
||||
*
|
||||
* @deprecated
|
||||
* Use {@link #listRepositories()}
|
||||
*/
|
||||
public synchronized Iterable<List<GHRepository>> iterateRepositories(final int pageSize) {
|
||||
return new Iterable<List<GHRepository>>() {
|
||||
public Iterator<List<GHRepository>> iterator() {
|
||||
final Iterator<GHRepository[]> pager = root.retrieve().asIterator("/users/" + login + "/repos?per_page="+pageSize,GHRepository[].class);
|
||||
|
||||
return new Iterator<List<GHRepository>>() {
|
||||
public boolean hasNext() {
|
||||
return pager.hasNext();
|
||||
}
|
||||
|
||||
public List<GHRepository> next() {
|
||||
GHRepository[] batch = pager.next();
|
||||
for (GHRepository r : batch)
|
||||
r.root = root;
|
||||
return Arrays.asList(batch);
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
@@ -50,19 +133,39 @@ public abstract class GHPerson {
|
||||
*/
|
||||
public GHRepository getRepository(String name) throws IOException {
|
||||
try {
|
||||
return root.retrieve3("/repos/" + login + '/' + name, GHRepository.class).wrap(root);
|
||||
return root.retrieve().to("/repos/" + login + '/' + name, GHRepository.class).wrap(root);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists events for an organization or an user.
|
||||
*/
|
||||
public abstract PagedIterable<GHEventInfo> listEvents() throws IOException;
|
||||
|
||||
/**
|
||||
* Gravatar ID of this user, like 0cb9832a01c22c083390f3c5dcb64105
|
||||
*
|
||||
* @deprecated
|
||||
* No longer available in the v3 API.
|
||||
*/
|
||||
public String getGravatarId() {
|
||||
return gravatar_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string like 'https://secure.gravatar.com/avatar/0cb9832a01c22c083390f3c5dcb64105'
|
||||
* that indicates the avatar image URL.
|
||||
*/
|
||||
public String getAvatarUrl() {
|
||||
if (avatar_url!=null)
|
||||
return avatar_url;
|
||||
if (gravatar_id!=null)
|
||||
return "https://secure.gravatar.com/avatar/"+gravatar_id;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the login ID of this user, like 'kohsuke'
|
||||
*/
|
||||
@@ -73,63 +176,75 @@ public abstract class GHPerson {
|
||||
/**
|
||||
* Gets the human-readable name of the user, like "Kohsuke Kawaguchi"
|
||||
*/
|
||||
public String getName() {
|
||||
public String getName() throws IOException {
|
||||
populate();
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the company name of this user, like "Sun Microsystems, Inc."
|
||||
*/
|
||||
public String getCompany() {
|
||||
public String getCompany() throws IOException {
|
||||
populate();
|
||||
return company;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the location of this user, like "Santa Clara, California"
|
||||
*/
|
||||
public String getLocation() {
|
||||
public String getLocation() throws IOException {
|
||||
populate();
|
||||
return location;
|
||||
}
|
||||
|
||||
public String getCreatedAt() {
|
||||
return created_at;
|
||||
public Date getCreatedAt() throws IOException {
|
||||
populate();
|
||||
return super.getCreatedAt();
|
||||
}
|
||||
|
||||
public Date getUpdatedAt() throws IOException {
|
||||
populate();
|
||||
return super.getCreatedAt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the blog URL of this user.
|
||||
*/
|
||||
public String getBlog() {
|
||||
public String getBlog() throws IOException {
|
||||
populate();
|
||||
return blog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getHtmlUrl() {
|
||||
return GitHub.parseURL(html_url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the e-mail address of the user.
|
||||
*/
|
||||
public String getEmail() {
|
||||
public String getEmail() throws IOException {
|
||||
populate();
|
||||
return email;
|
||||
}
|
||||
|
||||
public int getPublicGistCount() {
|
||||
return Math.max(public_gist_count,public_gists);
|
||||
public int getPublicGistCount() throws IOException {
|
||||
populate();
|
||||
return public_gists;
|
||||
}
|
||||
|
||||
public int getPublicRepoCount() {
|
||||
return Math.max(public_repo_count,public_repos);
|
||||
public int getPublicRepoCount() throws IOException {
|
||||
populate();
|
||||
return public_repos;
|
||||
}
|
||||
|
||||
public int getFollowingCount() {
|
||||
return Math.max(following_count,following);
|
||||
public int getFollowingCount() throws IOException {
|
||||
populate();
|
||||
return following;
|
||||
}
|
||||
|
||||
/**
|
||||
* What appears to be a GitHub internal unique number that identifies this user.
|
||||
*/
|
||||
public int getId() {
|
||||
return id;
|
||||
public int getFollowersCount() throws IOException {
|
||||
populate();
|
||||
return followers;
|
||||
}
|
||||
|
||||
public int getFollowersCount() {
|
||||
return Math.max(followers_count,followers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
@@ -8,7 +9,7 @@ import java.util.HashSet;
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public final class GHPersonSet<T extends GHPerson> extends HashSet<T> {
|
||||
public class GHPersonSet<T extends GHPerson> extends HashSet<T> {
|
||||
public GHPersonSet() {
|
||||
}
|
||||
|
||||
@@ -16,6 +17,10 @@ public final class GHPersonSet<T extends GHPerson> extends HashSet<T> {
|
||||
super(c);
|
||||
}
|
||||
|
||||
public GHPersonSet(T... c) {
|
||||
super(Arrays.asList(c));
|
||||
}
|
||||
|
||||
public GHPersonSet(int initialCapacity, float loadFactor) {
|
||||
super(initialCapacity, loadFactor);
|
||||
}
|
||||
|
||||
@@ -23,22 +23,59 @@
|
||||
*/
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* A pull request.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GHRepository#getPullRequest(int)
|
||||
*/
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public class GHPullRequest extends GHIssue {
|
||||
private String closed_at, patch_url, issue_updated_at;
|
||||
private GHUser issue_user, user;
|
||||
// labels??
|
||||
private GHCommitPointer base, head;
|
||||
private String mergeable, diff_url;
|
||||
|
||||
private String patch_url, diff_url, issue_url;
|
||||
private GHCommitPointer base;
|
||||
private String merged_at;
|
||||
private GHCommitPointer head;
|
||||
|
||||
// details that are only available when obtained from ID
|
||||
private GHUser merged_by;
|
||||
private int review_comments, additions;
|
||||
private boolean merged;
|
||||
private Boolean mergeable;
|
||||
private int deletions;
|
||||
private String mergeable_state;
|
||||
private int changed_files;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
GHPullRequest wrapUp(GitHub root) {
|
||||
if (owner != null) owner.wrap(root);
|
||||
if (base != null) base.wrapUp(root);
|
||||
if (head != null) head.wrapUp(root);
|
||||
if (merged_by != null) merged_by.wrapUp(root);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getApiRoute() {
|
||||
return "/repos/"+owner.getOwnerName()+"/"+owner.getName()+"/pulls/"+number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL of the patch file.
|
||||
@@ -47,12 +84,13 @@ public class GHPullRequest extends GHIssue {
|
||||
public URL getPatchUrl() {
|
||||
return GitHub.parseURL(patch_url);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* User who submitted a pull request.
|
||||
* The URL of the patch file.
|
||||
* like https://github.com/jenkinsci/jenkins/pull/100.patch
|
||||
*/
|
||||
public GHUser getUser() {
|
||||
return user;
|
||||
public URL getIssueUrl() {
|
||||
return GitHub.parseURL(issue_url);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,22 +102,15 @@ public class GHPullRequest extends GHIssue {
|
||||
}
|
||||
|
||||
/**
|
||||
* The change that should be pulled.
|
||||
* The change that should be pulled. The tip of the commits to merge.
|
||||
*/
|
||||
public GHCommitPointer getHead() {
|
||||
return head;
|
||||
}
|
||||
|
||||
public Date getIssueUpdatedAt() {
|
||||
return GitHub.parseDate(issue_updated_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML page of this pull request,
|
||||
* like https://github.com/jenkinsci/jenkins/pull/100
|
||||
*/
|
||||
public URL getUrl() {
|
||||
return super.getUrl();
|
||||
|
||||
@Deprecated
|
||||
public Date getIssueUpdatedAt() throws IOException {
|
||||
return super.getUpdatedAt();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +121,172 @@ public class GHPullRequest extends GHIssue {
|
||||
return GitHub.parseURL(diff_url);
|
||||
}
|
||||
|
||||
public Date getClosedAt() {
|
||||
return GitHub.parseDate(closed_at);
|
||||
public Date getMergedAt() {
|
||||
return GitHub.parseDate(merged_at);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<GHLabel> getLabels() throws IOException {
|
||||
fetchIssue();
|
||||
return super.getLabels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GHUser getClosedBy() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PullRequest getPullRequest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
//
|
||||
// details that are only available via get with ID
|
||||
//
|
||||
//
|
||||
public GHUser getMergedBy() throws IOException {
|
||||
populate();
|
||||
return merged_by;
|
||||
}
|
||||
|
||||
public int getReviewComments() throws IOException {
|
||||
populate();
|
||||
return review_comments;
|
||||
}
|
||||
|
||||
public int getAdditions() throws IOException {
|
||||
populate();
|
||||
return additions;
|
||||
}
|
||||
|
||||
public boolean isMerged() throws IOException {
|
||||
populate();
|
||||
return merged;
|
||||
}
|
||||
|
||||
public Boolean getMergeable() throws IOException {
|
||||
populate();
|
||||
return mergeable;
|
||||
}
|
||||
|
||||
public int getDeletions() throws IOException {
|
||||
populate();
|
||||
return deletions;
|
||||
}
|
||||
|
||||
public String getMergeableState() throws IOException {
|
||||
populate();
|
||||
return mergeable_state;
|
||||
}
|
||||
|
||||
public int getChangedFiles() throws IOException {
|
||||
populate();
|
||||
return changed_files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully populate the data by retrieving missing data.
|
||||
*
|
||||
* Depending on the original API call where this object is created, it may not contain everything.
|
||||
*/
|
||||
private void populate() throws IOException {
|
||||
if (merged_by!=null) return; // already populated
|
||||
|
||||
root.retrieve().to(url, this).wrapUp(owner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all the commits associated to this pull request.
|
||||
*/
|
||||
public PagedIterable<GHPullRequestFileDetail> listFiles() {
|
||||
return new PagedIterable<GHPullRequestFileDetail>() {
|
||||
public PagedIterator<GHPullRequestFileDetail> iterator() {
|
||||
return new PagedIterator<GHPullRequestFileDetail>(root.retrieve().asIterator(String.format("%s/files", getApiURL()),
|
||||
GHPullRequestFileDetail[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHPullRequestFileDetail[] page) {
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains all the review comments associated with this pull request.
|
||||
*/
|
||||
public PagedIterable<GHPullRequestReviewComment> listReviewComments() throws IOException {
|
||||
return new PagedIterable<GHPullRequestReviewComment>() {
|
||||
public PagedIterator<GHPullRequestReviewComment> iterator() {
|
||||
return new PagedIterator<GHPullRequestReviewComment>(root.retrieve().asIterator(getApiRoute() + "/comments",
|
||||
GHPullRequestReviewComment[].class)) {
|
||||
protected void wrapUp(GHPullRequestReviewComment[] page) {
|
||||
for (GHPullRequestReviewComment c : page)
|
||||
c.wrapUp(GHPullRequest.this);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all the commits associated to this pull request.
|
||||
*/
|
||||
public PagedIterable<GHPullRequestCommitDetail> listCommits() {
|
||||
return new PagedIterable<GHPullRequestCommitDetail>() {
|
||||
public PagedIterator<GHPullRequestCommitDetail> iterator() {
|
||||
return new PagedIterator<GHPullRequestCommitDetail>(root.retrieve().asIterator(
|
||||
String.format("%s/commits", getApiURL()),
|
||||
GHPullRequestCommitDetail[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHPullRequestCommitDetail[] page) {
|
||||
for (GHPullRequestCommitDetail c : page)
|
||||
c.wrapUp(GHPullRequest.this);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public GHPullRequestReviewComment createReviewComment(String body, String sha, String path, int position) throws IOException {
|
||||
return new Requester(root).method("POST")
|
||||
.with("body", body)
|
||||
.with("commit_id", sha)
|
||||
.with("path", path)
|
||||
.with("position", position)
|
||||
.to(getApiRoute() + "/comments", GHPullRequestReviewComment.class).wrapUp(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this pull request.
|
||||
*
|
||||
* The equivalent of the big green "Merge pull request" button.
|
||||
*
|
||||
* @param msg
|
||||
* Commit message. If null, the default one will be used.
|
||||
*/
|
||||
public void merge(String msg) throws IOException {
|
||||
merge(msg,null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this pull request.
|
||||
*
|
||||
* The equivalent of the big green "Merge pull request" button.
|
||||
*
|
||||
* @param msg
|
||||
* Commit message. If null, the default one will be used.
|
||||
* @param sha
|
||||
* SHA that pull request head must match to allow merge.
|
||||
*/
|
||||
public void merge(String msg, String sha) throws IOException {
|
||||
new Requester(root).method("PUT").with("commit_message",msg).with("sha",sha).to(getApiRoute()+"/merge");
|
||||
}
|
||||
|
||||
private void fetchIssue() throws IOException {
|
||||
if (!fetchedIssueDetails) {
|
||||
new Requester(root).to(getIssuesApiRoute(), this);
|
||||
fetchedIssueDetails = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
141
src/main/java/org/kohsuke/github/GHPullRequestCommitDetail.java
Normal file
141
src/main/java/org/kohsuke/github/GHPullRequestCommitDetail.java
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2013, Luca Milanesio
|
||||
*
|
||||
* 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.infradna.tool.bridge_method_injector.WithBridgeMethods;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Commit detail inside a {@link GHPullRequest}.
|
||||
*
|
||||
* @author Luca Milanesio
|
||||
* @see GHPullRequest#listCommits()
|
||||
*/
|
||||
public class GHPullRequestCommitDetail {
|
||||
private GHPullRequest owner;
|
||||
|
||||
/*package*/ void wrapUp(GHPullRequest owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link GitUser}
|
||||
*/
|
||||
public static class Authorship extends GitUser {
|
||||
}
|
||||
|
||||
public static class Tree {
|
||||
String sha;
|
||||
String url;
|
||||
|
||||
public String getSha() {
|
||||
return sha;
|
||||
}
|
||||
|
||||
public URL getUrl() {
|
||||
return GitHub.parseURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Commit {
|
||||
Authorship author;
|
||||
Authorship committer;
|
||||
String message;
|
||||
Tree tree;
|
||||
String url;
|
||||
int comment_count;
|
||||
|
||||
@WithBridgeMethods(value = Authorship.class, castRequired = true)
|
||||
public GitUser getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
@WithBridgeMethods(value = Authorship.class, castRequired = true)
|
||||
public GitUser getCommitter() {
|
||||
return committer;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public URL getUrl() {
|
||||
return GitHub.parseURL(url);
|
||||
}
|
||||
|
||||
public int getComment_count() {
|
||||
return comment_count;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommitPointer {
|
||||
String sha;
|
||||
String url;
|
||||
String html_url;
|
||||
|
||||
public URL getUrl() {
|
||||
return GitHub.parseURL(url);
|
||||
}
|
||||
|
||||
public URL getHtml_url() {
|
||||
return GitHub.parseURL(html_url);
|
||||
}
|
||||
|
||||
public String getSha() {
|
||||
return sha;
|
||||
}
|
||||
}
|
||||
|
||||
String sha;
|
||||
Commit commit;
|
||||
String url;
|
||||
String html_url;
|
||||
String comments_url;
|
||||
CommitPointer[] parents;
|
||||
|
||||
public String getSha() {
|
||||
return sha;
|
||||
}
|
||||
|
||||
public Commit getCommit() {
|
||||
return commit;
|
||||
}
|
||||
|
||||
public URL getApiUrl() {
|
||||
return GitHub.parseURL(url);
|
||||
}
|
||||
|
||||
public URL getUrl() {
|
||||
return GitHub.parseURL(html_url);
|
||||
}
|
||||
|
||||
public URL getCommentsUrl() {
|
||||
return GitHub.parseURL(comments_url);
|
||||
}
|
||||
|
||||
public CommitPointer[] getParents() {
|
||||
return parents;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2015, Julien Henry
|
||||
*
|
||||
* 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 java.net.URL;
|
||||
|
||||
/**
|
||||
* File detail inside a {@link GHPullRequest}.
|
||||
*
|
||||
* @author Julien Henry
|
||||
* @see GHPullRequest#listFiles()
|
||||
*/
|
||||
public class GHPullRequestFileDetail {
|
||||
|
||||
String sha;
|
||||
String filename;
|
||||
String status;
|
||||
int additions;
|
||||
int deletions;
|
||||
int changes;
|
||||
String blob_url;
|
||||
String raw_url;
|
||||
String contents_url;
|
||||
String patch;
|
||||
|
||||
public String getSha() {
|
||||
return sha;
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public int getAdditions() {
|
||||
return additions;
|
||||
}
|
||||
|
||||
public int getDeletions() {
|
||||
return deletions;
|
||||
}
|
||||
|
||||
public int getChanges() {
|
||||
return changes;
|
||||
}
|
||||
|
||||
public URL getBlobUrl() {
|
||||
return GitHub.parseURL(blob_url);
|
||||
}
|
||||
|
||||
public URL getRawUrl() {
|
||||
return GitHub.parseURL(raw_url);
|
||||
}
|
||||
|
||||
public URL getContentsUrl() {
|
||||
return GitHub.parseURL(contents_url);
|
||||
}
|
||||
|
||||
public String getPatch() {
|
||||
return patch;
|
||||
}
|
||||
}
|
||||
106
src/main/java/org/kohsuke/github/GHPullRequestReviewComment.java
Normal file
106
src/main/java/org/kohsuke/github/GHPullRequestReviewComment.java
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2010, Kohsuke Kawaguchi
|
||||
*
|
||||
* 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 java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Review comment to the pull request
|
||||
*
|
||||
* @author Julien Henry
|
||||
* @see GHPullRequest#listReviewComments()
|
||||
* @see GHPullRequest#createReviewComment(String, String, String, int)
|
||||
*/
|
||||
public class GHPullRequestReviewComment extends GHObject {
|
||||
GHPullRequest owner;
|
||||
|
||||
private String body;
|
||||
private GHUser user;
|
||||
private String path;
|
||||
private int position;
|
||||
private int originalPosition;
|
||||
|
||||
/*package*/ GHPullRequestReviewComment wrapUp(GHPullRequest owner) {
|
||||
this.owner = owner;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pull request to which this review comment is associated.
|
||||
*/
|
||||
public GHPullRequest getParent() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* The comment itself.
|
||||
*/
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user who posted this comment.
|
||||
*/
|
||||
public GHUser getUser() throws IOException {
|
||||
return owner.root.getUser(user.getLogin());
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public int getOriginalPosition() {
|
||||
return originalPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getHtmlUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String getApiRoute() {
|
||||
return "/repos/"+owner.getRepository().getFullName()+"/pulls/comments/"+id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the comment.
|
||||
*/
|
||||
public void update(String body) throws IOException {
|
||||
new Requester(owner.root).method("PATCH").with("body", body).to(getApiRoute(),this);
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this review comment.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
new Requester(owner.root).method("DELETE").to(getApiRoute());
|
||||
}
|
||||
}
|
||||
39
src/main/java/org/kohsuke/github/GHRateLimit.java
Normal file
39
src/main/java/org/kohsuke/github/GHRateLimit.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Rate limit.
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class GHRateLimit {
|
||||
/**
|
||||
* Remaining calls that can be made.
|
||||
*/
|
||||
public int remaining;
|
||||
/**
|
||||
* Allotted API call per hour.
|
||||
*/
|
||||
public int limit;
|
||||
|
||||
/**
|
||||
* The time at which the current rate limit window resets in UTC epoch seconds.
|
||||
*/
|
||||
public Date reset;
|
||||
|
||||
/**
|
||||
* Non-epoch date
|
||||
*/
|
||||
public Date getResetDate() {
|
||||
return new Date(reset.getTime() * 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GHRateLimit{" +
|
||||
"remaining=" + remaining +
|
||||
", limit=" + limit +
|
||||
", resetDate=" + getResetDate() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
105
src/main/java/org/kohsuke/github/GHRef.java
Normal file
105
src/main/java/org/kohsuke/github/GHRef.java
Normal file
@@ -0,0 +1,105 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Provides information on a Git ref from GitHub.
|
||||
*
|
||||
* @author Michael Clarke
|
||||
*/
|
||||
public class GHRef {
|
||||
/*package almost final*/ GitHub root;
|
||||
|
||||
private String ref, url;
|
||||
private GHObject object;
|
||||
|
||||
/**
|
||||
* Name of the ref, such as "refs/tags/abc"
|
||||
*/
|
||||
public String getRef() {
|
||||
return ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* The API URL of this tag, such as https://api.github.com/repos/jenkinsci/jenkins/git/refs/tags/1.312
|
||||
*/
|
||||
public URL getUrl() {
|
||||
return GitHub.parseURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* The object that this ref points to.
|
||||
*/
|
||||
public GHObject getObject() {
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates this ref to the specified commit.
|
||||
*
|
||||
* @param sha
|
||||
* The SHA1 value to set this reference to
|
||||
*/
|
||||
public void updateTo(String sha) throws IOException {
|
||||
updateTo(sha, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates this ref to the specified commit.
|
||||
*
|
||||
* @param sha
|
||||
* The SHA1 value to set this reference to
|
||||
* @param force
|
||||
* Whether or not to force this ref update.
|
||||
*/
|
||||
public void updateTo(String sha, Boolean force) throws IOException {
|
||||
new Requester(root)
|
||||
.with("sha", sha).with("force", force).method("PATCH").to(url, GHRef.class).wrap(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this ref from the repository using the GitHub API.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
new Requester(root).method("DELETE").to(url);
|
||||
}
|
||||
|
||||
/*package*/ GHRef wrap(GitHub root) {
|
||||
this.root = root;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*package*/ static GHRef[] wrap(GHRef[] in, GitHub root) {
|
||||
for (GHRef r : in) {
|
||||
r.wrap(root);
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
||||
|
||||
public static class GHObject {
|
||||
private String type, sha, url;
|
||||
|
||||
/**
|
||||
* Type of the object, such as "commit"
|
||||
*/
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* SHA1 of this object.
|
||||
*/
|
||||
public String getSha() {
|
||||
return sha;
|
||||
}
|
||||
|
||||
/**
|
||||
* API URL to this Git data, such as https://api.github.com/repos/jenkinsci/jenkins/git/commits/b72322675eb0114363a9a86e9ad5a170d1d07ac0
|
||||
*/
|
||||
public URL getUrl() {
|
||||
return GitHub.parseURL(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
150
src/main/java/org/kohsuke/github/GHRelease.java
Normal file
150
src/main/java/org/kohsuke/github/GHRelease.java
Normal file
@@ -0,0 +1,150 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
/**
|
||||
* Release in a github repository.
|
||||
*
|
||||
* @see GHRepository#getReleases()
|
||||
* @see GHRepository#createRelease(String)
|
||||
*/
|
||||
public class GHRelease extends GHObject {
|
||||
GitHub root;
|
||||
GHRepository owner;
|
||||
|
||||
private String html_url;
|
||||
private String assets_url;
|
||||
private String upload_url;
|
||||
private String tag_name;
|
||||
private String target_commitish;
|
||||
private String name;
|
||||
private String body;
|
||||
private boolean draft;
|
||||
private boolean prerelease;
|
||||
private Date published_at;
|
||||
private String tarball_url;
|
||||
private String zipball_url;
|
||||
|
||||
public String getAssetsUrl() {
|
||||
return assets_url;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public boolean isDraft() {
|
||||
return draft;
|
||||
}
|
||||
|
||||
public URL getHtmlUrl() {
|
||||
return GitHub.parseURL(html_url);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public GHRepository getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(GHRepository owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public boolean isPrerelease() {
|
||||
return prerelease;
|
||||
}
|
||||
|
||||
public Date getPublished_at() {
|
||||
return published_at;
|
||||
}
|
||||
|
||||
public GitHub getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
public String getTagName() {
|
||||
return tag_name;
|
||||
}
|
||||
|
||||
public String getTargetCommitish() {
|
||||
return target_commitish;
|
||||
}
|
||||
|
||||
public String getUploadUrl() {
|
||||
return upload_url;
|
||||
}
|
||||
|
||||
public String getZipballUrl() {
|
||||
return zipball_url;
|
||||
}
|
||||
|
||||
public String getTarballUrl() {
|
||||
return tarball_url;
|
||||
}
|
||||
|
||||
GHRelease wrap(GHRepository owner) {
|
||||
this.owner = owner;
|
||||
this.root = owner.root;
|
||||
return this;
|
||||
}
|
||||
|
||||
static GHRelease[] wrap(GHRelease[] releases, GHRepository owner) {
|
||||
for (GHRelease release : releases) {
|
||||
release.wrap(owner);
|
||||
}
|
||||
return releases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Because github relies on SNI (http://en.wikipedia.org/wiki/Server_Name_Indication) this method will only work on
|
||||
* Java 7 or greater. Options for fixing this for earlier JVMs can be found here
|
||||
* http://stackoverflow.com/questions/12361090/server-name-indication-sni-on-java but involve more complicated
|
||||
* handling of the HTTP requests to github's API.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public GHAsset uploadAsset(File file, String contentType) throws IOException {
|
||||
Requester builder = new Requester(owner.root);
|
||||
|
||||
String url = format("https://uploads.github.com%s/releases/%d/assets?name=%s",
|
||||
owner.getApiTailUrl(""), getId(), file.getName());
|
||||
return builder.contentType(contentType)
|
||||
.with(new FileInputStream(file))
|
||||
.to(url, GHAsset.class).wrap(this);
|
||||
}
|
||||
|
||||
public List<GHAsset> getAssets() throws IOException {
|
||||
Requester builder = new Requester(owner.root);
|
||||
|
||||
GHAsset[] assets = builder
|
||||
.method("GET")
|
||||
.to(getApiTailUrl("assets"), GHAsset[].class);
|
||||
return Arrays.asList(GHAsset.wrap(assets, this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this release.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
new Requester(root).method("DELETE").to(owner.getApiTailUrl("releases/"+id));
|
||||
}
|
||||
|
||||
private String getApiTailUrl(String end) {
|
||||
return owner.getApiTailUrl(format("releases/%s/%s",id,end));
|
||||
}
|
||||
}
|
||||
80
src/main/java/org/kohsuke/github/GHReleaseBuilder.java
Normal file
80
src/main/java/org/kohsuke/github/GHReleaseBuilder.java
Normal file
@@ -0,0 +1,80 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Builder pattern for creating a {@link GHRelease}
|
||||
*
|
||||
* @see GHRepository#createRelease(String)
|
||||
*/
|
||||
public class GHReleaseBuilder {
|
||||
private final GHRepository repo;
|
||||
private final Requester builder;
|
||||
|
||||
public GHReleaseBuilder(GHRepository ghRepository, String tag) {
|
||||
this.repo = ghRepository;
|
||||
this.builder = new Requester(repo.root);
|
||||
builder.with("tag_name", tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param body The release notes body.
|
||||
*/
|
||||
public GHReleaseBuilder body(String body) {
|
||||
if (body != null) {
|
||||
builder.with("body", body);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the commitish value that determines where the Git tag is created from. Can be any branch or
|
||||
* commit SHA.
|
||||
*
|
||||
* @param commitish Defaults to the repository’s default branch (usually "master"). Unused if the Git tag
|
||||
* already exists.
|
||||
* @return
|
||||
*/
|
||||
public GHReleaseBuilder commitish(String commitish) {
|
||||
if (commitish != null) {
|
||||
builder.with("target_commitish", commitish);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional.
|
||||
*
|
||||
* @param draft {@code true} to create a draft (unpublished) release, {@code false} to create a published one.
|
||||
* Default is {@code false}.
|
||||
*/
|
||||
public GHReleaseBuilder draft(boolean draft) {
|
||||
builder.with("draft", draft);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name the name of the release
|
||||
*/
|
||||
public GHReleaseBuilder name(String name) {
|
||||
if (name != null) {
|
||||
builder.with("name", name);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional
|
||||
*
|
||||
* @param prerelease {@code true} to identify the release as a prerelease. {@code false} to identify the release
|
||||
* as a full release. Default is {@code false}.
|
||||
*/
|
||||
public GHReleaseBuilder prerelease(boolean prerelease) {
|
||||
builder.with("prerelease", prerelease);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHRelease create() throws IOException {
|
||||
return builder.to(repo.getApiTailUrl("releases"), GHRelease.class).wrap(repo);
|
||||
}
|
||||
}
|
||||
23
src/main/java/org/kohsuke/github/GHRepoHook.java
Normal file
23
src/main/java/org/kohsuke/github/GHRepoHook.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
class GHRepoHook extends GHHook {
|
||||
/**
|
||||
* Repository that the hook belongs to.
|
||||
*/
|
||||
/*package*/ transient GHRepository repository;
|
||||
|
||||
/*package*/ GHRepoHook wrap(GHRepository owner) {
|
||||
this.repository = owner;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
GitHub getRoot() {
|
||||
return repository.root;
|
||||
}
|
||||
|
||||
@Override
|
||||
String getApiRoute() {
|
||||
return String.format("/repos/%s/%s/hooks/%d", repository.getOwnerName(), repository.getName(), id);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,82 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Search repositories.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GitHub#searchRepositories()
|
||||
*/
|
||||
public class GHRepositorySearchBuilder extends GHSearchBuilder<GHRepository> {
|
||||
/*package*/ GHRepositorySearchBuilder(GitHub root) {
|
||||
super(root,RepositorySearchResult.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search terms.
|
||||
*/
|
||||
public GHRepositorySearchBuilder q(String term) {
|
||||
super.q(term);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHRepositorySearchBuilder in(String v) {
|
||||
return q("in:"+v);
|
||||
}
|
||||
|
||||
public GHRepositorySearchBuilder size(String v) {
|
||||
return q("size:"+v);
|
||||
}
|
||||
|
||||
public GHRepositorySearchBuilder forks(String v) {
|
||||
return q("forks:"+v);
|
||||
}
|
||||
|
||||
public GHRepositorySearchBuilder created(String v) {
|
||||
return q("created:"+v);
|
||||
}
|
||||
|
||||
public GHRepositorySearchBuilder pushed(String v) {
|
||||
return q("pushed:"+v);
|
||||
}
|
||||
|
||||
public GHRepositorySearchBuilder user(String v) {
|
||||
return q("user:"+v);
|
||||
}
|
||||
|
||||
public GHRepositorySearchBuilder repo(String v) {
|
||||
return q("repo:"+v);
|
||||
}
|
||||
|
||||
public GHRepositorySearchBuilder language(String v) {
|
||||
return q("language:"+v);
|
||||
}
|
||||
|
||||
public GHRepositorySearchBuilder stars(String v) {
|
||||
return q("stars:"+v);
|
||||
}
|
||||
|
||||
public GHRepositorySearchBuilder sort(Sort sort) {
|
||||
req.with("sort",sort.toString().toLowerCase(Locale.ENGLISH));
|
||||
return this;
|
||||
}
|
||||
|
||||
public enum Sort { STARS, FORKS, UPDATED }
|
||||
|
||||
private static class RepositorySearchResult extends SearchResult<GHRepository> {
|
||||
private GHRepository[] items;
|
||||
|
||||
@Override
|
||||
/*package*/ GHRepository[] getItems(GitHub root) {
|
||||
for (GHRepository item : items)
|
||||
item.wrap(root);
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getApiUrl() {
|
||||
return "/search/repositories";
|
||||
}
|
||||
}
|
||||
54
src/main/java/org/kohsuke/github/GHSearchBuilder.java
Normal file
54
src/main/java/org/kohsuke/github/GHSearchBuilder.java
Normal file
@@ -0,0 +1,54 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Base class for various search builders.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public abstract class GHSearchBuilder<T> {
|
||||
protected final GitHub root;
|
||||
protected final Requester req;
|
||||
protected final List<String> terms = new ArrayList<String>();
|
||||
|
||||
/**
|
||||
* Data transfer object that receives the result of search.
|
||||
*/
|
||||
private final Class<? extends SearchResult<T>> receiverType;
|
||||
|
||||
/*package*/ GHSearchBuilder(GitHub root, Class<? extends SearchResult<T>> receiverType) {
|
||||
this.root = root;
|
||||
this.req = root.retrieve();
|
||||
this.receiverType = receiverType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search terms.
|
||||
*/
|
||||
public GHSearchBuilder q(String term) {
|
||||
terms.add(term);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the search.
|
||||
*/
|
||||
public PagedSearchIterable<T> list() {
|
||||
return new PagedSearchIterable<T>(root) {
|
||||
public PagedIterator<T> iterator() {
|
||||
req.set("q", StringUtils.join(terms, " "));
|
||||
return new PagedIterator<T>(adapt(req.asIterator(getApiUrl(), receiverType))) {
|
||||
protected void wrapUp(T[] page) {
|
||||
// SearchResult.getItems() should do it
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected abstract String getApiUrl();
|
||||
}
|
||||
64
src/main/java/org/kohsuke/github/GHSubscription.java
Normal file
64
src/main/java/org/kohsuke/github/GHSubscription.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Represents your subscribing to a repository / conversation thread..
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GHRepository#getSubscription()
|
||||
* @see GHThread#getSubscription()
|
||||
*/
|
||||
public class GHSubscription {
|
||||
private String created_at, url, repository_url, reason;
|
||||
private boolean subscribed, ignored;
|
||||
|
||||
private GitHub root;
|
||||
private GHRepository repo;
|
||||
|
||||
public Date getCreatedAt() {
|
||||
return GitHub.parseDate(created_at);
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getRepositoryUrl() {
|
||||
return repository_url;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
public boolean isSubscribed() {
|
||||
return subscribed;
|
||||
}
|
||||
|
||||
public boolean isIgnored() {
|
||||
return ignored;
|
||||
}
|
||||
|
||||
public GHRepository getRepository() {
|
||||
return repo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes this subscription.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
new Requester(root).method("DELETE").to(url);
|
||||
}
|
||||
|
||||
GHSubscription wrapUp(GHRepository repo) {
|
||||
this.repo = repo;
|
||||
return wrapUp(repo.root);
|
||||
}
|
||||
|
||||
GHSubscription wrapUp(GitHub root) {
|
||||
this.root = root;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
38
src/main/java/org/kohsuke/github/GHTag.java
Normal file
38
src/main/java/org/kohsuke/github/GHTag.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
/**
|
||||
* Represents a tag in {@link GHRepository}
|
||||
*
|
||||
* @see GHRepository#listTags()
|
||||
*/
|
||||
public class GHTag {
|
||||
private GHRepository owner;
|
||||
private GitHub root;
|
||||
|
||||
private String name;
|
||||
private GHCommit commit;
|
||||
|
||||
/*package*/ GHTag wrap(GHRepository owner) {
|
||||
this.owner = owner;
|
||||
this.root = owner.root;
|
||||
if (commit!=null)
|
||||
commit.wrapUp(owner);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHRepository getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public GitHub getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public GHCommit getCommit() {
|
||||
return commit;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* A team in GitHub organization.
|
||||
@@ -12,9 +14,27 @@ import java.util.Set;
|
||||
public class GHTeam {
|
||||
private String name,permission;
|
||||
private int id;
|
||||
private GHOrganization organization; // populated by GET /user/teams where Teams+Orgs are returned together
|
||||
|
||||
protected /*final*/ GHOrganization org;
|
||||
|
||||
/*package*/ GHTeam wrapUp(GHOrganization owner) {
|
||||
this.org = owner;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*package*/ GHTeam wrapUp(GitHub root) { // auto-wrapUp when organization is known from GET /user/teams
|
||||
this.organization.wrapUp(root);
|
||||
return wrapUp(organization);
|
||||
}
|
||||
|
||||
/*package*/ static GHTeam[] wrapUp(GHTeam[] teams, GHOrganization owner) {
|
||||
for (GHTeam t : teams) {
|
||||
t.wrapUp(owner);
|
||||
}
|
||||
return teams;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
@@ -30,37 +50,88 @@ public class GHTeam {
|
||||
/**
|
||||
* Retrieves the current members.
|
||||
*/
|
||||
public PagedIterable<GHUser> listMembers() throws IOException {
|
||||
return new PagedIterable<GHUser>() {
|
||||
public PagedIterator<GHUser> iterator() {
|
||||
return new PagedIterator<GHUser>(org.root.retrieve().asIterator(api("/members"), GHUser[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHUser[] page) {
|
||||
GHUser.wrap(page, org.root);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Set<GHUser> getMembers() throws IOException {
|
||||
return org.root.retrieveWithAuth(api("/members"),JsonUsersWithDetails.class).toSet(org.root);
|
||||
return Collections.unmodifiableSet(listMembers().asSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this team has the specified user as a member.
|
||||
*/
|
||||
public boolean hasMember(GHUser user) {
|
||||
try {
|
||||
org.root.retrieve().to("/teams/" + id + "/members/" + user.getLogin());
|
||||
return true;
|
||||
} catch (IOException ignore) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String,GHRepository> getRepositories() throws IOException {
|
||||
return org.root.retrieveWithAuth3(api("/repos"),JsonRepositories.class).wrap(org.root);
|
||||
Map<String,GHRepository> m = new TreeMap<String, GHRepository>();
|
||||
for (GHRepository r : listRepositories()) {
|
||||
m.put(r.getName(), r);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
public PagedIterable<GHRepository> listRepositories() {
|
||||
return new PagedIterable<GHRepository>() {
|
||||
public PagedIterator<GHRepository> iterator() {
|
||||
return new PagedIterator<GHRepository>(org.root.retrieve().asIterator(api("/repos"), GHRepository[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHRepository[] page) {
|
||||
for (GHRepository r : page)
|
||||
r.wrap(org.root);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a member to the team.
|
||||
*
|
||||
* The user will be invited to the organization if required.
|
||||
*
|
||||
* @since 1.59
|
||||
*/
|
||||
public void add(GHUser u) throws IOException {
|
||||
org.root.retrieveWithAuth(api("/members?name="+u.getLogin()),null, "POST");
|
||||
org.root.retrieve().method("PUT").to(api("/memberships/" + u.getLogin()), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a member to the team.
|
||||
*/
|
||||
public void remove(GHUser u) throws IOException {
|
||||
org.root.retrieveWithAuth(api("/members?name="+u.getLogin()),null, "DELETE");
|
||||
org.root.retrieve().method("DELETE").to(api("/members/" + u.getLogin()), null);
|
||||
}
|
||||
|
||||
public void add(GHRepository r) throws IOException {
|
||||
org.root.retrieveWithAuth(api("/repositories?name="+r.getOwnerName()+'/'+r.getName()),null, "POST");
|
||||
org.root.retrieve().method("PUT").to(api("/repos/" + r.getOwnerName() + '/' + r.getName()), null);
|
||||
}
|
||||
|
||||
public void remove(GHRepository r) throws IOException {
|
||||
org.root.retrieveWithAuth(api("/repositories?name="+r.getOwnerName()+'/'+r.getName()),null, "DELETE");
|
||||
org.root.retrieve().method("DELETE").to(api("/repos/" + r.getOwnerName() + '/' + r.getName()), null);
|
||||
}
|
||||
|
||||
private String api(String tail) {
|
||||
return "/teams/"+id+tail;
|
||||
}
|
||||
|
||||
public GHOrganization getOrganization() {
|
||||
return org;
|
||||
}
|
||||
}
|
||||
|
||||
142
src/main/java/org/kohsuke/github/GHThread.java
Normal file
142
src/main/java/org/kohsuke/github/GHThread.java
Normal file
@@ -0,0 +1,142 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A conversation in the notification API.
|
||||
*
|
||||
* @see <a href="https://developer.github.com/v3/activity/notifications/">documentation</a>
|
||||
* @see GHNotificationStream
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class GHThread extends GHObject {
|
||||
private GitHub root;
|
||||
private GHRepository repository;
|
||||
private Subject subject;
|
||||
private String reason;
|
||||
private boolean unread;
|
||||
private String last_read_at;
|
||||
private String url,subscription_url;
|
||||
|
||||
static class Subject {
|
||||
String title;
|
||||
String url;
|
||||
String latest_comment_url;
|
||||
String type;
|
||||
}
|
||||
|
||||
private GHThread() {// no external construction allowed
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null if the entire thread has never been read.
|
||||
*/
|
||||
public Date getLastReadAt() {
|
||||
return GitHub.parseDate(last_read_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This object has no HTML URL.
|
||||
*/
|
||||
@Override
|
||||
public URL getHtmlUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
public GHRepository getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
// TODO: how to expose the subject?
|
||||
|
||||
public boolean isRead() {
|
||||
return !unread;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return subject.title;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return subject.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this thread is about an issue, return that issue.
|
||||
*
|
||||
* @return null if this thread is not about an issue.
|
||||
*/
|
||||
public GHIssue getBoundIssue() throws IOException {
|
||||
if (!"Issue".equals(subject.type) && "PullRequest".equals(subject.type))
|
||||
return null;
|
||||
return repository.getIssue(
|
||||
Integer.parseInt(subject.url.substring(subject.url.lastIndexOf('/') + 1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* If this thread is about a pull request, return that pull request.
|
||||
*
|
||||
* @return null if this thread is not about a pull request.
|
||||
*/
|
||||
public GHPullRequest getBoundPullRequest() throws IOException {
|
||||
if (!"PullRequest".equals(subject.type))
|
||||
return null;
|
||||
return repository.getPullRequest(
|
||||
Integer.parseInt(subject.url.substring(subject.url.lastIndexOf('/') + 1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* If this thread is about a commit, return that commit.
|
||||
*
|
||||
* @return null if this thread is not about a commit.
|
||||
*/
|
||||
public GHCommit getBoundCommit() throws IOException {
|
||||
if (!"Commit".equals(subject.type))
|
||||
return null;
|
||||
return repository.getCommit(subject.url.substring(subject.url.lastIndexOf('/') + 1));
|
||||
}
|
||||
|
||||
/*package*/ GHThread wrap(GitHub root) {
|
||||
this.root = root;
|
||||
if (this.repository!=null)
|
||||
this.repository.wrap(root);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks this thread as read.
|
||||
*/
|
||||
public void markAsRead() throws IOException {
|
||||
new Requester(root).method("PATCH").to(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to this conversation to get notifications.
|
||||
*/
|
||||
public GHSubscription subscribe(boolean subscribed, boolean ignored) throws IOException {
|
||||
return new Requester(root)
|
||||
.with("subscribed", subscribed)
|
||||
.with("ignored", ignored)
|
||||
.method("PUT").to(subscription_url, GHSubscription.class).wrapUp(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current subscription for this thread.
|
||||
*
|
||||
* @return null if no subscription exists.
|
||||
*/
|
||||
public GHSubscription getSubscription() throws IOException {
|
||||
try {
|
||||
return new Requester(root).to(subscription_url, GHSubscription.class).wrapUp(root);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
58
src/main/java/org/kohsuke/github/GHTree.java
Normal file
58
src/main/java/org/kohsuke/github/GHTree.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides information for Git Trees
|
||||
* https://developer.github.com/v3/git/trees/
|
||||
*
|
||||
* @author Daniel Teixeira - https://github.com/ddtxra
|
||||
* @see GHRepository#getTree(String)
|
||||
*/
|
||||
public class GHTree {
|
||||
/* package almost final */GitHub root;
|
||||
|
||||
private boolean truncated;
|
||||
private String sha, url;
|
||||
private GHTreeEntry[] tree;
|
||||
|
||||
/**
|
||||
* The SHA for this trees
|
||||
*/
|
||||
public String getSha() {
|
||||
return sha;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of entries of the trees
|
||||
* @return
|
||||
*/
|
||||
public List<GHTreeEntry> getTree() {
|
||||
return Collections.unmodifiableList(Arrays.asList(tree));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the number of items in the tree array exceeded the GitHub maximum limit.
|
||||
* @return true true if the number of items in the tree array exceeded the GitHub maximum limit otherwise false.
|
||||
*/
|
||||
public boolean isTruncated() {
|
||||
return truncated;
|
||||
}
|
||||
|
||||
/**
|
||||
* The API URL of this tag, such as
|
||||
* "url": "https://api.github.com/repos/octocat/Hello-World/trees/fc6274d15fa3ae2ab983129fb037999f264ba9a7",
|
||||
*/
|
||||
public URL getUrl() {
|
||||
return GitHub.parseURL(url);
|
||||
}
|
||||
|
||||
/* package */GHTree wrap(GitHub root) {
|
||||
this.root = root;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
71
src/main/java/org/kohsuke/github/GHTreeEntry.java
Normal file
71
src/main/java/org/kohsuke/github/GHTreeEntry.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Provides information for Git Trees
|
||||
* https://developer.github.com/v3/git/trees/
|
||||
*
|
||||
* @author Daniel Teixeira - https://github.com/ddtxra
|
||||
* @see GHTree
|
||||
*/
|
||||
public class GHTreeEntry {
|
||||
private String path, mode, type, sha, url;
|
||||
private long size;
|
||||
|
||||
/**
|
||||
* Get the path such as
|
||||
* "subdir/file.txt"
|
||||
*
|
||||
* @return the path
|
||||
*/
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mode such as
|
||||
* 100644
|
||||
*
|
||||
* @return the mode
|
||||
*/
|
||||
public String getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of the file, such as
|
||||
* 132
|
||||
* @return The size of the path or 0 if it is a directory
|
||||
*/
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type such as:
|
||||
* "blob"
|
||||
*
|
||||
* @return The type
|
||||
*/
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* SHA1 of this object.
|
||||
*/
|
||||
public String getSha() {
|
||||
return sha;
|
||||
}
|
||||
|
||||
/**
|
||||
* API URL to this Git data, such as
|
||||
* https://api.github.com/repos/jenkinsci
|
||||
* /jenkins/git/commits/b72322675eb0114363a9a86e9ad5a170d1d07ac0
|
||||
*/
|
||||
public URL getUrl() {
|
||||
return GitHub.parseURL(url);
|
||||
}
|
||||
}
|
||||
@@ -23,12 +23,11 @@
|
||||
*/
|
||||
package org.kohsuke.github;
|
||||
|
||||
import com.infradna.tool.bridge_method_injector.BridgeMethodsAdded;
|
||||
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -42,14 +41,14 @@ public class GHUser extends GHPerson {
|
||||
* Follow this user.
|
||||
*/
|
||||
public void follow() throws IOException {
|
||||
new Poster(root).withCredential().to("/user/follow/"+login);
|
||||
new Requester(root).method("PUT").to("/user/following/" + login);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfollow this user.
|
||||
*/
|
||||
public void unfollow() throws IOException {
|
||||
new Poster(root).withCredential().to("/user/unfollow/"+login);
|
||||
new Requester(root).method("DELETE").to("/user/following/" + login);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +56,8 @@ public class GHUser extends GHPerson {
|
||||
*/
|
||||
@WithBridgeMethods(Set.class)
|
||||
public GHPersonSet<GHUser> getFollows() throws IOException {
|
||||
return root.retrieve("/user/show/"+login+"/following",JsonUsers.class).toSet(root);
|
||||
GHUser[] followers = root.retrieve().to("/users/" + login + "/following", GHUser[].class);
|
||||
return new GHPersonSet<GHUser>(Arrays.asList(wrap(followers,root)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,7 +65,53 @@ public class GHUser extends GHPerson {
|
||||
*/
|
||||
@WithBridgeMethods(Set.class)
|
||||
public GHPersonSet<GHUser> getFollowers() throws IOException {
|
||||
return root.retrieve("/user/show/"+login+"/followers",JsonUsers.class).toSet(root);
|
||||
GHUser[] followers = root.retrieve().to("/users/" + login + "/followers", GHUser[].class);
|
||||
return new GHPersonSet<GHUser>(Arrays.asList(wrap(followers,root)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all the subscribed (aka watched) repositories.
|
||||
*
|
||||
* https://developer.github.com/v3/activity/watching/
|
||||
*/
|
||||
public PagedIterable<GHRepository> listSubscriptions() {
|
||||
return new PagedIterable<GHRepository>() {
|
||||
public PagedIterator<GHRepository> iterator() {
|
||||
return new PagedIterator<GHRepository>(root.retrieve().asIterator(getApiTailUrl("subscriptions"), GHRepository[].class)) {
|
||||
protected void wrapUp(GHRepository[] page) {
|
||||
for (GHRepository c : page)
|
||||
c.wrap(root);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this user belongs to the specified organization.
|
||||
*/
|
||||
public boolean isMemberOf(GHOrganization org) {
|
||||
return org.hasMember(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this user belongs to the specified team.
|
||||
*/
|
||||
public boolean isMemberOf(GHTeam team) {
|
||||
return team.hasMember(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this user belongs to the specified organization as a public member.
|
||||
*/
|
||||
public boolean isPublicMemberOf(GHOrganization org) {
|
||||
return org.hasPublicMember(this);
|
||||
}
|
||||
|
||||
/*package*/ static GHUser[] wrap(GHUser[] users, GitHub root) {
|
||||
for (GHUser f : users)
|
||||
f.root = root;
|
||||
return users;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,13 +121,47 @@ public class GHUser extends GHPerson {
|
||||
public GHPersonSet<GHOrganization> getOrganizations() throws IOException {
|
||||
GHPersonSet<GHOrganization> orgs = new GHPersonSet<GHOrganization>();
|
||||
Set<String> names = new HashSet<String>();
|
||||
for (GHOrganization o : root.retrieve3("/users/"+login+"/orgs",GHOrganization[].class)) {
|
||||
for (GHOrganization o : root.retrieve().to("/users/" + login + "/orgs", GHOrganization[].class)) {
|
||||
if (names.add(o.getLogin())) // I've seen some duplicates in the data
|
||||
orgs.add(root.getOrganization(o.getLogin()));
|
||||
}
|
||||
return orgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists events performed by a user (this includes private events if the caller is authenticated.
|
||||
*/
|
||||
public PagedIterable<GHEventInfo> listEvents() throws IOException {
|
||||
return new PagedIterable<GHEventInfo>() {
|
||||
public PagedIterator<GHEventInfo> iterator() {
|
||||
return new PagedIterator<GHEventInfo>(root.retrieve().asIterator(String.format("/users/%s/events", login), GHEventInfo[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHEventInfo[] page) {
|
||||
for (GHEventInfo c : page)
|
||||
c.wrapUp(root);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists Gists created by this user.
|
||||
*/
|
||||
public PagedIterable<GHGist> listGists() throws IOException {
|
||||
return new PagedIterable<GHGist>() {
|
||||
public PagedIterator<GHGist> iterator() {
|
||||
return new PagedIterator<GHGist>(root.retrieve().asIterator(String.format("/users/%s/gists", login), GHGist[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHGist[] page) {
|
||||
for (GHGist c : page)
|
||||
c.wrapUp(GHUser.this);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User:"+login;
|
||||
@@ -100,4 +180,9 @@ public class GHUser extends GHPerson {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String getApiTailUrl(String tail) {
|
||||
if (tail.length()>0 && !tail.startsWith("/")) tail='/'+tail;
|
||||
return "/users/" + login + tail;
|
||||
}
|
||||
}
|
||||
|
||||
72
src/main/java/org/kohsuke/github/GHUserSearchBuilder.java
Normal file
72
src/main/java/org/kohsuke/github/GHUserSearchBuilder.java
Normal file
@@ -0,0 +1,72 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Search users.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GitHub#searchUsers()
|
||||
*/
|
||||
public class GHUserSearchBuilder extends GHSearchBuilder<GHUser> {
|
||||
/*package*/ GHUserSearchBuilder(GitHub root) {
|
||||
super(root,UserSearchResult.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search terms.
|
||||
*/
|
||||
public GHUserSearchBuilder q(String term) {
|
||||
super.q(term);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHUserSearchBuilder type(String v) {
|
||||
return q("type:"+v);
|
||||
}
|
||||
|
||||
public GHUserSearchBuilder in(String v) {
|
||||
return q("in:"+v);
|
||||
}
|
||||
|
||||
public GHUserSearchBuilder repos(String v) {
|
||||
return q("repos:"+v);
|
||||
}
|
||||
|
||||
public GHUserSearchBuilder location(String v) {
|
||||
return q("location:"+v);
|
||||
}
|
||||
|
||||
public GHUserSearchBuilder language(String v) {
|
||||
return q("language:"+v);
|
||||
}
|
||||
|
||||
public GHUserSearchBuilder created(String v) {
|
||||
return q("created:"+v);
|
||||
}
|
||||
|
||||
public GHUserSearchBuilder followers(String v) {
|
||||
return q("followers:"+v);
|
||||
}
|
||||
|
||||
public GHUserSearchBuilder sort(Sort sort) {
|
||||
req.with("sort",sort.toString().toLowerCase(Locale.ENGLISH));
|
||||
return this;
|
||||
}
|
||||
|
||||
public enum Sort { FOLLOWERS, REPOSITORIES, JOINED }
|
||||
|
||||
private static class UserSearchResult extends SearchResult<GHUser> {
|
||||
private GHUser[] items;
|
||||
|
||||
@Override
|
||||
/*package*/ GHUser[] getItems(GitHub root) {
|
||||
return GHUser.wrap(items,root);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getApiUrl() {
|
||||
return "/search/users";
|
||||
}
|
||||
}
|
||||
13
src/main/java/org/kohsuke/github/GHVerifiedKey.java
Normal file
13
src/main/java/org/kohsuke/github/GHVerifiedKey.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
public class GHVerifiedKey extends GHKey {
|
||||
|
||||
public GHVerifiedKey() {
|
||||
this.verified = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return (title == null ? "key-" + id : title);
|
||||
}
|
||||
}
|
||||
@@ -23,238 +23,273 @@
|
||||
*/
|
||||
package org.kohsuke.github;
|
||||
|
||||
import com.gargoylesoftware.htmlunit.WebClient;
|
||||
import com.gargoylesoftware.htmlunit.html.HtmlForm;
|
||||
import com.gargoylesoftware.htmlunit.html.HtmlPage;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.codehaus.jackson.map.DeserializationConfig.Feature;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.map.introspect.VisibilityChecker.Std;
|
||||
import sun.misc.BASE64Encoder;
|
||||
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;
|
||||
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.io.Reader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.codehaus.jackson.annotate.JsonAutoDetect.Visibility.*;
|
||||
import static org.kohsuke.github.ApiVersion.*;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.introspect.VisibilityChecker.Std;
|
||||
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
|
||||
|
||||
/**
|
||||
* Root of the GitHub API.
|
||||
*
|
||||
* <h2>Thread safety</h2>
|
||||
* <p>
|
||||
* This library aims to be safe for use by multiple threads concurrently, although
|
||||
* the library itself makes no attempt to control/serialize potentially conflicting
|
||||
* operations to GitHub, such as updating & deleting a repository at the same time.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class GitHub {
|
||||
/*package*/ final String login;
|
||||
/*package*/ final String encodedAuthorization;
|
||||
/*package*/ final String password;
|
||||
/*package*/ final String apiToken;
|
||||
|
||||
private final Map<String,GHUser> users = new HashMap<String, GHUser>();
|
||||
private final Map<String,GHOrganization> orgs = new HashMap<String, GHOrganization>();
|
||||
/*package*/ String oauthAccessToken;
|
||||
|
||||
private GitHub(String login, String apiToken, String password) {
|
||||
this.login = login;
|
||||
this.apiToken = apiToken;
|
||||
this.password = password;
|
||||
|
||||
BASE64Encoder enc = new sun.misc.BASE64Encoder();
|
||||
if (apiToken!=null || password!=null) {
|
||||
String userpassword = password==null ? (login + "/token" + ":" + apiToken) : (login + ':'+password);
|
||||
encodedAuthorization = enc.encode(userpassword.getBytes());
|
||||
} else
|
||||
encodedAuthorization = null;
|
||||
}
|
||||
|
||||
private GitHub (String oauthAccessToken) throws IOException {
|
||||
|
||||
this.password = null;
|
||||
this.encodedAuthorization = null;
|
||||
|
||||
this.oauthAccessToken = oauthAccessToken;
|
||||
this.apiToken = oauthAccessToken;
|
||||
|
||||
this.login = getMyself().getLogin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the credential from "~/.github"
|
||||
* Value of the authorization header to be sent with the request.
|
||||
*/
|
||||
/*package*/ final String encodedAuthorization;
|
||||
|
||||
private final Map<String,GHUser> users = new Hashtable<String, GHUser>();
|
||||
private final Map<String,GHOrganization> orgs = new Hashtable<String, GHOrganization>();
|
||||
|
||||
private final String apiUrl;
|
||||
|
||||
/*package*/ final RateLimitHandler rateLimitHandler;
|
||||
|
||||
private HttpConnector connector = HttpConnector.DEFAULT;
|
||||
|
||||
/**
|
||||
* Creates a client API root object.
|
||||
*
|
||||
* <p>
|
||||
* Several different combinations of the login/oauthAccessToken/password parameters are allowed
|
||||
* to represent different ways of authentication.
|
||||
*
|
||||
* <dl>
|
||||
* <dt>Loging anonymously
|
||||
* <dd>Leave all three parameters null and you will be making HTTP requests without any authentication.
|
||||
*
|
||||
* <dt>Log in with password
|
||||
* <dd>Specify the login and password, then leave oauthAccessToken null.
|
||||
* This will use the HTTP BASIC auth with the GitHub API.
|
||||
*
|
||||
* <dt>Log in with OAuth token
|
||||
* <dd>Specify oauthAccessToken, and optionally specify the login. Leave password null.
|
||||
* This will send OAuth token to the GitHub API. If the login parameter is null,
|
||||
* The constructor makes an API call to figure out the user name that owns the token.
|
||||
* </dl>
|
||||
*
|
||||
* @param apiUrl
|
||||
* The URL of GitHub (or GitHub enterprise) API endpoint, such as "https://api.github.com" or
|
||||
* "http://ghe.acme.com/api/v3". Note that GitHub Enterprise has <tt>/api/v3</tt> in the URL.
|
||||
* For historical reasons, this parameter still accepts the bare domain name, but that's considered deprecated.
|
||||
* Password is also considered deprecated as it is no longer required for api usage.
|
||||
* @param login
|
||||
* The use ID on GitHub that you are logging in as. Can be omitted if the OAuth token is
|
||||
* provided or if logging in anonymously. Specifying this would save one API call.
|
||||
* @param oauthAccessToken
|
||||
* Secret OAuth token.
|
||||
* @param password
|
||||
* User's password. Always used in conjunction with the {@code login} parameter
|
||||
* @param connector
|
||||
* HttpConnector to use. Pass null to use default connector.
|
||||
*/
|
||||
/* package */ GitHub(String apiUrl, String login, String oauthAccessToken, String password, HttpConnector connector, RateLimitHandler rateLimitHandler) throws IOException {
|
||||
if (apiUrl.endsWith("/")) apiUrl = apiUrl.substring(0, apiUrl.length()-1); // normalize
|
||||
this.apiUrl = apiUrl;
|
||||
if (null != connector) this.connector = connector;
|
||||
|
||||
if (oauthAccessToken!=null) {
|
||||
encodedAuthorization = "token "+oauthAccessToken;
|
||||
} else {
|
||||
if (password!=null) {
|
||||
String authorization = (login + ':' + password);
|
||||
encodedAuthorization = "Basic "+new String(Base64.encodeBase64(authorization.getBytes()));
|
||||
} else {// anonymous access
|
||||
encodedAuthorization = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.rateLimitHandler = rateLimitHandler;
|
||||
|
||||
if (login==null && encodedAuthorization!=null)
|
||||
login = getMyself().getLogin();
|
||||
this.login = login;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the credential from "~/.github" or from the System Environment Properties.
|
||||
*/
|
||||
public static GitHub connect() throws IOException {
|
||||
Properties props = new Properties();
|
||||
File homeDir = new File(System.getProperty("user.home"));
|
||||
FileInputStream in = new FileInputStream(new File(homeDir, ".github"));
|
||||
try {
|
||||
props.load(in);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(in);
|
||||
}
|
||||
return new GitHub(props.getProperty("login"),props.getProperty("token"),props.getProperty("password"));
|
||||
return GitHubBuilder.fromCredentials().build();
|
||||
}
|
||||
|
||||
public static GitHub connect(String login, String apiToken) throws IOException {
|
||||
return new GitHub(login,apiToken,null);
|
||||
/**
|
||||
* Version that connects to GitHub Enterprise.
|
||||
*
|
||||
* @param apiUrl
|
||||
* The URL of GitHub (or GitHub enterprise) API endpoint, such as "https://api.github.com" or
|
||||
* "http://ghe.acme.com/api/v3". Note that GitHub Enterprise has <tt>/api/v3</tt> in the URL.
|
||||
* For historical reasons, this parameter still accepts the bare domain name, but that's considered deprecated.
|
||||
*/
|
||||
public static GitHub connectToEnterprise(String apiUrl, String oauthAccessToken) throws IOException {
|
||||
return new GitHubBuilder().withEndpoint(apiUrl).withOAuthToken(oauthAccessToken).build();
|
||||
}
|
||||
|
||||
public static GitHub connect(String login, String apiToken, String password) throws IOException {
|
||||
return new GitHub(login,apiToken,password);
|
||||
public static GitHub connectToEnterprise(String apiUrl, String login, String password) throws IOException {
|
||||
return new GitHubBuilder().withEndpoint(apiUrl).withPassword(login, password).build();
|
||||
}
|
||||
|
||||
public static GitHub connectUsingOAuth (String accessToken) throws IOException {
|
||||
return new GitHub(accessToken);
|
||||
public static GitHub connect(String login, String oauthAccessToken) throws IOException {
|
||||
return new GitHubBuilder().withOAuthToken(oauthAccessToken, login).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Either OAuth token or password is sufficient, so there's no point in passing both.
|
||||
* Use {@link #connectUsingPassword(String, String)} or {@link #connectUsingOAuth(String)}.
|
||||
*/
|
||||
public static GitHub connect(String login, String oauthAccessToken, String password) throws IOException {
|
||||
return new GitHubBuilder().withOAuthToken(oauthAccessToken, login).withPassword(login, password).build();
|
||||
}
|
||||
|
||||
public static GitHub connectUsingPassword(String login, String password) throws IOException {
|
||||
return new GitHubBuilder().withPassword(login, password).build();
|
||||
}
|
||||
|
||||
public static GitHub connectUsingOAuth(String oauthAccessToken) throws IOException {
|
||||
return new GitHubBuilder().withOAuthToken(oauthAccessToken).build();
|
||||
}
|
||||
|
||||
public static GitHub connectUsingOAuth(String githubServer, String oauthAccessToken) throws IOException {
|
||||
return new GitHubBuilder().withEndpoint(githubServer).withOAuthToken(oauthAccessToken).build();
|
||||
}
|
||||
/**
|
||||
* Connects to GitHub anonymously.
|
||||
*
|
||||
* All operations that requires authentication will fail.
|
||||
*/
|
||||
public static GitHub connectAnonymously() {
|
||||
return new GitHub(null,null,null);
|
||||
public static GitHub connectAnonymously() throws IOException {
|
||||
return new GitHubBuilder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this an anonymous connection
|
||||
* @return {@code true} if operations that require authentication will fail.
|
||||
*/
|
||||
public boolean isAnonymous() {
|
||||
return login==null && encodedAuthorization==null;
|
||||
}
|
||||
|
||||
public HttpConnector getConnector() {
|
||||
return connector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom connector used to make requests to GitHub.
|
||||
*/
|
||||
public void setConnector(HttpConnector connector) {
|
||||
this.connector = connector;
|
||||
}
|
||||
|
||||
/*package*/ void requireCredential() {
|
||||
if ((login==null || encodedAuthorization==null) && oauthAccessToken == null)
|
||||
if (isAnonymous())
|
||||
throw new IllegalStateException("This operation requires a credential but none is given to the GitHub constructor");
|
||||
}
|
||||
|
||||
/*package*/ URL getApiURL(ApiVersion v, String tailApiUrl) throws IOException {
|
||||
if (oauthAccessToken != null) {
|
||||
// append the access token
|
||||
tailApiUrl = tailApiUrl + (tailApiUrl.indexOf('?')>=0 ?'&':'?') + "access_token=" + oauthAccessToken;
|
||||
}
|
||||
|
||||
return new URL(v.url+tailApiUrl);
|
||||
}
|
||||
|
||||
/*package*/ <T> T retrieve(String tailApiUrl, Class<T> type) throws IOException {
|
||||
return _retrieve(tailApiUrl, type, "GET", false, V2);
|
||||
}
|
||||
|
||||
/*package*/ <T> T retrieve3(String tailApiUrl, Class<T> type) throws IOException {
|
||||
return _retrieve(tailApiUrl, type, "GET", false, V3);
|
||||
}
|
||||
|
||||
/*package*/ <T> T retrieveWithAuth(String tailApiUrl, Class<T> type) throws IOException {
|
||||
return retrieveWithAuth(tailApiUrl,type,"GET");
|
||||
}
|
||||
|
||||
/*package*/ <T> T retrieveWithAuth3(String tailApiUrl, Class<T> type) throws IOException {
|
||||
return _retrieve(tailApiUrl, type, "GET", true, V3);
|
||||
}
|
||||
|
||||
/*package*/ <T> T retrieveWithAuth(String tailApiUrl, Class<T> type, String method) throws IOException {
|
||||
return _retrieve(tailApiUrl, type, method, true, V2);
|
||||
}
|
||||
|
||||
/*package*/ <T> T retrieveWithAuth3(String tailApiUrl, Class<T> type, String method) throws IOException {
|
||||
return _retrieve(tailApiUrl, type, method, true, V3);
|
||||
}
|
||||
|
||||
private <T> T _retrieve(String tailApiUrl, Class<T> type, String method, boolean withAuth, ApiVersion v) throws IOException {
|
||||
while (true) {// loop while API rate limit is hit
|
||||
|
||||
HttpURLConnection uc = (HttpURLConnection) getApiURL(v,tailApiUrl).openConnection();
|
||||
|
||||
if (withAuth && this.oauthAccessToken == null)
|
||||
uc.setRequestProperty("Authorization", "Basic " + encodedAuthorization);
|
||||
|
||||
uc.setRequestMethod(method);
|
||||
if (method.equals("PUT")) {
|
||||
uc.setDoOutput(true);
|
||||
uc.setRequestProperty("Content-Length","0");
|
||||
uc.getOutputStream().close();
|
||||
}
|
||||
|
||||
try {
|
||||
InputStreamReader r = new InputStreamReader(uc.getInputStream(), "UTF-8");
|
||||
if (type==null) {
|
||||
String data = IOUtils.toString(r);
|
||||
return null;
|
||||
}
|
||||
return MAPPER.readValue(r,type);
|
||||
} catch (IOException e) {
|
||||
handleApiError(e,uc);
|
||||
/*package*/ URL getApiURL(String tailApiUrl) throws IOException {
|
||||
if (tailApiUrl.startsWith("/")) {
|
||||
if ("github.com".equals(apiUrl)) {// backward compatibility
|
||||
return new URL(GITHUB_URL + tailApiUrl);
|
||||
} else {
|
||||
return new URL(apiUrl + tailApiUrl);
|
||||
}
|
||||
} else {
|
||||
return new URL(tailApiUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ Requester retrieve() {
|
||||
return new Requester(this).method("GET");
|
||||
}
|
||||
|
||||
/**
|
||||
* If the error is because of the API limit, wait 10 sec and return normally.
|
||||
* Otherwise throw an exception reporting an error.
|
||||
* Gets the current rate limit.
|
||||
*/
|
||||
/*package*/ void handleApiError(IOException e, HttpURLConnection uc) throws IOException {
|
||||
if ("0".equals(uc.getHeaderField("X-RateLimit-Remaining"))) {
|
||||
// API limit reached. wait 10 secs and return normally
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
return;
|
||||
} catch (InterruptedException _) {
|
||||
throw (InterruptedIOException)new InterruptedIOException().initCause(e);
|
||||
}
|
||||
public GHRateLimit getRateLimit() throws IOException {
|
||||
try {
|
||||
return retrieve().to("/rate_limit", JsonRateLimit.class).rate;
|
||||
} catch (FileNotFoundException e) {
|
||||
// GitHub Enterprise doesn't have the rate limit, so in that case
|
||||
// return some big number that's not too big.
|
||||
// see issue #78
|
||||
GHRateLimit r = new GHRateLimit();
|
||||
r.limit = r.remaining = 1000000;
|
||||
r.reset = new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1));
|
||||
return r;
|
||||
}
|
||||
|
||||
if (e instanceof FileNotFoundException)
|
||||
throw e; // pass through 404 Not Found to allow the caller to handle it intelligently
|
||||
|
||||
InputStream es = uc.getErrorStream();
|
||||
if (es!=null)
|
||||
throw (IOException)new IOException(IOUtils.toString(es,"UTF-8")).initCause(e);
|
||||
else
|
||||
throw e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link GHUser} that represents yourself.
|
||||
*/
|
||||
public GHUser getMyself() throws IOException {
|
||||
requireCredential();
|
||||
* Gets the {@link GHUser} that represents yourself.
|
||||
*/
|
||||
@WithBridgeMethods(GHUser.class)
|
||||
public GHMyself getMyself() throws IOException {
|
||||
requireCredential();
|
||||
|
||||
if (oauthAccessToken != null) {
|
||||
GHUser u = retrieveWithAuth("/user/show", JsonUser.class).user;
|
||||
|
||||
u.root = this;
|
||||
users.put(u.getLogin(), u);
|
||||
|
||||
return u;
|
||||
} else {
|
||||
return getUser(login);
|
||||
}
|
||||
}
|
||||
GHMyself u = retrieve().to("/user", GHMyself.class);
|
||||
|
||||
/**
|
||||
* Obtains the object that represents the named user.
|
||||
*/
|
||||
public GHUser getUser(String login) throws IOException {
|
||||
GHUser u = users.get(login);
|
||||
if (u == null) {
|
||||
u.root = this;
|
||||
users.put(u.getLogin(), u);
|
||||
|
||||
if (oauthAccessToken != null) {
|
||||
u = retrieve("/user/show/" + login, JsonUser.class).user;
|
||||
u.root = this;
|
||||
users.put(u.getLogin(), u);
|
||||
} else {
|
||||
u = retrieve("/user/show/" + login, JsonUser.class).user;
|
||||
u.root = this;
|
||||
users.put(login, u);
|
||||
}
|
||||
return u;
|
||||
}
|
||||
|
||||
}
|
||||
return u;
|
||||
}
|
||||
/**
|
||||
* Obtains the object that represents the named user.
|
||||
*/
|
||||
public GHUser getUser(String login) throws IOException {
|
||||
GHUser u = users.get(login);
|
||||
if (u == null) {
|
||||
u = retrieve().to("/users/" + login, GHUser.class);
|
||||
u.root = this;
|
||||
users.put(u.getLogin(), u);
|
||||
}
|
||||
return u;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* clears all cached data in order for external changes (modifications and del
|
||||
*/
|
||||
public void refreshCache() {
|
||||
users.clear();
|
||||
orgs.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interns the given {@link GHUser}.
|
||||
@@ -263,7 +298,7 @@ public class GitHub {
|
||||
GHUser u = users.get(orig.getLogin());
|
||||
if (u==null) {
|
||||
orig.root = this;
|
||||
users.put(login,orig);
|
||||
users.put(orig.getLogin(),orig);
|
||||
return orig;
|
||||
}
|
||||
return u;
|
||||
@@ -272,28 +307,124 @@ public class GitHub {
|
||||
public GHOrganization getOrganization(String name) throws IOException {
|
||||
GHOrganization o = orgs.get(name);
|
||||
if (o==null) {
|
||||
o = retrieve("/organizations/"+name,JsonOrganization.class).organization;
|
||||
o.root = this;
|
||||
o = retrieve().to("/orgs/" + name, GHOrganization.class).wrapUp(this);
|
||||
orgs.put(name,o);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
public Map<String, GHOrganization> getMyOrganizations() throws IOException {
|
||||
return retrieveWithAuth("/organizations",JsonOrganizations.class).wrap(this);
|
||||
|
||||
/**
|
||||
* Gets the repository object from 'user/reponame' string that GitHub calls as "repository name"
|
||||
*
|
||||
* @see GHRepository#getName()
|
||||
*/
|
||||
public GHRepository getRepository(String name) throws IOException {
|
||||
String[] tokens = name.split("/");
|
||||
return retrieve().to("/repos/" + tokens[0] + '/' + tokens[1], GHRepository.class).wrap(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method returns a shallowly populated organizations.
|
||||
*
|
||||
* To retrieve full organization details, you need to call {@link #getOrganization(String)}
|
||||
* TODO: make this automatic.
|
||||
*/
|
||||
public Map<String, GHOrganization> getMyOrganizations() throws IOException {
|
||||
GHOrganization[] orgs = retrieve().to("/user/orgs", GHOrganization[].class);
|
||||
Map<String, GHOrganization> r = new HashMap<String, GHOrganization>();
|
||||
for (GHOrganization o : orgs) {
|
||||
// don't put 'o' into orgs because they are shallow
|
||||
r.put(o.getLogin(),o.wrapUp(this));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets complete map of organizations/teams that current user belongs to.
|
||||
*
|
||||
* Leverages the new GitHub API /user/teams made available recently to
|
||||
* get in a single call the complete set of organizations, teams and permissions
|
||||
* in a single call.
|
||||
*/
|
||||
public Map<String, Set<GHTeam>> getMyTeams() throws IOException {
|
||||
Map<String, Set<GHTeam>> allMyTeams = new HashMap<String, Set<GHTeam>>();
|
||||
for (GHTeam team : retrieve().to("/user/teams", GHTeam[].class)) {
|
||||
team.wrapUp(this);
|
||||
String orgLogin = team.getOrganization().getLogin();
|
||||
Set<GHTeam> teamsPerOrg = allMyTeams.get(orgLogin);
|
||||
if (teamsPerOrg == null) {
|
||||
teamsPerOrg = new HashSet<GHTeam>();
|
||||
}
|
||||
teamsPerOrg.add(team);
|
||||
allMyTeams.put(orgLogin, teamsPerOrg);
|
||||
}
|
||||
return allMyTeams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public events visible to you. Equivalent of what's displayed on https://github.com/
|
||||
*/
|
||||
public List<GHEventInfo> getEvents() throws IOException {
|
||||
GHEventInfo[] events = retrieve().to("/events", GHEventInfo[].class);
|
||||
for (GHEventInfo e : events)
|
||||
e.wrapUp(this);
|
||||
return Arrays.asList(events);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a sigle gist by ID.
|
||||
*/
|
||||
public GHGist getGist(String id) throws IOException {
|
||||
return retrieve().to("/gists/"+id,GHGist.class).wrapUp(this);
|
||||
}
|
||||
|
||||
public GHGistBuilder createGist() {
|
||||
return new GHGistBuilder(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the GitHub event object.
|
||||
*
|
||||
* This is primarily intended for receiving a POST HTTP call from a hook.
|
||||
* Unfortunately, hook script payloads aren't self-descriptive, so you need
|
||||
* to know the type of the payload you are expecting.
|
||||
*/
|
||||
public <T extends GHEventPayload> T parseEventPayload(Reader r, Class<T> type) throws IOException {
|
||||
T t = MAPPER.readValue(r, type);
|
||||
t.wrapUp(this);
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new repository.
|
||||
*
|
||||
* To create a repository in an organization, see
|
||||
* {@link GHOrganization#createRepository(String, String, String, GHTeam, boolean)}
|
||||
*
|
||||
* @return
|
||||
* Newly created repository.
|
||||
*/
|
||||
public GHRepository createRepository(String name, String description, String homepage, boolean isPublic) throws IOException {
|
||||
return new Poster(this,V3).withCredential()
|
||||
Requester requester = new Requester(this)
|
||||
.with("name", name).with("description", description).with("homepage", homepage)
|
||||
.with("public", isPublic ? 1 : 0).to("/user/repos", GHRepository.class,"POST").wrap(this);
|
||||
.with("public", isPublic ? 1 : 0);
|
||||
return requester.method("POST").to("/user/repos", GHRepository.class).wrap(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new authorization.
|
||||
*
|
||||
* The token created can be then used for {@link GitHub#connectUsingOAuth(String)} in the future.
|
||||
*
|
||||
* @see <a href="http://developer.github.com/v3/oauth/#create-a-new-authorization">Documentation</a>
|
||||
*/
|
||||
public GHAuthorization createToken(Collection<String> scope, String note, String noteUrl) throws IOException{
|
||||
Requester requester = new Requester(this)
|
||||
.with("scopes", scope)
|
||||
.with("note", note)
|
||||
.with("note_url", noteUrl);
|
||||
|
||||
return requester.method("POST").to("/authorizations", GHAuthorization.class).wrap(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,23 +432,94 @@ public class GitHub {
|
||||
*/
|
||||
public boolean isCredentialValid() throws IOException {
|
||||
try {
|
||||
retrieveWithAuth("/user/show",JsonUser.class);
|
||||
retrieve().to("/user", GHUser.class);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
WebClient createWebClient() throws IOException {
|
||||
WebClient wc = new WebClient();
|
||||
wc.setJavaScriptEnabled(false);
|
||||
wc.setCssEnabled(false);
|
||||
HtmlPage pg = (HtmlPage)wc.getPage("https://github.com/login");
|
||||
HtmlForm f = pg.getForms().get(0);
|
||||
f.getInputByName("login").setValueAttribute(login);
|
||||
f.getInputByName("password").setValueAttribute(password);
|
||||
f.submit();
|
||||
return wc;
|
||||
/**
|
||||
* Search issues.
|
||||
*/
|
||||
public GHIssueSearchBuilder searchIssues() {
|
||||
return new GHIssueSearchBuilder(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search users.
|
||||
*/
|
||||
public GHUserSearchBuilder searchUsers() {
|
||||
return new GHUserSearchBuilder(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search repositories.
|
||||
*/
|
||||
public GHRepositorySearchBuilder searchRepositories() {
|
||||
return new GHRepositorySearchBuilder(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search content.
|
||||
*/
|
||||
public GHContentSearchBuilder searchContent() {
|
||||
return new GHContentSearchBuilder(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all the notifications.
|
||||
*/
|
||||
public GHNotificationStream listNotifications() {
|
||||
return new GHNotificationStream(this,"/notifications");
|
||||
}
|
||||
|
||||
/**
|
||||
* This provides a dump of every public repository, in the order that they were created.
|
||||
* @see <a href="https://developer.github.com/v3/repos/#list-all-public-repositories">documentation</a>
|
||||
*/
|
||||
public PagedIterable<GHRepository> listAllPublicRepositories() {
|
||||
return listAllPublicRepositories(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This provides a dump of every public repository, in the order that they were created.
|
||||
*
|
||||
* @param since
|
||||
* The integer ID of the last Repository that you’ve seen. See {@link GHRepository#getId()}
|
||||
* @see <a href="https://developer.github.com/v3/repos/#list-all-public-repositories">documentation</a>
|
||||
*/
|
||||
public PagedIterable<GHRepository> listAllPublicRepositories(final String since) {
|
||||
return new PagedIterable<GHRepository>() {
|
||||
public PagedIterator<GHRepository> iterator() {
|
||||
return new PagedIterator<GHRepository>(retrieve().with("since",since).asIterator("/repositories", GHRepository[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHRepository[] page) {
|
||||
for (GHRepository c : page)
|
||||
c.wrap(GitHub.this);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a Markdown document in raw mode.
|
||||
*
|
||||
* <p>
|
||||
* It takes a Markdown document as plaintext and renders it as plain Markdown
|
||||
* without a repository context (just like a README.md file is rendered – this
|
||||
* is the simplest way to preview a readme online).
|
||||
*
|
||||
* @see GHRepository#renderMarkdown(String, MarkdownMode)
|
||||
*/
|
||||
public Reader renderMarkdown(String text) throws IOException {
|
||||
return new InputStreamReader(
|
||||
new Requester(this)
|
||||
.with(new ByteArrayInputStream(text.getBytes("UTF-8")))
|
||||
.contentType("text/plain;charset=UTF-8")
|
||||
.asStream("/markdown/raw"),
|
||||
"UTF-8");
|
||||
}
|
||||
|
||||
/*package*/ static URL parseURL(String s) {
|
||||
@@ -329,9 +531,12 @@ public class GitHub {
|
||||
}
|
||||
|
||||
/*package*/ static Date parseDate(String timestamp) {
|
||||
if (timestamp==null) return null;
|
||||
for (String f : TIME_FORMATS) {
|
||||
try {
|
||||
return new SimpleDateFormat(f).parse(timestamp);
|
||||
SimpleDateFormat df = new SimpleDateFormat(f);
|
||||
df.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
return df.parse(timestamp);
|
||||
} catch (ParseException e) {
|
||||
// try next
|
||||
}
|
||||
@@ -339,12 +544,18 @@ public class GitHub {
|
||||
throw new IllegalStateException("Unable to parse the timestamp: "+timestamp);
|
||||
}
|
||||
|
||||
/*package*/ static String printDate(Date dt) {
|
||||
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(dt);
|
||||
}
|
||||
|
||||
/*package*/ static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
private static final String[] TIME_FORMATS = {"yyyy/MM/dd HH:mm:ss ZZZZ","yyyy-MM-dd'T'HH:mm:ss'Z'"};
|
||||
|
||||
static {
|
||||
MAPPER.setVisibilityChecker(new Std(NONE, NONE, NONE, NONE, ANY));
|
||||
MAPPER.getDeserializationConfig().set(Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
/* package */ static final String GITHUB_URL = "https://api.github.com";
|
||||
}
|
||||
|
||||
197
src/main/java/org/kohsuke/github/GitHubBuilder.java
Normal file
197
src/main/java/org/kohsuke/github/GitHubBuilder.java
Normal file
@@ -0,0 +1,197 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
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;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @since 1.59
|
||||
*/
|
||||
public class GitHubBuilder {
|
||||
|
||||
// default scoped so unit tests can read them.
|
||||
/* private */ String endpoint = GitHub.GITHUB_URL;
|
||||
/* private */ String user;
|
||||
/* private */ String password;
|
||||
/* private */ String oauthToken;
|
||||
|
||||
private HttpConnector connector;
|
||||
|
||||
private RateLimitHandler rateLimitHandler = RateLimitHandler.WAIT;
|
||||
|
||||
public GitHubBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* First check if the credentials are configured using the ~/.github properties file.
|
||||
*
|
||||
* If no user is specified it means there is no configuration present so check the environment instead.
|
||||
*
|
||||
* If there is still no user it means there are no credentials defined and throw an IOException.
|
||||
*
|
||||
* @return the configured Builder from credentials defined on the system or in the environment.
|
||||
*
|
||||
* @throws IOException If there are no credentials defined in the ~/.github properties file or the process environment.
|
||||
*/
|
||||
public static GitHubBuilder fromCredentials() throws IOException {
|
||||
Exception cause = null;
|
||||
GitHubBuilder builder;
|
||||
|
||||
try {
|
||||
builder = fromPropertyFile();
|
||||
|
||||
if (builder.user != null)
|
||||
return builder;
|
||||
} catch (FileNotFoundException e) {
|
||||
// fall through
|
||||
cause = e;
|
||||
}
|
||||
|
||||
builder = fromEnvironment();
|
||||
|
||||
if (builder.user != null)
|
||||
return builder;
|
||||
else
|
||||
throw (IOException)new IOException("Failed to resolve credentials from ~/.github or the environment.").initCause(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Use {@link #fromEnvironment()} to pick up standard set of environment variables, so that
|
||||
* different clients of this library will all recognize one consistent set of coordinates.
|
||||
*/
|
||||
public static GitHubBuilder fromEnvironment(String loginVariableName, String passwordVariableName, String oauthVariableName) throws IOException {
|
||||
return fromEnvironment(loginVariableName, passwordVariableName, oauthVariableName, "");
|
||||
}
|
||||
|
||||
private static void loadIfSet(String envName, Properties p, String propName) {
|
||||
String v = System.getenv(envName);
|
||||
if (v != null)
|
||||
p.put(propName, v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Use {@link #fromEnvironment()} to pick up standard set of environment variables, so that
|
||||
* different clients of this library will all recognize one consistent set of coordinates.
|
||||
*/
|
||||
public static GitHubBuilder fromEnvironment(String loginVariableName, String passwordVariableName, String oauthVariableName, String endpointVariableName) throws IOException {
|
||||
Properties env = new Properties();
|
||||
loadIfSet(loginVariableName,env,"login");
|
||||
loadIfSet(passwordVariableName,env,"password");
|
||||
loadIfSet(oauthVariableName,env,"oauth");
|
||||
loadIfSet(endpointVariableName,env,"endpoint");
|
||||
return fromProperties(env);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link GitHubBuilder} by picking up coordinates from environment variables.
|
||||
*
|
||||
* <p>
|
||||
* The following environment variables are recognized:
|
||||
*
|
||||
* <ul>
|
||||
* <li>GITHUB_LOGIN: username like 'kohsuke'
|
||||
* <li>GITHUB_PASSWORD: raw password
|
||||
* <li>GITHUB_OAUTH: OAuth token to login
|
||||
* <li>GITHUB_ENDPOINT: URL of the API endpoint
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* See class javadoc for the relationship between these coordinates.
|
||||
*
|
||||
* <p>
|
||||
* For backward compatibility, the following environment variables are recognized but discouraged:
|
||||
* login, password, oauth
|
||||
*/
|
||||
public static GitHubBuilder fromEnvironment() throws IOException {
|
||||
Properties props = new Properties();
|
||||
for (Entry<String, String> e : System.getenv().entrySet()) {
|
||||
String name = e.getKey().toLowerCase(Locale.ENGLISH);
|
||||
if (name.startsWith("github_")) name=name.substring(7);
|
||||
props.put(name,e.getValue());
|
||||
}
|
||||
return fromProperties(props);
|
||||
}
|
||||
|
||||
public static GitHubBuilder fromPropertyFile() throws IOException {
|
||||
File homeDir = new File(System.getProperty("user.home"));
|
||||
File propertyFile = new File(homeDir, ".github");
|
||||
return fromPropertyFile(propertyFile.getPath());
|
||||
}
|
||||
|
||||
public static GitHubBuilder fromPropertyFile(String propertyFileName) throws IOException {
|
||||
Properties props = new Properties();
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(propertyFileName);
|
||||
props.load(in);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(in);
|
||||
}
|
||||
|
||||
return fromProperties(props);
|
||||
}
|
||||
|
||||
public static GitHubBuilder fromProperties(Properties props) {
|
||||
GitHubBuilder self = new GitHubBuilder();
|
||||
self.withOAuthToken(props.getProperty("oauth"), props.getProperty("login"));
|
||||
self.withPassword(props.getProperty("login"), props.getProperty("password"));
|
||||
self.withEndpoint(props.getProperty("endpoint", GitHub.GITHUB_URL));
|
||||
return self;
|
||||
}
|
||||
|
||||
public GitHubBuilder withEndpoint(String endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
return this;
|
||||
}
|
||||
public GitHubBuilder withPassword(String user, String password) {
|
||||
this.user = user;
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
public GitHubBuilder withOAuthToken(String oauthToken) {
|
||||
return withOAuthToken(oauthToken, null);
|
||||
}
|
||||
public GitHubBuilder withOAuthToken(String oauthToken, String user) {
|
||||
this.oauthToken = oauthToken;
|
||||
this.user = user;
|
||||
return this;
|
||||
}
|
||||
public GitHubBuilder withConnector(HttpConnector connector) {
|
||||
this.connector = connector;
|
||||
return this;
|
||||
}
|
||||
public GitHubBuilder withRateLimitHandler(RateLimitHandler handler) {
|
||||
this.rateLimitHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures {@linkplain #withConnector(HttpConnector) connector}
|
||||
* that uses HTTP library in JRE but use a specific proxy, instead of
|
||||
* the system default one.
|
||||
*/
|
||||
public GitHubBuilder withProxy(final Proxy p) {
|
||||
return withConnector(new HttpConnector() {
|
||||
public HttpURLConnection connect(URL url) throws IOException {
|
||||
return (HttpURLConnection) url.openConnection(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public GitHub build() throws IOException {
|
||||
return new GitHub(endpoint, user, oauthToken, password, connector, rateLimitHandler);
|
||||
}
|
||||
}
|
||||
38
src/main/java/org/kohsuke/github/GitUser.java
Normal file
38
src/main/java/org/kohsuke/github/GitUser.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Represents a user in Git who authors/commits a commit.
|
||||
*
|
||||
* In contrast, {@link GHUser} is an user of GitHub. Because Git allows a person to
|
||||
* use multiple e-mail addresses and names when creating a commit, there's generally
|
||||
* no meaningful mapping between {@link GHUser} and {@link GitUser}.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class GitUser {
|
||||
private String name, email, date;
|
||||
|
||||
/**
|
||||
* Human readable name of the user, such as "Kohsuke Kawaguchi"
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* E-mail address, such as "foo@example.com"
|
||||
*/
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
/**
|
||||
* This field doesn't appear to be consistently available in all the situations where this class
|
||||
* is used.
|
||||
*/
|
||||
public Date getDate() {
|
||||
return GitHub.parseDate(date);
|
||||
}
|
||||
}
|
||||
29
src/main/java/org/kohsuke/github/HttpConnector.java
Normal file
29
src/main/java/org/kohsuke/github/HttpConnector.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Pluggability for customizing HTTP request behaviors or using altogether different library.
|
||||
*
|
||||
* <p>
|
||||
* For example, you can implement this to st custom timeouts.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public interface HttpConnector {
|
||||
/**
|
||||
* Opens a connection to the given URL.
|
||||
*/
|
||||
HttpURLConnection connect(URL url) throws IOException;
|
||||
|
||||
/**
|
||||
* Default implementation that uses {@link URL#openConnection()}.
|
||||
*/
|
||||
HttpConnector DEFAULT = new HttpConnector() {
|
||||
public HttpURLConnection connect(URL url) throws IOException {
|
||||
return (HttpURLConnection) url.openConnection();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2010, Kohsuke Kawaguchi
|
||||
*
|
||||
* 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 java.util.List;
|
||||
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class JsonCollaborators {
|
||||
List<String> collaborators;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2011, Eric Maupin
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @author Eric Maupin
|
||||
*/
|
||||
class JsonIssue {
|
||||
GHIssue issue;
|
||||
|
||||
GHIssue wrap(GHRepository r) {
|
||||
issue.owner = r;
|
||||
issue.root = r.root;
|
||||
return issue;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class JsonIssueComments {
|
||||
List<GHIssueComment> comments;
|
||||
|
||||
List<GHIssueComment> wrap(GHIssue owner) {
|
||||
for (GHIssueComment c : comments)
|
||||
c.owner = owner;
|
||||
return comments;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2011, Eric Maupin
|
||||
*
|
||||
* 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 java.util.List;
|
||||
|
||||
class JsonIssues {
|
||||
List<GHIssue> issues;
|
||||
|
||||
public List<GHIssue> wrap(GHRepository owner) {
|
||||
for (GHIssue issue : issues) {
|
||||
issue.owner = owner;
|
||||
issue.root = owner.root;
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2010, Kohsuke Kawaguchi
|
||||
*
|
||||
* 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 java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class JsonOrganizations {
|
||||
public List<GHOrganization> organizations;
|
||||
|
||||
public Map<String,GHOrganization> wrap(GitHub root) {
|
||||
Map<String,GHOrganization> map = new TreeMap<String, GHOrganization>();
|
||||
for (GHOrganization o : organizations) {
|
||||
o.root = root;
|
||||
map.put(o.getLogin(),o);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2010, Kohsuke Kawaguchi
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class JsonPullRequest {
|
||||
public GHPullRequest pull;
|
||||
|
||||
public GHPullRequest wrap(GHRepository owner) {
|
||||
pull.owner = owner;
|
||||
pull.root = owner.root;
|
||||
return pull;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,6 @@ package org.kohsuke.github;
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class JsonOrganization {
|
||||
public GHOrganization organization;
|
||||
class JsonRateLimit {
|
||||
GHRateLimit rate;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2010, Kohsuke Kawaguchi
|
||||
*
|
||||
* 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 java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class JsonRepositories {
|
||||
public List<GHRepository> repositories;
|
||||
|
||||
public Map<String,GHRepository> wrap(GitHub root) {
|
||||
Map<String,GHRepository> map = new TreeMap<String, GHRepository>();
|
||||
for (GHRepository r : repositories) {
|
||||
r.root = root;
|
||||
map.put(r.getName(),r);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class JsonTeam {
|
||||
public GHTeam team;
|
||||
|
||||
GHTeam wrap(GHOrganization org) {
|
||||
team.org = org;
|
||||
return team;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class JsonTeams {
|
||||
public List<GHTeam> teams;
|
||||
|
||||
Map<String, GHTeam> toMap(GHOrganization org) {
|
||||
Map<String, GHTeam> r = new TreeMap<String, GHTeam>();
|
||||
for (GHTeam t : teams) {
|
||||
t.org = org;
|
||||
r.put(t.getName(),t);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
Set<GHTeam> toSet(GHOrganization org) {
|
||||
Set<GHTeam> r = new HashSet<GHTeam>();
|
||||
for (GHTeam t : teams) {
|
||||
t.org = org;
|
||||
r.add(t);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2010, Kohsuke Kawaguchi
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class JsonUser {
|
||||
public GHUser user;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2010, Kohsuke Kawaguchi
|
||||
*
|
||||
* 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 java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class JsonUsers {
|
||||
public List<String> users;
|
||||
|
||||
public GHPersonSet<GHUser> toSet(GitHub root) throws IOException {
|
||||
GHPersonSet<GHUser> r = new GHPersonSet<GHUser>();
|
||||
for (String u : users)
|
||||
r.add(root.getUser(u));
|
||||
return r;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class JsonUsersWithDetails {
|
||||
public List<GHUser> users;
|
||||
|
||||
public Set<GHUser> toSet(GitHub root) throws IOException {
|
||||
Set<GHUser> r = new HashSet<GHUser>();
|
||||
for (GHUser u : users)
|
||||
r.add(root.getUser(u));
|
||||
return r;
|
||||
}
|
||||
}
|
||||
29
src/main/java/org/kohsuke/github/MarkdownMode.java
Normal file
29
src/main/java/org/kohsuke/github/MarkdownMode.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Rendering mode of markdown.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GitHub#renderMarkdown(String)
|
||||
* @see GHRepository#renderMarkdown(String, MarkdownMode)
|
||||
*/
|
||||
public enum MarkdownMode {
|
||||
/**
|
||||
* Render a document as plain Markdown, just like README files are rendered.
|
||||
*/
|
||||
MARKDOWN,
|
||||
/**
|
||||
* Render a document as user-content, e.g. like user comments or issues are rendered.
|
||||
* In GFM mode, hard line breaks are always taken into account, and issue and user
|
||||
* mentions are linked accordingly.
|
||||
*
|
||||
* @see GHRepository#renderMarkdown(String, MarkdownMode)
|
||||
*/
|
||||
GFM;
|
||||
|
||||
public String toString() {
|
||||
return name().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
}
|
||||
37
src/main/java/org/kohsuke/github/PagedIterable.java
Normal file
37
src/main/java/org/kohsuke/github/PagedIterable.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* {@link Iterable} that returns {@link PagedIterator}
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public abstract class PagedIterable<T> implements Iterable<T> {
|
||||
public abstract PagedIterator<T> iterator();
|
||||
|
||||
/**
|
||||
* Eagerly walk {@link Iterable} and return the result in a list.
|
||||
*/
|
||||
public List<T> asList() {
|
||||
List<T> r = new ArrayList<T>();
|
||||
for(PagedIterator<T> i = iterator(); i.hasNext();) {
|
||||
r.addAll(i.nextPage());
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eagerly walk {@link Iterable} and return the result in a set.
|
||||
*/
|
||||
public Set<T> asSet() {
|
||||
LinkedHashSet<T> r = new LinkedHashSet<T>();
|
||||
for(PagedIterator<T> i = iterator(); i.hasNext();) {
|
||||
r.addAll(i.nextPage());
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
72
src/main/java/org/kohsuke/github/PagedIterator.java
Normal file
72
src/main/java/org/kohsuke/github/PagedIterator.java
Normal file
@@ -0,0 +1,72 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* Iterator over a pagenated data source.
|
||||
*
|
||||
* Aside from the normal iterator operation, this method exposes {@link #nextPage()}
|
||||
* that allows the caller to retrieve items per page.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public abstract class PagedIterator<T> implements Iterator<T> {
|
||||
private final Iterator<T[]> base;
|
||||
|
||||
/**
|
||||
* Current batch that we retrieved but haven't returned to the caller.
|
||||
*/
|
||||
private T[] current;
|
||||
private int pos;
|
||||
|
||||
/*package*/ PagedIterator(Iterator<T[]> base) {
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
protected abstract void wrapUp(T[] page);
|
||||
|
||||
public boolean hasNext() {
|
||||
fetch();
|
||||
return current!=null;
|
||||
}
|
||||
|
||||
public T next() {
|
||||
fetch();
|
||||
if (current==null) throw new NoSuchElementException();
|
||||
return current[pos++];
|
||||
}
|
||||
|
||||
private void fetch() {
|
||||
while (current==null || current.length<=pos) {
|
||||
if (!base.hasNext()) {// no more to retrieve
|
||||
current = null;
|
||||
pos = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
current = base.next();
|
||||
wrapUp(current);
|
||||
pos = 0;
|
||||
}
|
||||
// invariant at the end: there's some data to retrieve
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next page worth of data.
|
||||
*/
|
||||
public List<T> nextPage() {
|
||||
fetch();
|
||||
List<T> r = Arrays.asList(current);
|
||||
r = r.subList(pos,r.size());
|
||||
current = null;
|
||||
pos = 0;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
60
src/main/java/org/kohsuke/github/PagedSearchIterable.java
Normal file
60
src/main/java/org/kohsuke/github/PagedSearchIterable.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* {@link PagedIterable} enhanced to report search result specific information.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public abstract class PagedSearchIterable<T> extends PagedIterable<T> {
|
||||
private final GitHub root;
|
||||
|
||||
/**
|
||||
* As soon as we have any result fetched, it's set here so that we can report the total count.
|
||||
*/
|
||||
private SearchResult<T> result;
|
||||
|
||||
/*package*/ PagedSearchIterable(GitHub root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of hit, including the results that's not yet fetched.
|
||||
*/
|
||||
public int getTotalCount() {
|
||||
populate();
|
||||
return result.total_count;
|
||||
}
|
||||
|
||||
public boolean isIncomplete() {
|
||||
populate();
|
||||
return result.incomplete_results;
|
||||
}
|
||||
|
||||
private void populate() {
|
||||
if (result==null)
|
||||
iterator().hasNext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts {@link Iterator}.
|
||||
*/
|
||||
protected Iterator<T[]> adapt(final Iterator<? extends SearchResult<T>> base) {
|
||||
return new Iterator<T[]>() {
|
||||
public boolean hasNext() {
|
||||
return base.hasNext();
|
||||
}
|
||||
|
||||
public T[] next() {
|
||||
SearchResult<T> v = base.next();
|
||||
if (result==null) result = v;
|
||||
return v.getItems(root);
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2010, Kohsuke Kawaguchi
|
||||
*
|
||||
* 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.apache.commons.io.IOUtils;
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.impl.WriterBasedGenerator;
|
||||
import org.codehaus.jackson.node.ObjectNode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.kohsuke.github.GitHub.*;
|
||||
|
||||
/**
|
||||
* Handles HTTP POST.
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class Poster {
|
||||
private final GitHub root;
|
||||
private final List<Entry> args = new ArrayList<Entry>();
|
||||
private boolean authenticate;
|
||||
|
||||
private final ApiVersion v;
|
||||
|
||||
private static class Entry {
|
||||
String key;
|
||||
Object value;
|
||||
|
||||
private Entry(String key, Object value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String toFormArg() throws UnsupportedEncodingException {
|
||||
return URLEncoder.encode(key,"UTF-8")+'='+URLEncoder.encode(value.toString(),"UTF-8");
|
||||
}
|
||||
}
|
||||
|
||||
Poster(GitHub root, ApiVersion v) {
|
||||
this.root = root;
|
||||
this.v = v;
|
||||
}
|
||||
|
||||
Poster(GitHub root) {
|
||||
this(root,ApiVersion.V2);
|
||||
}
|
||||
|
||||
public Poster withCredential() {
|
||||
root.requireCredential();
|
||||
authenticate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Poster with(String key, int value) {
|
||||
return _with(key, value);
|
||||
}
|
||||
|
||||
public Poster with(String key, boolean value) {
|
||||
return _with(key, value);
|
||||
}
|
||||
|
||||
public Poster with(String key, String value) {
|
||||
return _with(key, value);
|
||||
}
|
||||
|
||||
public Poster _with(String key, Object value) {
|
||||
if (value!=null) {
|
||||
args.add(new Entry(key,value));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void to(String tailApiUrl) throws IOException {
|
||||
to(tailApiUrl,null);
|
||||
}
|
||||
|
||||
/**
|
||||
* POSTs the form to the specified URL.
|
||||
*
|
||||
* @throws IOException
|
||||
* if the server returns 4xx/5xx responses.
|
||||
* @return
|
||||
* {@link Reader} that reads the response.
|
||||
*/
|
||||
public <T> T to(String tailApiUrl, Class<T> type) throws IOException {
|
||||
return to(tailApiUrl,type,"POST");
|
||||
}
|
||||
|
||||
public <T> T to(String tailApiUrl, Class<T> type, String method) throws IOException {
|
||||
while (true) {// loop while API rate limit is hit
|
||||
HttpURLConnection uc = (HttpURLConnection) root.getApiURL(v,tailApiUrl).openConnection();
|
||||
|
||||
uc.setDoOutput(true);
|
||||
uc.setRequestProperty("Content-type","application/x-www-form-urlencoded");
|
||||
if (authenticate) {
|
||||
if (v==ApiVersion.V3) {
|
||||
if (root.oauthAccessToken!=null) {
|
||||
uc.setRequestProperty("Authorization", "token " + root.oauthAccessToken);
|
||||
} else {
|
||||
if (root.password==null)
|
||||
throw new IllegalArgumentException("V3 API doesn't support API token");
|
||||
uc.setRequestProperty("Authorization", "Basic " + root.encodedAuthorization);
|
||||
}
|
||||
} else {
|
||||
uc.setRequestProperty("Authorization", "Basic " + root.encodedAuthorization);
|
||||
}
|
||||
}
|
||||
uc.setRequestMethod(method);
|
||||
|
||||
|
||||
if (v==ApiVersion.V2) {
|
||||
StringBuilder body = new StringBuilder();
|
||||
for (Entry e : args) {
|
||||
if (body.length()>0) body.append('&');
|
||||
body.append(e.toFormArg());
|
||||
}
|
||||
|
||||
OutputStreamWriter o = new OutputStreamWriter(uc.getOutputStream(), "UTF-8");
|
||||
o.write(body.toString());
|
||||
o.close();
|
||||
} else {
|
||||
Map json = new HashMap();
|
||||
for (Entry e : args) {
|
||||
json.put(e.key, e.value);
|
||||
}
|
||||
MAPPER.writeValue(uc.getOutputStream(),json);
|
||||
}
|
||||
|
||||
try {
|
||||
InputStreamReader r = new InputStreamReader(uc.getInputStream(), "UTF-8");
|
||||
String data = IOUtils.toString(r);
|
||||
if (type==null) {
|
||||
return null;
|
||||
}
|
||||
return MAPPER.readValue(data,type);
|
||||
} catch (IOException e) {
|
||||
root.handleApiError(e,uc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/main/java/org/kohsuke/github/RateLimitHandler.java
Normal file
61
src/main/java/org/kohsuke/github/RateLimitHandler.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
/**
|
||||
* Pluggable strategy to determine what to do when the API rate limit is reached.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GitHubBuilder#withRateLimitHandler(RateLimitHandler)
|
||||
*/
|
||||
public abstract class RateLimitHandler {
|
||||
/**
|
||||
* Called when the library encounters HTTP error indicating that the API rate limit is reached.
|
||||
*
|
||||
* <p>
|
||||
* Any exception thrown from this method will cause the request to fail, and the caller of github-api
|
||||
* will receive an exception. If this method returns normally, another request will be attempted.
|
||||
* For that to make sense, the implementation needs to wait for some time.
|
||||
*
|
||||
* @see <a href="https://developer.github.com/v3/#rate-limiting">API documentation from GitHub</a>
|
||||
* @param e
|
||||
* Exception from Java I/O layer. If you decide to fail the processing, you can throw
|
||||
* this exception (or wrap this exception into another exception and throw it.)
|
||||
* @param uc
|
||||
* Connection that resulted in an error. Useful for accessing other response headers.
|
||||
*/
|
||||
public abstract void onError(IOException e, HttpURLConnection uc) throws IOException;
|
||||
|
||||
/**
|
||||
* Block until the API rate limit is reset. Useful for long-running batch processing.
|
||||
*/
|
||||
public static final RateLimitHandler WAIT = new RateLimitHandler() {
|
||||
@Override
|
||||
public void onError(IOException e, HttpURLConnection uc) throws IOException {
|
||||
try {
|
||||
Thread.sleep(parseWaitTime(uc));
|
||||
} catch (InterruptedException _) {
|
||||
throw (InterruptedIOException)new InterruptedIOException().initCause(e);
|
||||
}
|
||||
}
|
||||
|
||||
private long parseWaitTime(HttpURLConnection uc) {
|
||||
String v = uc.getHeaderField("X-RateLimit-Reset");
|
||||
if (v==null) return 10000; // can't tell
|
||||
|
||||
return Math.max(10000, Long.parseLong(v)*1000 - System.currentTimeMillis());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fail immediately.
|
||||
*/
|
||||
public static final RateLimitHandler FAIL = new RateLimitHandler() {
|
||||
@Override
|
||||
public void onError(IOException e, HttpURLConnection uc) throws IOException {
|
||||
throw (IOException)new IOException("API rate limit reached").initCause(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
517
src/main/java/org/kohsuke/github/Requester.java
Normal file
517
src/main/java/org/kohsuke/github/Requester.java
Normal file
@@ -0,0 +1,517 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2010, Kohsuke Kawaguchi
|
||||
*
|
||||
* 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.databind.JsonMappingException;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.kohsuke.github.GitHub.*;
|
||||
|
||||
/**
|
||||
* A builder pattern for making HTTP call and parsing its output.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class Requester {
|
||||
private static final List<String> METHODS_WITHOUT_BODY = asList("GET", "DELETE");
|
||||
|
||||
private final GitHub root;
|
||||
private final List<Entry> args = new ArrayList<Entry>();
|
||||
private final Map<String,String> headers = new LinkedHashMap<String, String>();
|
||||
|
||||
/**
|
||||
* Request method.
|
||||
*/
|
||||
private String method = "POST";
|
||||
private String contentType = "application/x-www-form-urlencoded";
|
||||
private InputStream body;
|
||||
|
||||
/**
|
||||
* Current connection.
|
||||
*/
|
||||
private HttpURLConnection uc;
|
||||
|
||||
private static class Entry {
|
||||
String key;
|
||||
Object value;
|
||||
|
||||
private Entry(String key, Object value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
Requester(GitHub root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the request HTTP header.
|
||||
*
|
||||
* If a header of the same name is already set, this method overrides it.
|
||||
*/
|
||||
public void setHeader(String name, String value) {
|
||||
headers.put(name,value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request with authentication credential.
|
||||
*/
|
||||
@Deprecated
|
||||
public Requester withCredential() {
|
||||
// keeping it inline with retrieveWithAuth not to enforce the check
|
||||
// root.requireCredential();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Requester with(String key, int value) {
|
||||
return _with(key, value);
|
||||
}
|
||||
|
||||
public Requester with(String key, Integer value) {
|
||||
if (value!=null)
|
||||
_with(key, value.intValue());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Requester with(String key, boolean value) {
|
||||
return _with(key, value);
|
||||
}
|
||||
public Requester with(String key, Boolean value) {
|
||||
return _with(key, value);
|
||||
}
|
||||
|
||||
|
||||
public Requester with(String key, String value) {
|
||||
return _with(key, value);
|
||||
}
|
||||
|
||||
public Requester with(String key, Collection<String> value) {
|
||||
return _with(key, value);
|
||||
}
|
||||
|
||||
public Requester with(String key, Map<String, String> value) {
|
||||
return _with(key, value);
|
||||
}
|
||||
|
||||
public Requester with(InputStream body) {
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Requester _with(String key, Object value) {
|
||||
if (value!=null) {
|
||||
args.add(new Entry(key,value));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlike {@link #with(String, String)}, overrides the existing value
|
||||
*/
|
||||
public Requester set(String key, Object value) {
|
||||
for (Entry e : args) {
|
||||
if (e.key.equals(key)) {
|
||||
e.value = value;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
return _with(key,value);
|
||||
}
|
||||
|
||||
public Requester method(String method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Requester contentType(String contentType) {
|
||||
this.contentType = contentType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void to(String tailApiUrl) throws IOException {
|
||||
to(tailApiUrl,null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the specified URL, and parses the response into the given type via databinding.
|
||||
*
|
||||
* @throws IOException
|
||||
* if the server returns 4xx/5xx responses.
|
||||
* @return
|
||||
* {@link Reader} that reads the response.
|
||||
*/
|
||||
public <T> T to(String tailApiUrl, Class<T> type) throws IOException {
|
||||
return _to(tailApiUrl, type, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #to(String, Class)} but updates an existing object instead of creating a new instance.
|
||||
*/
|
||||
public <T> T to(String tailApiUrl, T existingInstance) throws IOException {
|
||||
return _to(tailApiUrl, null, existingInstance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Short for {@code method(method).to(tailApiUrl,type)}
|
||||
*/
|
||||
@Deprecated
|
||||
public <T> T to(String tailApiUrl, Class<T> type, String method) throws IOException {
|
||||
return method(method).to(tailApiUrl,type);
|
||||
}
|
||||
|
||||
private <T> T _to(String tailApiUrl, Class<T> type, T instance) throws IOException {
|
||||
while (true) {// loop while API rate limit is hit
|
||||
if (METHODS_WITHOUT_BODY.contains(method) && !args.isEmpty()) {
|
||||
StringBuilder qs=new StringBuilder();
|
||||
for (Entry arg : args) {
|
||||
qs.append(qs.length()==0 ? '?' : '&');
|
||||
qs.append(arg.key).append('=').append(URLEncoder.encode(arg.value.toString(),"UTF-8"));
|
||||
}
|
||||
tailApiUrl += qs.toString();
|
||||
}
|
||||
setupConnection(root.getApiURL(tailApiUrl));
|
||||
|
||||
buildRequest();
|
||||
|
||||
try {
|
||||
T result = parse(type, instance);
|
||||
if (type != null && type.isArray()) { // we might have to loop for pagination - done through recursion
|
||||
final String links = uc.getHeaderField("link");
|
||||
if (links != null && links.contains("rel=\"next\"")) {
|
||||
Pattern nextLinkPattern = Pattern.compile(".*<(.*)>; rel=\"next\"");
|
||||
Matcher nextLinkMatcher = nextLinkPattern.matcher(links);
|
||||
if (nextLinkMatcher.find()) {
|
||||
final String link = nextLinkMatcher.group(1);
|
||||
T nextResult = _to(link, type, instance);
|
||||
|
||||
final int resultLength = Array.getLength(result);
|
||||
final int nextResultLength = Array.getLength(nextResult);
|
||||
T concatResult = (T) Array.newInstance(type.getComponentType(), resultLength + nextResultLength);
|
||||
System.arraycopy(result, 0, concatResult, 0, resultLength);
|
||||
System.arraycopy(nextResult, 0, concatResult, resultLength, nextResultLength);
|
||||
result = concatResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (IOException e) {
|
||||
handleApiError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request and just obtains the HTTP status code.
|
||||
*/
|
||||
public int asHttpStatusCode(String tailApiUrl) throws IOException {
|
||||
while (true) {// loop while API rate limit is hit
|
||||
setupConnection(root.getApiURL(tailApiUrl));
|
||||
|
||||
buildRequest();
|
||||
|
||||
try {
|
||||
return uc.getResponseCode();
|
||||
} catch (IOException e) {
|
||||
handleApiError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public InputStream asStream(String tailApiUrl) throws IOException {
|
||||
while (true) {// loop while API rate limit is hit
|
||||
setupConnection(root.getApiURL(tailApiUrl));
|
||||
|
||||
buildRequest();
|
||||
|
||||
try {
|
||||
return wrapStream(uc.getInputStream());
|
||||
} catch (IOException e) {
|
||||
handleApiError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getResponseHeader(String header) {
|
||||
return uc.getHeaderField(header);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set up the request parameters or POST payload.
|
||||
*/
|
||||
private void buildRequest() throws IOException {
|
||||
if (isMethodWithBody()) {
|
||||
uc.setDoOutput(true);
|
||||
uc.setRequestProperty("Content-type", contentType);
|
||||
|
||||
if (body == null) {
|
||||
Map json = new HashMap();
|
||||
for (Entry e : args) {
|
||||
json.put(e.key, e.value);
|
||||
}
|
||||
MAPPER.writeValue(uc.getOutputStream(), json);
|
||||
} else {
|
||||
try {
|
||||
byte[] bytes = new byte[32768];
|
||||
int read = 0;
|
||||
while ((read = body.read(bytes)) != -1) {
|
||||
uc.getOutputStream().write(bytes, 0, read);
|
||||
}
|
||||
} finally {
|
||||
body.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMethodWithBody() {
|
||||
return !METHODS_WITHOUT_BODY.contains(method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads pagenated resources.
|
||||
*
|
||||
* Every iterator call reports a new batch.
|
||||
*/
|
||||
/*package*/ <T> Iterator<T> asIterator(String _tailApiUrl, final Class<T> type) {
|
||||
method("GET");
|
||||
|
||||
if (!args.isEmpty()) {
|
||||
boolean first=true;
|
||||
try {
|
||||
for (Entry a : args) {
|
||||
_tailApiUrl += first ? '?' : '&';
|
||||
first = false;
|
||||
_tailApiUrl += URLEncoder.encode(a.key,"UTF-8")+'='+URLEncoder.encode(a.value.toString(),"UTF-8");
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e); // UTF-8 is mandatory
|
||||
}
|
||||
}
|
||||
|
||||
final String tailApiUrl = _tailApiUrl;
|
||||
|
||||
return new Iterator<T>() {
|
||||
/**
|
||||
* The next batch to be returned from {@link #next()}.
|
||||
*/
|
||||
T next;
|
||||
/**
|
||||
* URL of the next resource to be retrieved, or null if no more data is available.
|
||||
*/
|
||||
URL url;
|
||||
|
||||
{
|
||||
try {
|
||||
url = root.getApiURL(tailApiUrl);
|
||||
} catch (IOException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
fetch();
|
||||
return next!=null;
|
||||
}
|
||||
|
||||
public T next() {
|
||||
fetch();
|
||||
T r = next;
|
||||
if (r==null) throw new NoSuchElementException();
|
||||
next = null;
|
||||
return r;
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private void fetch() {
|
||||
if (next!=null) return; // already fetched
|
||||
if (url==null) return; // no more data to fetch
|
||||
|
||||
try {
|
||||
while (true) {// loop while API rate limit is hit
|
||||
setupConnection(url);
|
||||
try {
|
||||
next = parse(type,null);
|
||||
assert next!=null;
|
||||
findNextURL();
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
handleApiError(e);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the next page from the pagination "Link" tag.
|
||||
*/
|
||||
private void findNextURL() throws MalformedURLException {
|
||||
url = null; // start defensively
|
||||
String link = uc.getHeaderField("Link");
|
||||
if (link==null) return;
|
||||
|
||||
for (String token : link.split(", ")) {
|
||||
if (token.endsWith("rel=\"next\"")) {
|
||||
// found the next page. This should look something like
|
||||
// <https://api.github.com/repos?page=3&per_page=100>; rel="next"
|
||||
int idx = token.indexOf('>');
|
||||
url = new URL(token.substring(1,idx));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// no more "next" link. we are done.
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private void setupConnection(URL url) throws IOException {
|
||||
uc = root.getConnector().connect(url);
|
||||
|
||||
// if the authentication is needed but no credential is given, try it anyway (so that some calls
|
||||
// that do work with anonymous access in the reduced form should still work.)
|
||||
if (root.encodedAuthorization!=null)
|
||||
uc.setRequestProperty("Authorization", root.encodedAuthorization);
|
||||
|
||||
for (Map.Entry<String, String> e : headers.entrySet()) {
|
||||
String v = e.getValue();
|
||||
if (v!=null)
|
||||
uc.setRequestProperty(e.getKey(), v);
|
||||
}
|
||||
|
||||
try {
|
||||
uc.setRequestMethod(method);
|
||||
} catch (ProtocolException e) {
|
||||
// JDK only allows one of the fixed set of verbs. Try to override that
|
||||
try {
|
||||
Field $method = HttpURLConnection.class.getDeclaredField("method");
|
||||
$method.setAccessible(true);
|
||||
$method.set(uc,method);
|
||||
} catch (Exception x) {
|
||||
throw (IOException)new IOException("Failed to set the custom verb").initCause(x);
|
||||
}
|
||||
}
|
||||
uc.setRequestProperty("Accept-Encoding", "gzip");
|
||||
}
|
||||
|
||||
private <T> T parse(Class<T> type, T instance) throws IOException {
|
||||
if (uc.getResponseCode()==304)
|
||||
return null; // special case handling for 304 unmodified, as the content will be ""
|
||||
InputStreamReader r = null;
|
||||
try {
|
||||
r = new InputStreamReader(wrapStream(uc.getInputStream()), "UTF-8");
|
||||
String data = IOUtils.toString(r);
|
||||
if (type!=null)
|
||||
try {
|
||||
return MAPPER.readValue(data,type);
|
||||
} catch (JsonMappingException e) {
|
||||
throw (IOException)new IOException("Failed to deserialize "+data).initCause(e);
|
||||
}
|
||||
if (instance!=null)
|
||||
return MAPPER.readerForUpdating(instance).<T>readValue(data);
|
||||
return null;
|
||||
} finally {
|
||||
IOUtils.closeQuietly(r);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the "Content-Encoding" header.
|
||||
*/
|
||||
private InputStream wrapStream(InputStream in) throws IOException {
|
||||
String encoding = uc.getContentEncoding();
|
||||
if (encoding==null || in==null) return in;
|
||||
if (encoding.equals("gzip")) return new GZIPInputStream(in);
|
||||
|
||||
throw new UnsupportedOperationException("Unexpected Content-Encoding: "+encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle API error by either throwing it or by returning normally to retry.
|
||||
*/
|
||||
/*package*/ void handleApiError(IOException e) throws IOException {
|
||||
if (uc.getResponseCode() == 401) // Unauthorized == bad creds
|
||||
throw e;
|
||||
|
||||
if ("0".equals(uc.getHeaderField("X-RateLimit-Remaining"))) {
|
||||
root.rateLimitHandler.onError(e,uc);
|
||||
}
|
||||
|
||||
InputStream es = wrapStream(uc.getErrorStream());
|
||||
try {
|
||||
if (es!=null) {
|
||||
if (e instanceof FileNotFoundException) {
|
||||
// pass through 404 Not Found to allow the caller to handle it intelligently
|
||||
throw (IOException) new FileNotFoundException(IOUtils.toString(es, "UTF-8")).initCause(e);
|
||||
} else
|
||||
throw (IOException) new IOException(IOUtils.toString(es, "UTF-8")).initCause(e);
|
||||
} else
|
||||
throw e;
|
||||
} finally {
|
||||
IOUtils.closeQuietly(es);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> toSet(String s) {
|
||||
Set<String> r = new HashSet<String>();
|
||||
for (String t : s.split(","))
|
||||
r.add(t.trim());
|
||||
return r;
|
||||
}
|
||||
}
|
||||
16
src/main/java/org/kohsuke/github/SearchResult.java
Normal file
16
src/main/java/org/kohsuke/github/SearchResult.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
/**
|
||||
* Represents the result of a search
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
abstract class SearchResult<T> {
|
||||
int total_count;
|
||||
boolean incomplete_results;
|
||||
|
||||
/**
|
||||
* Wraps up the retrieved object and return them. Only called once.
|
||||
*/
|
||||
/*package*/ abstract T[] getItems(GitHub root);
|
||||
}
|
||||
32
src/main/java/org/kohsuke/github/extras/OkHttpConnector.java
Normal file
32
src/main/java/org/kohsuke/github/extras/OkHttpConnector.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package org.kohsuke.github.extras;
|
||||
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
import com.squareup.okhttp.OkUrlFactory;
|
||||
import org.kohsuke.github.HttpConnector;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* {@link HttpConnector} for {@link OkHttpClient}.
|
||||
*
|
||||
* Unlike {@link #DEFAULT}, OkHttp does response caching.
|
||||
* Making a conditional request against GitHubAPI and receiving a 304
|
||||
* response does not count against the rate limit.
|
||||
* See http://developer.github.com/v3/#conditional-requests
|
||||
*
|
||||
* @author Roberto Tyley
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class OkHttpConnector implements HttpConnector {
|
||||
private final OkUrlFactory urlFactory;
|
||||
|
||||
public OkHttpConnector(OkUrlFactory urlFactory) {
|
||||
this.urlFactory = urlFactory;
|
||||
}
|
||||
|
||||
public HttpURLConnection connect(URL url) throws IOException {
|
||||
return urlFactory.open(url);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
What is this?
|
||||
|
||||
This library defines an object oriented representation of the GitHub API. The library doesn't yet cover the entirety of the GitHub API, but it's implemented with the right abstractions and libraries to make it very easy to improve the coverage.
|
||||
|
||||
Sample Usage
|
||||
|
||||
------------------
|
||||
GitHub github = GitHub.connect();
|
||||
GHRepository repo = github.createRepository(
|
||||
"new-repository","this is my new repository",
|
||||
"http://www.kohsuke.org/",true/*public*/);
|
||||
repo.addCollaborators(github.getUser("abayer"),github.getUser("rtyler"));
|
||||
repo.delete();
|
||||
------------------
|
||||
|
||||
Credential
|
||||
|
||||
This library allows the caller to supply the credential as parameters, but it also defines a common convention
|
||||
so that applications using this library will look at the consistent location. In this convention, the library
|
||||
looks at "~/.github" property file, which should have the following two values:
|
||||
|
||||
------------------
|
||||
login=kohsuke
|
||||
token=012345678
|
||||
------------------
|
||||
51
src/site/markdown/index.md
Normal file
51
src/site/markdown/index.md
Normal file
@@ -0,0 +1,51 @@
|
||||
What is this?
|
||||
=====
|
||||
|
||||
This library defines an object oriented representation of the GitHub API. By "object oriented" we mean
|
||||
there are classes that correspond to the domain model of GitHub (such as `GHUser` and `GHRepository`),
|
||||
operations that act on them as defined as methods (such as `GHUser.follow()`), and those object references
|
||||
are used in favor of using string handle (such as `GHUser.isMemberOf(GHOrganization)` instead of
|
||||
`GHUser.isMemberOf(String)`)
|
||||
|
||||
The library supports both github.com and GitHub Enterprise.
|
||||
|
||||
Most of the GitHub APIs are covered, although there are some corners that are still not yet implemented.
|
||||
|
||||
Sample Usage
|
||||
-----
|
||||
|
||||
GitHub github = GitHub.connect();
|
||||
GHRepository repo = github.createRepository(
|
||||
"new-repository","this is my new repository",
|
||||
"http://www.kohsuke.org/",true/*public*/);
|
||||
repo.addCollaborators(github.getUser("abayer"),github.getUser("rtyler"));
|
||||
repo.delete();
|
||||
|
||||
Credential
|
||||
----
|
||||
|
||||
This library allows the caller to supply the credential as parameters, but it also defines a common convention
|
||||
so that applications using this library will look at the consistent location. In this convention, the library
|
||||
looks at `~/.github` property file, which should have the following two values:
|
||||
|
||||
login=kohsuke
|
||||
password=012345678
|
||||
|
||||
Alternatively, you can have just the OAuth token in this file:
|
||||
|
||||
oauth=4d98173f7c075527cb64878561d1fe70
|
||||
|
||||
OkHttp
|
||||
----
|
||||
This library comes with a pluggable connector to use different HTTP client implementations
|
||||
through `HttpConnector`. In particular, this means you can use [OkHttp](http://square.github.io/okhttp/),
|
||||
so we can make use of it's HTTP response cache.
|
||||
Making a conditional request against the GitHub API and receiving a 304 response
|
||||
[does not count against the rate limit](http://developer.github.com/v3/#conditional-requests).
|
||||
|
||||
The following code shows an example of how to set up persistent cache on the disk:
|
||||
|
||||
Cache cache = new Cache(cacheDirectory, 10 * 1024 * 1024); // 10MB cache
|
||||
GitHub gitHub = GitHubBuilder.fromCredentials()
|
||||
.withConnector(new OkHttpConnector(new OkUrlFactory(new OkHttpClient().setCache(cache))))
|
||||
.build();
|
||||
@@ -15,6 +15,7 @@
|
||||
<item name="Introduction" href="/index.html"/>
|
||||
<item name="Download" href="http://mvnrepository.com/artifact/${project.groupId}/${project.artifactId}"/>
|
||||
<item name="Source code" href="https://github.com/kohsuke/${project.artifactId}"/>
|
||||
<item name="Mailing List" href="https://groups.google.com/forum/#!forum/github-api"/>
|
||||
</menu>
|
||||
|
||||
<menu name="References">
|
||||
|
||||
17
src/test/java/Foo.java
Normal file
17
src/test/java/Foo.java
Normal file
@@ -0,0 +1,17 @@
|
||||
import org.kohsuke.github.GHRepository;
|
||||
import org.kohsuke.github.GitHub;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class Foo {
|
||||
public static void main(String[] args) throws Exception {
|
||||
Collection<GHRepository> lst = GitHub.connect().getUser("kohsuke").getRepositories().values();
|
||||
for (GHRepository r : lst) {
|
||||
System.out.println(r.getName());
|
||||
}
|
||||
System.out.println(lst.size());
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
package org.kohsuke;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.kohsuke.github.GHHook;
|
||||
import org.kohsuke.github.GHOrganization;
|
||||
import org.kohsuke.github.GHOrganization.Permission;
|
||||
import org.kohsuke.github.GHRepository;
|
||||
import org.kohsuke.github.GHTeam;
|
||||
import org.kohsuke.github.GitHub;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Unit test for simple App.
|
||||
*/
|
||||
public class AppTest extends TestCase {
|
||||
public void testCredentialValid() throws IOException {
|
||||
assertTrue(GitHub.connect().isCredentialValid());
|
||||
assertFalse(GitHub.connect("totally","bogus").isCredentialValid());
|
||||
}
|
||||
|
||||
public void tryOrgFork() throws Exception {
|
||||
GitHub gh = GitHub.connect();
|
||||
gh.getUser("kohsuke").getRepository("rubywm").forkTo(gh.getOrganization("jenkinsci"));
|
||||
}
|
||||
|
||||
public void tryGetTeamsForRepo() throws Exception {
|
||||
GitHub gh = GitHub.connect();
|
||||
Set<GHTeam> o = gh.getOrganization("jenkinsci").getRepository("rubywm").getTeams();
|
||||
System.out.println(o);
|
||||
}
|
||||
|
||||
public void testMembership() throws Exception {
|
||||
GitHub gitHub = GitHub.connect();
|
||||
Set<String> members = gitHub.getOrganization("jenkinsci").getRepository("violations-plugin").getCollaboratorNames();
|
||||
System.out.println(members.contains("kohsuke"));
|
||||
}
|
||||
|
||||
public void testMemberOrgs() throws Exception {
|
||||
GitHub gitHub = GitHub.connect();
|
||||
Set<GHOrganization> o = gitHub.getUser("kohsuke").getOrganizations();
|
||||
System.out.println(o);
|
||||
}
|
||||
|
||||
public void tryHook() throws Exception {
|
||||
GitHub gitHub = GitHub.connect();
|
||||
GHRepository r = gitHub.getMyself().getRepository("test2");
|
||||
GHHook hook = r.createWebHook(new URL("http://www.google.com/"));
|
||||
System.out.println(hook);
|
||||
|
||||
for (GHHook h : r.getHooks())
|
||||
h.delete();
|
||||
}
|
||||
|
||||
public void testApp() throws IOException {
|
||||
GitHub gitHub = GitHub.connect();
|
||||
|
||||
// GHRepository r = gitHub.connect().getOrganization("jenkinsci").createRepository("kktest4", "Kohsuke's test", "http://kohsuke.org/", "Everyone", true);
|
||||
// r.fork();
|
||||
|
||||
// tryDisablingIssueTrackers(gitHub);
|
||||
|
||||
// tryDisablingWiki(gitHub);
|
||||
|
||||
// GHPullRequest i = gitHub.getOrganization("jenkinsci").getRepository("sandbox").getPullRequest(1);
|
||||
// for (GHIssueComment c : i.getComments())
|
||||
// System.out.println(c);
|
||||
// System.out.println(i);
|
||||
|
||||
// gitHub.getMyself().getRepository("perforce-plugin").setEmailServiceHook("kk@kohsuke.org");
|
||||
|
||||
// tryRenaming(gitHub);
|
||||
// tryOrgFork(gitHub);
|
||||
|
||||
// testOrganization(gitHub);
|
||||
// testPostCommitHook(gitHub);
|
||||
|
||||
// tryTeamCreation(gitHub);
|
||||
|
||||
// t.add(gitHub.getMyself());
|
||||
// System.out.println(t.getMembers());
|
||||
// t.remove(gitHub.getMyself());
|
||||
// System.out.println(t.getMembers());
|
||||
|
||||
// GHRepository r = GitHub.connect().getOrganization("HudsonLabs").createRepository("auto-test", "some description", "http://kohsuke.org/", "Plugin Developers", true);
|
||||
|
||||
// r.
|
||||
// GitHub hub = GitHub.connectAnonymously();
|
||||
//// hub.createRepository("test","test repository",null,true);
|
||||
//// hub.getUser("kohsuke").getRepository("test").delete();
|
||||
//
|
||||
// System.out.println(hub.getUser("kohsuke").getRepository("hudson").getCollaborators());
|
||||
}
|
||||
|
||||
private void tryDisablingIssueTrackers(GitHub gitHub) throws IOException {
|
||||
for (GHRepository r : gitHub.getOrganization("jenkinsci").getRepositories().values()) {
|
||||
if (r.hasIssues()) {
|
||||
if (r.getOpenIssueCount()==0) {
|
||||
System.out.println("DISABLED "+r.getName());
|
||||
r.enableIssueTracker(false);
|
||||
} else {
|
||||
System.out.println("UNTOUCHED "+r.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void tryDisablingWiki(GitHub gitHub) throws IOException {
|
||||
for (GHRepository r : gitHub.getOrganization("jenkinsci").getRepositories().values()) {
|
||||
if (r.hasWiki()) {
|
||||
System.out.println("DISABLED "+r.getName());
|
||||
r.enableWiki(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void tryUpdatingIssueTracker(GitHub gitHub) throws IOException {
|
||||
GHRepository r = gitHub.getOrganization("jenkinsci").getRepository("lib-task-reactor");
|
||||
System.out.println(r.hasIssues());
|
||||
System.out.println(r.getOpenIssueCount());
|
||||
r.enableIssueTracker(false);
|
||||
}
|
||||
|
||||
private void tryRenaming(GitHub gitHub) throws IOException {
|
||||
gitHub.getUser("kohsuke").getRepository("test").renameTo("test2");
|
||||
}
|
||||
|
||||
private void tryOrgFork(GitHub gitHub) throws IOException {
|
||||
GHOrganization o = gitHub.getOrganization("HudsonLabs");
|
||||
System.out.println(gitHub.getUser("rtyler").getRepository("memcache-ada").forkTo(o).getUrl());
|
||||
}
|
||||
|
||||
private void tryTeamCreation(GitHub gitHub) throws IOException {
|
||||
GHOrganization o = gitHub.getOrganization("HudsonLabs");
|
||||
GHTeam t = o.createTeam("auto team", Permission.PUSH);
|
||||
t.add(o.getRepository("auto-test"));
|
||||
}
|
||||
|
||||
private void testPostCommitHook(GitHub gitHub) throws IOException {
|
||||
GHRepository r = gitHub.getMyself().getRepository("foo");
|
||||
Set<URL> hooks = r.getPostCommitHooks();
|
||||
hooks.add(new URL("http://kohsuke.org/test"));
|
||||
System.out.println(hooks);
|
||||
hooks.remove(new URL("http://kohsuke.org/test"));
|
||||
System.out.println(hooks);
|
||||
}
|
||||
|
||||
public void testOrganization() throws IOException {
|
||||
GitHub gitHub = GitHub.connect();
|
||||
GHOrganization j = gitHub.getOrganization("jenkinsci");
|
||||
GHTeam t = j.getTeams().get("Core Developers");
|
||||
|
||||
assertNotNull(j.getRepository("jenkins"));
|
||||
|
||||
// t.add(labs.getRepository("xyz"));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user