Compare commits

...

404 Commits

Author SHA1 Message Date
Kohsuke Kawaguchi
336924ef23 [maven-release-plugin] prepare release github-api-1.87 2017-09-09 08:06:18 -07:00
Kohsuke Kawaguchi
ad28ca4a90 Use string constant like other previews 2017-09-09 07:59:31 -07:00
Kohsuke Kawaguchi
aebbe86cfc Keeping findbugs happy 2017-09-09 07:57:49 -07:00
Kohsuke Kawaguchi
df9faf4943 Auto-retry flaky tests 2017-09-08 16:09:41 -07:00
Kohsuke Kawaguchi
3e295b6be4 Merge branch 'master' of github.com:kohsuke/github-api 2017-09-08 15:52:31 -07:00
Kohsuke Kawaguchi
8dd6dbf995 getRef never returns null 2017-09-08 15:52:11 -07:00
Kohsuke Kawaguchi
612139f2ff Merge pull request #375 from stephenc/tag-object-support
Add basic support for tag objects
2017-09-08 15:49:33 -07:00
Kohsuke Kawaguchi
240bcabb76 Merge pull request #351 2017-09-08 14:16:45 -07:00
Kohsuke Kawaguchi
cda27d5963 This field should be still final 2017-09-08 14:16:09 -07:00
Kohsuke Kawaguchi
2f8c3997f7 Restored signature of the previous enableProtection() method 2017-09-08 14:13:21 -07:00
Kohsuke Kawaguchi
46b89a48db Merge Pull request #369 2017-09-08 14:06:22 -07:00
Kohsuke Kawaguchi
5c9cbee2f9 Merge pull request #332 from sebkur/fix_javadoc
Fix a bug in the Javadocs (due to copy and paste)
2017-09-08 10:39:58 -07:00
Kohsuke Kawaguchi
ee8973c239 Merge pull request #338 from sebkur/ignore-eclipse-files
Ignore eclipse files
2017-09-08 10:39:39 -07:00
Kohsuke Kawaguchi
2e2813f363 Merge pull request #343 from kamontat/feature/latest-release
add latest release
2017-09-08 10:39:13 -07:00
Kohsuke Kawaguchi
7ceca0769f Merge pull request #363 from PauloMigAlmeida/master
Add missing event types used by repository webhooks
2017-09-08 10:36:33 -07:00
Kohsuke Kawaguchi
4d277cc61f Merge pull request #352 from stephenc/pr-reviews
Add support for PR reviews preview
2017-09-08 10:35:58 -07:00
Kohsuke Kawaguchi
e67fbb4621 Merge pull request #362 from KostyaSha/pingHook
Add ping hook method
2017-09-08 10:34:45 -07:00
Kohsuke Kawaguchi
29b5357ceb Merge pull request #358 from jglick/no-preview-JENKINS-36240
[JENKINS-36240] /repos/:owner/:repo/collaborators/:username/permission no longer requires korra preview
2017-09-08 10:33:31 -07:00
Stephen Connolly
9dabec107b Add basic support for tag objects 2017-09-01 12:52:15 +01:00
Jae Gangemi
23cd51a6da - improved branch protection support 2017-08-08 16:17:58 -06:00
Jae Gangemi
7396395f90 - updated ignore entries for eclipse/moc os 2017-08-08 14:31:11 -06:00
Paulo Miguel Almeida
4abe87036c Add missing event types used by repository webhooks 2017-07-14 04:23:08 +00:00
Kanstantsin Shautsou
8d1b44db97 Add ping hook method 2017-07-10 02:39:03 +03:00
Kohsuke Kawaguchi
f2fe8eaf86 [maven-release-plugin] prepare for next development iteration 2017-07-02 17:08:35 -07:00
Kohsuke Kawaguchi
46715cac08 [maven-release-plugin] prepare release github-api-1.86 2017-07-02 17:08:24 -07:00
Kohsuke Kawaguchi
0f21eba57f Merge pull request #359 from jglick/SocketTimeoutException-JENKINS-45142
[JENKINS-45142] Retry connections after getting SocketTimeoutException
2017-06-29 17:13:31 -04:00
Jesse Glick
cb7620395a [JENKINS-45142] Retry connections after getting SocketTimeoutException. 2017-06-28 17:09:54 -04:00
Jesse Glick
3a40af8871 [JENKINS-36240] /repos/:owner/:repo/collaborators/:username/permission no longer requires korra preview. 2017-06-12 10:28:25 -04:00
mdeverdelhan
c9b5074bc4 Fix the wrapping of retrieved commits (owner/repository) 2017-05-11 12:32:34 +02:00
Stephen Connolly
44d4d0d767 Add support for PR reviews preview 2017-03-30 12:17:06 +01:00
mdeverdelhan
64af13f40d Add the Commit search API (still in preview) 2017-03-30 12:55:55 +02:00
Kohsuke Kawaguchi
e9b59c6bef [maven-release-plugin] prepare for next development iteration 2017-02-28 21:06:14 -08:00
Kohsuke Kawaguchi
3d03659508 [maven-release-plugin] prepare release github-api-1.85 2017-02-28 21:06:08 -08:00
Kohsuke Kawaguchi
0374d2de48 Merge pull request #346 from stephenc/close-the-connections
Ensure that connections are closed for error responses
2017-02-27 12:33:18 -08:00
Stephen Connolly
2627dc5ee4 Ensure that connections are closed for error responses
- This was endless fun to trace, but I found it at last. This should
stop the `WARNING: A connection to https://api.github.com/ was leaked.
Did you forget to close a response body?` messages in the logs when
using the OkHttpConnector.
2017-02-23 12:52:29 +00:00
kamontat
5554332b5b add return null if latest release not found 2017-02-20 10:20:53 +07:00
kamontat
1cffea892b add test 2017-02-20 10:20:08 +07:00
kamontat
fd859815b0 fixed indent, rename to getLastestRelease 2017-02-20 09:36:28 +07:00
kamontat
166e26d101 add latest release 2017-02-19 23:57:44 +07:00
Sebastian Kürten
429b26cee8 Ignore eclipse files 2017-02-09 18:18:08 +01:00
Sebastian Kürten
75f0c08ca4 Fix a bug in the Javadocs (due to copy and paste) 2017-01-23 12:21:13 +01:00
Kohsuke Kawaguchi
1f4325e7db Merge pull request #329 from stephenc/patch-1
Correct algebra in #327
2017-01-16 18:31:33 -08:00
Stephen Connolly
f2e7b40425 Correct algebra in #327 2017-01-10 10:15:04 +00:00
Kohsuke Kawaguchi
4ee3086b6d [maven-release-plugin] prepare for next development iteration 2017-01-09 16:50:15 -08:00
Kohsuke Kawaguchi
a3a715c3ba [maven-release-plugin] prepare release github-api-1.84 2017-01-09 16:50:08 -08:00
Kohsuke Kawaguchi
6cad4a3c33 static import for conciseness 2017-01-09 16:43:03 -08:00
Kohsuke Kawaguchi
b9b6f4fd44 INFO level logging is harmful as it's reported to stdout by default 2017-01-09 16:42:03 -08:00
Kohsuke Kawaguchi
17d1994a53 [maven-release-plugin] prepare for next development iteration 2017-01-09 16:38:02 -08:00
Kohsuke Kawaguchi
13184e72e1 [maven-release-plugin] prepare release github-api-1.83 2017-01-09 16:37:56 -08:00
Kohsuke Kawaguchi
911e8d21a7 Made the test case runnable, at least for me 2017-01-09 16:26:58 -08:00
Kohsuke Kawaguchi
5b69a2925f Merge pull request #324 2017-01-09 16:18:48 -08:00
Kohsuke Kawaguchi
d1c900a620 Marking the fact that these APIs are still in preview and subject to change 2017-01-09 16:18:40 -08:00
Kohsuke Kawaguchi
6bfeb54f3c Just exposing permission type enum until there's more to this relation than one property 2017-01-09 16:14:30 -08:00
Kohsuke Kawaguchi
198fede915 Merge pull request #325 2017-01-09 16:06:14 -08:00
Kohsuke Kawaguchi
1212ae3eb3 Touch up for uniformity
- Prefer typed 'URL' over 'String' that is URL
- Mark API as @Preview to communicate that this is subject to change

More branch protection stuff needs to be added. See https://developer.github.com/v3/repos/branches/
2017-01-09 16:06:05 -08:00
Kohsuke Kawaguchi
1266dcc0c7 Merge pull request #327 from stephenc/expose-rate-limit-headers
Expose Rate Limit Headers
2017-01-10 08:57:07 +09:00
Stephen Connolly
6fcddf4a47 Some usage patterns require more pro-active rate limit queries 2017-01-05 10:25:40 +00:00
Stephen Connolly
dfea424b94 Expose the API url used by the GitHub 2017-01-05 09:36:58 +00:00
Stephen Connolly
9d03435aa1 Expose Rate Limit Headers
Exposes the rate limit header responses so that consumers of the API can proactively tune their usage
2017-01-05 09:21:32 +00:00
Jeffrey.Nelson
26c20a7a22 add branch protection attributes 2016-12-22 11:55:01 -06:00
Kohsuke Kawaguchi
470da06ecf Cleaning up javadoc warnings 2016-12-17 08:17:20 -08:00
Kohsuke Kawaguchi
a746a310bc [maven-release-plugin] prepare for next development iteration 2016-12-17 07:50:55 -08:00
Kohsuke Kawaguchi
3dbb516084 [maven-release-plugin] prepare release github-api-1.82 2016-12-17 07:50:48 -08:00
Kohsuke Kawaguchi
32177283b3 Fixed issue #317
There's no need for the library to replicate a logic when GitHub does
that (and does that correctly.)

Looking at the commit history, I couldn't see why this was added in the
first place.
2016-12-17 07:30:14 -08:00
Kohsuke Kawaguchi
3a66e90b7a Fixed issue #319
getApiUrl() is unreliable given that we collapse issue & PR into one
object.
2016-12-17 07:28:28 -08:00
Kohsuke Kawaguchi
a454fb10ec Tree traversal from commit & its associated tests 2016-12-17 07:21:31 -08:00
Kohsuke Kawaguchi
b5386a35ee Defining better traversal methods for apps that walk trees 2016-12-17 07:09:29 -08:00
Kohsuke Kawaguchi
11651da411 not always a blob, for example it could be a tree. 2016-12-17 07:04:31 -08:00
Kohsuke Kawaguchi
88d52c44ad Recording parent GHRepository 2016-12-17 07:03:40 -08:00
Kohsuke Kawaguchi
2d7d4bbd4e Merge pull request #320 with some additional changes 2016-12-17 06:56:41 -08:00
Kohsuke Kawaguchi
e6ee278fde Another version that directly reads BLOB without going through an intermediate object. 2016-12-17 06:55:44 -08:00
Kohsuke Kawaguchi
6380cf9ed0 Added a method to retrieve the actual bytes of BLOB
... which is probably more useful than the getContent() method
2016-12-17 06:47:24 -08:00
Kohsuke Kawaguchi
2e78dc52c7 Merge pull request #323 from jglick/bad-json
Fix syntactically malformed test JSON
2016-12-17 06:24:43 -08:00
Jesse Glick
ccb42d3249 [JENKINS-36240] Added GHRepository.getPermission(String). 2016-12-16 18:02:28 -05:00
Jesse Glick
c5009ab44b Fix syntactically malformed test JSON. 2016-12-16 15:04:10 -05:00
Jeff Nelson
9d15cd43a3 Merge pull request #1 from kohsuke/master
catchup
2016-12-14 12:12:57 -06:00
Kanstantsin Shautsou
0780e10fa2 Added ghRepo.getBlob(String) method
Signed-off-by: Kanstantsin Shautsou <kanstantsin.sha@gmail.com>
2016-12-13 15:28:08 +03:00
Kohsuke Kawaguchi
0731f63237 Added order parameter 2016-11-26 14:45:03 -08:00
Kohsuke Kawaguchi
a29896042b Merge pull request #315 from davidxia/dxia/patch1
Fix typos in javadocs
2016-11-26 14:38:45 -08:00
David Xia
68ebc08c9d Fix typos in javadocs
Replace "pagenated" with "paginated".
2016-11-26 00:44:52 -05:00
Kohsuke Kawaguchi
a1df526f93 [maven-release-plugin] prepare for next development iteration 2016-11-21 08:53:42 -08:00
Kohsuke Kawaguchi
0023ecefa4 [maven-release-plugin] prepare release github-api-1.81 2016-11-21 08:53:38 -08:00
Kohsuke Kawaguchi
511f156603 Added the membership API for the authenticated user. 2016-11-19 15:26:04 -08:00
Kohsuke Kawaguchi
3f223b1ba0 Support assignees when creating a new issue 2016-11-19 14:50:47 -08:00
Kohsuke Kawaguchi
a1528a1a63 API to add/set/remove assignees from an issue 2016-11-19 14:48:43 -08:00
Kohsuke Kawaguchi
b8bfddbf3a Code simplification 2016-11-19 14:30:35 -08:00
Kohsuke Kawaguchi
47fc813027 Assignees of the repository.
(Personally this concept makes no sense for me, so I don't know what this API really does. I'm just following their API docs)
2016-11-19 14:29:27 -08:00
Kohsuke Kawaguchi
c7f2228a44 [maven-release-plugin] prepare for next development iteration 2016-11-16 22:52:14 -08:00
Kohsuke Kawaguchi
b0e0f045f8 [maven-release-plugin] prepare release github-api-1.80 2016-11-16 22:52:10 -08:00
Kohsuke Kawaguchi
1296514794 this field is not yet used 2016-11-16 22:49:13 -08:00
Kohsuke Kawaguchi
18e797095f rewrote assert with JUnit ones 2016-11-16 22:36:16 -08:00
Kohsuke Kawaguchi
85aa2ad4e6 Added reaction API 2016-11-16 19:10:37 -08:00
Kohsuke Kawaguchi
818f6dc045 Issue #309: Added user listing 2016-11-16 18:26:49 -08:00
Kohsuke Kawaguchi
1c162c6390 Merge pull request #304 2016-11-16 18:19:26 -08:00
Kohsuke Kawaguchi
def3a28fb5 Restoring backward compatibility of names 2016-11-16 18:18:45 -08:00
Kohsuke Kawaguchi
d1378a0236 Merge pull request #306 from stephenc/offline-support
Add offline support to the API to make parsing events easier
2016-11-16 18:12:39 -08:00
Stephen Connolly
e544c7a65a Fix the push event payload 2016-11-14 12:52:22 +00:00
Kanstantsin Shautsou
24f48f668c Add portion of auth/application API. (#307)
* Add portion of auth/application API.

Signed-off-by: Kanstantsin Shautsou <kanstantsin.sha@gmail.com>

* fixup
2016-11-11 15:56:03 +01:00
Stephen Connolly
9988a090ac Add some more tests 2016-11-11 14:27:09 +00:00
Stephen Connolly
d36e145d06 Need to be able to tell if this is a creation / deletion of a ref for multibranch projects 2016-11-11 14:18:47 +00:00
Stephen Connolly
498d63ea00 Typos spotted by Jesse 2016-11-08 15:48:39 +00:00
Stephen Connolly
7dc620a3ba More details emerge on the PingEvent payload 2016-11-08 15:29:27 +00:00
Stephen Connolly
66145e1d23 Seems there is an undocumented but important PING event used by github-plugin 2016-11-08 15:26:33 +00:00
Stephen Connolly
7bf8621afe Need the pusher details for github-plugin 2016-11-08 15:14:42 +00:00
Stephen Connolly
ce3f74232e Ensure a use case required by github-plugin is valid 2016-11-08 15:11:35 +00:00
Stephen Connolly
5b92d4b88c Fix findbugs false alarms 2016-11-08 15:04:02 +00:00
Stephen Connolly
4daf6ba057 Add offline support to the API to make parsing events easier
- When we receive events from a webhook, it is non-trivial to determine which GitHub instance the event came from
  or for that matter even if the event actually came from GitHub or GitHub Enterprise.
- In order to ensure that the logic for parsing events does not get replicated in clients, we need to be
  able to call GitHub.parseEventPayload(Reader,Class) without knowing which GitHub the event originates from
  and without the resulting objects triggering API calls back to a GitHub
- Thus we add GitHub.offline() to provide an off-line connection
- Thus we modify some of the object classes to return best-effort objects when off-line
- Add support for more of the event types into GHEventPayload
- Add tests of the event payload and accessing critical fields when using GitHub.offline()
2016-11-08 12:56:52 +00:00
Jason Song
955e9899af Fix fields of GHRepository 2016-11-04 00:09:59 +08:00
Kohsuke Kawaguchi
fa3d0887ef [maven-release-plugin] prepare for next development iteration 2016-10-24 19:23:18 -07:00
Kohsuke Kawaguchi
5d5c6cf71c [maven-release-plugin] prepare release github-api-1.79 2016-10-24 19:23:14 -07:00
Kohsuke Kawaguchi
89aac45f41 Merge pull request #299 2016-10-24 19:15:46 -07:00
Kohsuke Kawaguchi
4965fd5f4c Noting possible TODO for the future 2016-10-24 19:15:38 -07:00
Kohsuke Kawaguchi
87fbb8ec98 Copy/paste error 2016-10-24 19:07:32 -07:00
Kohsuke Kawaguchi
0d92d4ba61 [maven-release-plugin] prepare for next development iteration 2016-10-24 14:10:35 -07:00
Kohsuke Kawaguchi
b0df93bbcb [maven-release-plugin] prepare release github-api-1.78 2016-10-24 14:10:30 -07:00
Kohsuke Kawaguchi
290d0b226a Merge pull request #295 from jglick/pageSize
Use maximum permitted page size
2016-10-24 14:04:05 -07:00
Kohsuke Kawaguchi
8b3469610c Merge pull request #300 from stephenc/commit-dates
Expose the commit dates
2016-10-24 14:03:14 -07:00
Stephen Connolly
50b47fb73b Expose the commit dates 2016-10-24 21:12:56 +01:00
Ben Sheats
5334cb8688 url encode hashes in ref names 2016-10-21 11:40:01 -04:00
Jesse Glick
38983df42d If we are a returning a collection of all things, we might as well use the maximum page size to minimize HTTP requests. 2016-09-19 09:48:23 -07:00
Kohsuke Kawaguchi
df963cb71c [maven-release-plugin] prepare for next development iteration 2016-08-05 21:32:15 -07:00
Kohsuke Kawaguchi
e9368fb04e [maven-release-plugin] prepare release github-api-1.77 2016-08-05 21:32:10 -07:00
Kohsuke Kawaguchi
e2a1630cf4 findbug taming 2016-08-05 21:28:54 -07:00
Kohsuke Kawaguchi
4f15b7c9fa NPE fix. type can be null 2016-08-05 21:19:32 -07:00
Kohsuke Kawaguchi
63f500ad7f Fixed issue #286
List commit API (https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository) already populates short info, and so populate() call could be excessive.

 It's possible that the short info is always available and therefore there's never a need to call populate(), but that assumption is hard to test, so I'm leaving that in
2016-08-05 21:11:00 -07:00
Kohsuke Kawaguchi
a9fb4546e1 Constants for preview media types 2016-08-05 20:56:11 -07:00
Kohsuke Kawaguchi
cabbbf7f02 Handle 404 that represents "no license" 2016-08-05 20:50:39 -07:00
Kohsuke Kawaguchi
59324b0082 Add preview media type header explicitly 2016-08-05 20:47:08 -07:00
Kohsuke Kawaguchi
6a356c82a5 Fixed up tests 2016-08-05 20:44:45 -07:00
Kohsuke Kawaguchi
70f0f5714a While a use of custom HttpConnector is clever, it doesn't fit the current idiom of this library. 2016-08-05 20:44:10 -07:00
Kohsuke Kawaguchi
07b527a0f2 Enumeration in GitHub should be PagedIterable 2016-08-05 20:40:34 -07:00
Kohsuke Kawaguchi
80aa75aab1 Added the wrap() method for a backpointer 2016-08-05 20:40:23 -07:00
Kohsuke Kawaguchi
0cf4211aa5 Merged GHLicense & GHLicenseBase
Elsewhere in this library, whenever there are multiple forms of the same
object, we map that to the same class and use lazy data retrieval to
fill missing fields.
2016-08-05 20:36:46 -07:00
Kohsuke Kawaguchi
1de02a5099 Added a marker for preview APIs 2016-08-05 20:19:36 -07:00
Duncan Dickinson
bb1cecb95b PR-284: license API support
Had to do git-diff | git-apply to avoid whitespe changes to GHRepository
2016-08-05 20:11:33 -07:00
Kohsuke Kawaguchi
d82397a173 doc fix 2016-08-05 20:00:05 -07:00
Kohsuke Kawaguchi
856cf5e568 Better type safety by splitting RateLimitHandler and AbuseLimitHandler
While the signature is the same, headers that they expect are different,
so any non-trivial logic cannot be reused.
2016-08-05 19:58:04 -07:00
Matt Mitchell
9f5a6ee549 Implement an abuse handler
If too many requests are made within X amount of time (not the traditional hourly rate limit), github may begin returning 403.  Then we should wait for a bit to attempt to access the API again.  In this case, we parse out the Retry-After field returned and sleep until that (it's usually 60 seconds)
2016-07-22 13:16:12 -07:00
Kohsuke Kawaguchi
a2f0837d14 Issue #279: added another overload that takes permission 2016-06-03 20:56:17 -07:00
Kohsuke Kawaguchi
c7f6889534 Issue #258: updated OkHttp that handles Cache control headers better 2016-06-03 20:31:12 -07:00
Kohsuke Kawaguchi
0415326d09 Issue #261: handle 204 no content correctly 2016-06-03 20:27:29 -07:00
Kohsuke Kawaguchi
16a0f8ece0 [maven-release-plugin] prepare for next development iteration 2016-06-03 00:19:15 -07:00
Kohsuke Kawaguchi
5d1ef296b3 [maven-release-plugin] prepare release github-api-1.76 2016-06-03 00:19:09 -07:00
Kohsuke Kawaguchi
16dbcde90b Shut up FindBugs! 2016-06-03 00:16:20 -07:00
Kohsuke Kawaguchi
50cbf25c72 Shut up FindBugs 2016-06-03 00:09:25 -07:00
Kohsuke Kawaguchi
7b87de2b4c Giving it a bit of delay in the hope that it removes flakiness of tests 2016-06-03 00:04:36 -07:00
Kohsuke Kawaguchi
1ce54a7925 Bug fix in toString() 2016-06-02 23:55:55 -07:00
Kohsuke Kawaguchi
1bfe7dd99b Issue #264: wait for the repo to finish forking 2016-06-02 23:50:18 -07:00
Kohsuke Kawaguchi
27e855ddbd Issue 262: added support for branch protection API 2016-06-02 23:40:37 -07:00
Kohsuke Kawaguchi
3d1bed0f8f In JDK I'm using (Java8), I get a delegating HttpURLConnection that breaks the hack to set the method.
This change makes this hack even worse, but this is the only way I can think of, since I cannot update HttpURLConnection.methods that is static final.
2016-06-02 23:40:14 -07:00
Kohsuke Kawaguchi
5c9ea9b63a Added a fluent version 2016-06-02 23:27:52 -07:00
Kohsuke Kawaguchi
7c034f5670 Doc improvement 2016-06-02 22:43:58 -07:00
Kohsuke Kawaguchi
3b9f5a417a Formatting changes 2016-06-02 22:43:06 -07:00
Kohsuke Kawaguchi
cde501af8d More meaningful toString() method
Produce toString without dilligently adding it to every single class.
Rely on heuristics to cut down the number of fields to show.
2016-06-02 22:38:38 -07:00
Kohsuke Kawaguchi
3c5592c1c8 Following the convention with GHMyself.getEmails2()
This way the method is more discoverable with IDE auto-completion
2016-06-02 22:05:24 -07:00
Kohsuke Kawaguchi
2508e022bb Merge pull request #272 from noctarius/master
Added support for the extended stargazers API in Github V3 API
2016-06-03 14:04:24 +09:00
Kohsuke Kawaguchi
01fcbc24e8 Merge pull request #282 from apemberton/org-fix
related to JENKINS-34834. updating test for similar condition
2016-06-03 13:52:26 +09:00
Kohsuke Kawaguchi
204e639679 Merge pull request #281 from apemberton/master
Add Slug to GHTeam per v3 API: https://developer.github.com/v3/orgs/t…
2016-06-03 13:51:47 +09:00
Kohsuke Kawaguchi
3d301ec730 Merge pull request #278 from jglick/javadoc
Fixed broken link
2016-06-03 13:50:37 +09:00
Kohsuke Kawaguchi
9ab6d57019 Merge pull request #277 from rhels/patch-1
Updated Date was wrong
2016-06-03 13:50:22 +09:00
Kohsuke Kawaguchi
37c473130f Merge pull request #276 from thug-gamer/patch-1
Add support to delete a team
2016-06-03 13:50:11 +09:00
Andy Pemberton
5f95987a48 related to JENKINS-34834. updating test for similar condition 2016-05-14 20:07:04 -04:00
Andy Pemberton
d530b34073 Add Slug to GHTeam per v3 API: https://developer.github.com/v3/orgs/teams/ 2016-05-14 08:58:42 -04:00
Jesse Glick
35dec7a5ec Fixed broken link. 2016-05-03 18:19:57 -04:00
Konda Reddy
baedad8124 Updated Date was wrong 2016-04-30 13:20:44 -05:00
thug-gamer
007378c3a6 Add support to delete a team
Add a method to delete a team.
2016-04-29 11:41:14 +02:00
noctarius
beae9fd6ec Added support for the extended stargazers API in Github V3 API 2016-04-14 07:22:17 +02:00
Kohsuke Kawaguchi
b7507076c6 Merge pull request #270 from szpak/issue/269-reopenMilestone
[#269] Add reopen method on GHMilestone
2016-04-13 16:15:39 -07:00
Kohsuke Kawaguchi
6b5ade3ca0 [maven-release-plugin] prepare for next development iteration 2016-04-13 13:08:02 -07:00
Kohsuke Kawaguchi
715192d26c [maven-release-plugin] prepare release github-api-1.75 2016-04-13 13:07:58 -07:00
Marcin Zajaczkowski
ce140460af [#269] Add reopen method on GHMilestone 2016-04-04 17:54:55 +02:00
Kohsuke Kawaguchi
d30b0403ce [maven-release-plugin] prepare for next development iteration 2016-03-18 19:22:37 -07:00
Kohsuke Kawaguchi
255c993548 [maven-release-plugin] prepare release github-api-1.74 2016-03-18 19:22:34 -07:00
Kohsuke Kawaguchi
557ae4165c Not important 2016-03-18 19:19:51 -07:00
Kohsuke Kawaguchi
a31395ed80 Signature fix for Java5 2016-03-18 19:15:52 -07:00
Kohsuke Kawaguchi
397886d289 Excluding a flaky test 2016-03-18 19:08:51 -07:00
Kohsuke Kawaguchi
7307bec2ae Merge pull request #259 from Shredder121/animal-sniffer
Animal sniffer
2016-03-18 18:27:22 -07:00
Ruben Dijkstra
0cd5147e1a Java 5 doesn't have TimeUnit.HOURS
http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/TimeUnit.html
2016-03-12 22:01:25 +01:00
Ruben Dijkstra
36d5b092d7 Java 5 doesn't have new String(byte[], Charset)
http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/String.html
2016-03-12 21:59:51 +01:00
Ruben Dijkstra
f9014dbab3 Convert to legacy Throwable.initCause() 2016-03-12 21:58:27 +01:00
Ruben Dijkstra
755d5f77ea Java 5 doesn't have Arrays.copyOf()
http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Arrays.html
2016-03-12 21:56:56 +01:00
Ruben Dijkstra
906d9af7b7 Include the animal sniffer plugin
7a78f9f5aa set the compiler level back to 5, but there are no guarantees that we don't accidentally use any features from newer class libraries.
2016-03-12 21:48:50 +01:00
Kohsuke Kawaguchi
14dcb37ee1 Not all caller wants GET 2016-03-11 23:29:58 -08:00
Kohsuke Kawaguchi
c1c2a27358 Doc improvement 2016-03-11 23:21:07 -08:00
Kohsuke Kawaguchi
5ab9657f9c Merge branch 'master' of github.com:kohsuke/github-api 2016-03-11 23:16:37 -08:00
Kohsuke Kawaguchi
7a78f9f5aa No need to require 1.6 2016-03-11 23:16:24 -08:00
Kohsuke Kawaguchi
ac8c65f062 Merge pull request #251
Conflicts:
	src/main/java/org/kohsuke/github/GitHub.java
2016-03-11 23:14:01 -08:00
Kohsuke Kawaguchi
1954a9f3f8 This isn't just about API URL but it also checks the valid credential 2016-03-11 23:12:50 -08:00
Kohsuke Kawaguchi
3b764f9c90 Don't lose the original problem 2016-03-11 23:11:22 -08:00
Kohsuke Kawaguchi
10f55cc549 Checking another header
I think it's better to pick a header that's unique to GitHub.
2016-03-11 23:10:41 -08:00
Kohsuke Kawaguchi
cd8d955646 Documenting what one gets 2016-03-11 23:06:21 -08:00
Kohsuke Kawaguchi
bba07c9080 Merge pull request #253 from cyrille-leclerc/fix-infinite-loop
Fix #252: infinite loop because the "hypertext engine" generates invalid URLs
2016-03-11 22:59:00 -08:00
Kohsuke Kawaguchi
dba84a33b9 Logger should be static 2016-03-11 22:55:58 -08:00
Kohsuke Kawaguchi
ae49166aa2 No such parameter exists on this method 2016-03-11 22:55:44 -08:00
Kohsuke Kawaguchi
e09185fd0e Logger should be static 2016-03-11 22:51:48 -08:00
Cyrille Le Clerc
56379bb3b9 Fix broken log message in GitHub.java and cleanup code as recommended by @jglick 2016-03-07 19:25:42 +01:00
Cyrille Le Clerc
027e4b4f25 Better error message: introduce HttpException, subclass of IOException with url, http responseCode and http responseMessage to help exception handling. 2016-03-06 18:34:27 +01:00
Cyrille Le Clerc
ba951cb6e3 Fix #252: infinite loop because the "hypertext engine" may duplicate '?' generating invalid "https://api.github.com/notifications?all=true&page=2?all=true" instead of "https://api.github.com/notifications?all=true&page=2&all=true". A better fix will be to prevent duplication of parameters ("all=true" in this case). 2016-03-06 18:27:05 +01:00
Manuel Recena
ae85cf4b6c Improve checkApiUrlValidity() method to support the private mode in GitHub Enterprise servers 2016-03-05 17:42:25 +01:00
Kohsuke Kawaguchi
dbc79f8c42 Fixing issue raised in https://github.com/kohsuke/github-api/pull/247
From Shredder121,
--------------------
Only the HttpURLConnection.method was set by that change. Not the
Requester.method.

This means that Requester.method is still set to POST, isMethodWithBody
will return true, and uc.setDoOutput(true) will be called.

I use Okhttp, and their HttpURLConnectionImpl's method changes to POST
if you tell it to open a stream to write to.
2016-03-01 19:46:50 -08:00
Kohsuke Kawaguchi
54c3070607 [maven-release-plugin] prepare for next development iteration 2016-02-29 21:03:34 -08:00
Kohsuke Kawaguchi
013eaa30b6 [maven-release-plugin] prepare release github-api-1.73 2016-02-29 21:03:31 -08:00
Kohsuke Kawaguchi
751043bf81 change in the markup generated 2016-02-29 21:01:18 -08:00
Kohsuke Kawaguchi
14f7198a07 Handle "all" webhook correctly
This fixes #250
2016-02-29 20:56:47 -08:00
Kohsuke Kawaguchi
94af819ae5 Merge pull request #249 from zapelin/master
Added getHtmlUrl() to GHCommit
2016-02-29 20:48:19 -08:00
Kohsuke Kawaguchi
dbcc9afbc7 Merge pull request #248 from daniel-beck/populate-commit
Populate commit with data for getCommitShortInfo
2016-02-29 20:47:52 -08:00
Kohsuke Kawaguchi
8556033ae6 Merge pull request #245 from daniel-beck/email-hook-error
Fix error when creating email service hook
2016-02-29 20:45:40 -08:00
Kohsuke Kawaguchi
650493f863 Merge pull request #244 from benbek/patch-1
Minor amendment to the documentation
2016-02-29 19:57:47 -08:00
Kohsuke Kawaguchi
d80ad77871 Use builder pattern to support all the other options 2016-02-29 19:57:16 -08:00
Artem Gubanov
f4b129b9f1 Added getHtmlUrl() to GHCommit 2016-02-25 10:46:17 +02:00
Daniel Beck
c0a05e0650 Populate commit with data for getCommitShortInfo 2016-02-21 01:26:43 +01:00
Daniel Beck
33d95d3e3a Fix error when creating email service hook 2016-01-20 23:30:10 +01:00
benbek
f573f83fb9 Amendment to the documentation
The status reported by GitHub for deleting a file is actually "removed", not "deleted".
2016-01-19 00:05:01 +02:00
Daniel Lovera
e94c36b7e6 clean: remove unused import 2015-12-13 21:05:36 +01:00
Daniel Lovera
c879e9e34d Support for auto_init parameter in organization
The GitHub api auto_init parameter allows to initialize created repository with a readme file.
Add createRepository methods in GHOrganization using auto_init parameter. Already existing createRepository methods use auto_init parameter as false for retro-compatibility.
2015-12-13 21:00:40 +01:00
Daniel Lovera
ac39b564a8 Support for auto_init parameter
The GitHub api auto_init parameter allows to initialize created repository with a readme file.
Add a createRepository method using auto_init parameter. Already existing createRepository method uses auto_init parameter as false for retro-compatibility.
2015-12-13 20:59:49 +01:00
Kohsuke Kawaguchi
d91388aba4 [maven-release-plugin] prepare for next development iteration 2015-12-10 07:00:06 -08:00
Kohsuke Kawaguchi
733d78abdd [maven-release-plugin] prepare release github-api-1.72 2015-12-10 06:59:37 -08:00
Kohsuke Kawaguchi
d5809e375c Simplification via enum handling in 'req.with' 2015-12-10 06:33:49 -08:00
Kohsuke Kawaguchi
2440a676bd Added more comprehensive API to list pull requests
This fixes issue #234
2015-12-10 06:26:04 -08:00
Kohsuke Kawaguchi
03ac6c72e7 Formatting change 2015-12-10 06:01:34 -08:00
Kohsuke Kawaguchi
1bbbcabae0 Making API flow better 2015-12-10 05:56:08 -08:00
Kohsuke Kawaguchi
9149b6b998 GHCommit might be only partially populated.
This fixes issue #230
2015-12-10 05:53:55 -08:00
Kohsuke Kawaguchi
2603b5a402 Added stargazers and stars 2015-12-03 17:55:06 +01:00
Kohsuke Kawaguchi
dbddf5b9eb Implemented pagenation size support. 2015-12-03 17:41:43 +01:00
Kohsuke Kawaguchi
841f77bac2 Naming anonymous iterator.
... in anticipation of the page size support.
2015-12-03 16:33:39 +01:00
Kohsuke Kawaguchi
83ffe75baa Fixed rate handling limit handling
Issue #220. If RateLimitHandler returns normally, it should retry.
2015-12-02 12:03:39 +01:00
Kohsuke Kawaguchi
8cb7094803 Added pagination for following & follower 2015-12-02 08:35:56 +01:00
Kohsuke Kawaguchi
ed8cd0ad19 Added getter for ID
And the actual value has already gone past 'int'

This fixes issue #199
2015-12-01 17:02:53 +01:00
Kohsuke Kawaguchi
90d8e65a3b [maven-release-plugin] prepare for next development iteration 2015-12-01 16:27:29 +01:00
Kohsuke Kawaguchi
b24fcb18af [maven-release-plugin] prepare release github-api-1.71 2015-12-01 16:27:24 +01:00
Kohsuke Kawaguchi
c2f2d0f8af Making FindBugs happy 2015-12-01 16:23:51 +01:00
Kohsuke Kawaguchi
e33bdd7e62 Two teams now.
I assume this is because 'owner' is now a team.
2015-12-01 16:17:03 +01:00
Kohsuke Kawaguchi
402adc3559 Merge pull request #232 2015-12-01 16:05:25 +01:00
Kohsuke Kawaguchi
0f45d03c51 Reworked this change a bit.
- GHApiInfo need not be public because it's not publicly exposed.
- Throwing an exception is better IMO as it allows richer error message,
  including the differentiation between unreachable host name vs wrong
  URL, and reporting the API endpoint URL that was actually tried.
2015-12-01 16:01:01 +01:00
Kohsuke Kawaguchi
0397d7ab53 Fixing a test problem 2015-12-01 15:39:19 +01:00
Kohsuke Kawaguchi
79f86b82e4 Merge pull request #237 2015-12-01 15:18:15 +01:00
Kohsuke Kawaguchi
261a7a34e3 Consolidated timeout handling 2015-12-01 15:17:54 +01:00
Kohsuke Kawaguchi
723bb89e10 Merge pull request #219 from if6was9/cross-fork-compare
#218 enable cross fork compare
2015-12-01 15:04:50 +01:00
Oliver Gondža
832e4f3c37 Use default timeouts for URLConnections 2015-12-01 14:58:46 +01:00
Kohsuke Kawaguchi
75a4081549 Follow up to PR #216 2015-12-01 14:57:30 +01:00
Kohsuke Kawaguchi
f9291f9fd1 Merge pull request #216 from if6was9/issue-215-download-failure
#215 fix read() failure with private repos
2015-12-01 14:56:49 +01:00
Kohsuke Kawaguchi
c3b4ee9321 Merge pull request #226 from Shredder121/oauth-credentials
Check builder result to either be a token or a user
2015-12-01 14:54:35 +01:00
Kohsuke Kawaguchi
f86896943d Merge pull request #224 from Shredder121/directory-content-trailing-slash
Remove trailing slash when requesting directory content
2015-12-01 14:53:56 +01:00
Kohsuke Kawaguchi
c33f05e8ca Merge pull request #225 from Shredder121/findbugs-changes
Overzealous FindBugs changes.
2015-12-01 14:51:53 +01:00
Oleg Nenashev
52727ded03 Merge pull request #233 from vparfonov/master
Add information about mirror url if it exist.
2015-11-30 18:18:44 +03:00
Manuel Recena
f7d132758e Merge pull request #231 from recena/MergeCommitSha
Support for merge_commit_sha
2015-11-25 23:22:10 +01:00
Manuel Recena
bb17ca9a53 Merge pull request #236 from recena/findbugs
Findbugs plugin has been upgraded
2015-11-25 23:06:57 +01:00
Manuel Recena
aab21c5b17 findbugs plugin has been upgraded 2015-11-23 10:29:04 +01:00
Manuel Recena
acbafee02a Removed unrelated changes. @KostyaSha's suggestion 2015-11-23 10:25:41 +01:00
Manuel Recena
c6d2b1a222 @deprecated annotations were removed 2015-11-23 09:33:32 +01:00
Vitaly Parfonov
b037f75fb0 Add information about mirror url if it exist. Like https://github.com/apache/tomee 2015-11-19 12:09:34 +02:00
Ruben Dijkstra
a1e79d3050 Add unit test to cover the truncation of ?ref 2015-11-16 18:33:51 +01:00
Manuel Recena
354969d5fa Javadoc comment reviewed 2015-11-16 11:16:58 +01:00
Manuel Recena
a371892409 Added a new method to validate the GitHub API URL 2015-11-15 16:36:27 +01:00
Manuel Recena
b0789a7ce7 Set merge_commit_sha as deprecated 2015-11-15 11:50:13 +01:00
Manuel Recena
08be8eb4f8 Added a new test testMergeCommitSHA() 2015-11-15 11:46:18 +01:00
Manuel Recena
bafddf4baf Initial source code modifications 2015-11-14 12:11:24 +01:00
Ruben Dijkstra
75b9184a00 Check builder result to either be a token or a user
Currently, a `user` property is always required (it not having content is also fine).

This adds support for only having the `oauth` key in the property file/environment.
2015-10-09 17:20:11 +02:00
Ruben Dijkstra
5dc83cf2bf Overzealous FindBugs changes.
Charsets that are standard on the JRE are try-lookuped,
bridge methods were removed and a stream that would be closed later is closed explicitly
2015-10-08 19:31:55 +02:00
Ruben Dijkstra
092e9062c8 Remove trailing slash when requesting directory content 2015-10-06 18:23:51 +02:00
Rob Schoening
512c921a81 enable cross fork compare 2015-09-27 07:50:11 -07:00
Kohsuke Kawaguchi
defcd6fe26 Merge pull request #217 from dblevins/closed_at
Support Milestone closed_at date
2015-09-17 19:23:00 -07:00
dblevins
025b6cbfb7 Support Milestone closed_at date 2015-09-14 10:53:07 -07:00
Rob Schoening
b0687dbeb5 Fix compilation errror: The constructor ArrayList<GHHook>(List<capture#1-of ? extends GHHook[]>) is undefined 2015-09-06 08:45:42 -07:00
Rob Schoening
e0b109cba6 fix read() failure with private repos
reorder buildRequest()
2015-09-06 08:28:20 -07:00
Kohsuke Kawaguchi
adaa8ece89 [maven-release-plugin] prepare for next development iteration 2015-08-15 07:19:25 -07:00
Kohsuke Kawaguchi
6516b20e16 [maven-release-plugin] prepare release github-api-1.70 2015-08-15 07:19:22 -07:00
Kohsuke Kawaguchi
839cb03690 Overzealous FindBugs changes.
String given without the encoding mandated by a protocol/design/etc should be converted with the platform default encoding. After all it exists for a reason!
2015-08-15 07:17:15 -07:00
Kohsuke Kawaguchi
2bcd99b14f Overzealous findbugs fix.
This method should rely on platform specific encoding
2015-08-14 11:59:55 -07:00
Kohsuke Kawaguchi
e3ebf6e8a1 Merge pull request #212 from umajeric/master
Added option to edit GitHub release once it is created
2015-08-11 09:24:30 +02:00
Uros Majeric
76d28314b0 Added option to edit GitHub release once it is created 2015-08-06 15:40:26 +02:00
Oleg Nenashev
ec450b8fd8 Merge pull request #210 from oleg-nenashev/findbugs-cleanup
Cleanup issues discovered by FindBugs
2015-07-24 15:57:31 +03:00
Oleg Nenashev
79c5b2edd5 FindBugs: Fix over 100 issues and enforce FindBugs 2015-07-20 12:28:41 +03:00
Kohsuke Kawaguchi
2d45ac51ef [maven-release-plugin] prepare for next development iteration 2015-07-17 05:09:28 -07:00
Kohsuke Kawaguchi
505bb8f06d [maven-release-plugin] prepare release github-api-1.69 2015-07-17 05:09:25 -07:00
Kohsuke Kawaguchi
f8408bd29f This method can return null.
I think what's going on is that GitHub takes some non-zero amount of time to compute this value, and our test runs too fast sometimes and try to fetch a PR before its mergeability is computed
2015-07-17 15:06:39 +03:00
Kohsuke Kawaguchi
5f2c84a913 Tests should use test repositories for mutating tests.
Picking up the first random repository you are an owner of and making a change to it is too dangerous.
2015-07-17 14:33:18 +03:00
Kohsuke Kawaguchi
3011c99e3f Merge pull request #208 from oleg-nenashev/master
Fix potential NPE in the code
2015-07-17 14:24:14 +03:00
Oleg Nenashev
ebc97f42ad Fix potential NPE in the code 2015-07-17 14:21:46 +03:00
Kohsuke Kawaguchi
4660c6d363 Merge pull request #192
Changing GHHook to abstract is a binary incompatible change in theory,
but given the way this class is designed it is difficult to imagine
any client code instantiating this class.

So I think it is OK.
2015-07-17 14:06:47 +03:00
Kohsuke Kawaguchi
e239ef50ba Consistent name with other classes 2015-07-17 14:06:35 +03:00
Kohsuke Kawaguchi
8b4312a880 Merge pull request #203 2015-07-17 13:52:35 +03:00
Kohsuke Kawaguchi
90daf8087e Turning this into javadoc so that users can see them in IDE. 2015-07-17 13:52:16 +03:00
Kohsuke Kawaguchi
dd21bcb34c I think this is a better name 2015-07-17 13:51:31 +03:00
Kohsuke Kawaguchi
c4113f1ac7 Merge pull request #182 from henryju/master
Fix invalid URL for pull request comments update/delete
2015-07-17 13:47:43 +03:00
Kohsuke Kawaguchi
efa48acd1d Merge pull request #185 from marc-guenther/master
Fixes #183: added a method listForks() to GHRepository
2015-07-17 13:47:17 +03:00
Kohsuke Kawaguchi
15e4d07a6d Merge pull request #187 from yegorius/master
Recognize previous_file field in GHCommit.File
2015-07-17 13:46:21 +03:00
Kohsuke Kawaguchi
cb2248809c Merge pull request #190 2015-07-17 13:43:01 +03:00
Kohsuke Kawaguchi
eb9551d81b Renamed getMasterBranch to getDefaultBranch
The new set method can be simply renamed without the backward
compatibility version since it's new
2015-07-17 13:42:20 +03:00
Kohsuke Kawaguchi
87d1256a1b Merge pull request #189 from if6was9/fix-post-body-regression
fixed regression that caused POST operations to be sent as GET
2015-07-17 13:39:52 +03:00
Kohsuke Kawaguchi
dd6179cf25 Merge pull request #197 from treeduck/patch-1
added Page Build
2015-07-17 13:35:20 +03:00
Marc Guenther
23c56ff887 remove unused import 2015-07-17 12:30:49 +02:00
Kohsuke Kawaguchi
c4eefa6917 Merge branch 'master' of github.com:kohsuke/github-api 2015-07-17 13:29:40 +03:00
Kohsuke Kawaguchi
202cff58f2 Merge pull request #201 2015-07-17 13:29:22 +03:00
Kohsuke Kawaguchi
025806f0fd Consistent name with other classes 2015-07-17 13:29:07 +03:00
Kohsuke Kawaguchi
b0c54ef0f1 Restored backward compatibility with the former signature 2015-07-17 13:27:20 +03:00
Kohsuke Kawaguchi
4d7681b1a4 Merge pull request #206 from torodev/master
Specified the GET
2015-07-17 13:24:48 +03:00
Kohsuke Kawaguchi
340fb3f624 Merge pull request #207 from oleg-nenashev/findbugs-enable
Enable FindBugs in the repo
2015-07-17 13:13:23 +03:00
Marc Guenther
a83aad22ca renamed Sort enum, some cleanup, better javadoc 2015-07-17 09:33:48 +02:00
Oleg Nenashev
5a418dcce6 Enable FindBugs 2015-07-16 16:56:09 +03:00
torodev
ec5392708f Specified the GET
Previously doesn't specify the HTTP method to perform
2015-07-14 09:47:04 +08:00
Oleg Nenashev
901db92b11 Merge pull request #205 from stephenc/fix-npe
Fix NPE found when resolving issues from search api
2015-07-07 00:22:46 +03:00
Stephen Connolly
01b8b10344 Fix NPE found when resolving issues from search api 2015-07-06 11:07:05 +01:00
Oleg Nenashev
698d642ec8 Merge pull request #204 from lanwen/ping_event
add ping event to GH events enum
2015-07-05 19:57:20 +03:00
MerkushevKirill
90d1047fb2 add ping event to GH events enum 2015-07-05 19:37:56 +03:00
Luca Milanesio
b0d1eac477 GitHub API have changed the semantics of /user/repos API
It seems that since a couple of days (or weeks?) the /user/repos is returning
ALL the repositories that the user has access or collaborates to whilst previously
were only the ones that belong to him.

JavaDoc updated in order to avoid getting unwanted results and introduced as well
the possibility to filter a specific type of repository to be returned:
- All (the GitHub's default)
- Owner (the user's repos)
- Public / Private (public or private repos)
- Member (the user collaborates to)
2015-06-26 13:42:43 +01:00
Julien HENRY
cce02aec3d Add delete and update for GHIssueComment 2015-06-16 09:27:55 +02:00
Julien HENRY
492ff58aa8 Fix invalid URL for pull request comments update/delete 2015-06-16 09:16:49 +02:00
Kohsuke Kawaguchi
dd3e73996b Merge pull request #200 from lanwen/lost_body_write
fix for unused json map when method with body, but body is null
2015-06-15 10:38:14 -07:00
MerkushevKirill
931ed7adac don't ignore args when method without body
in case of GET or DELETE request
2015-06-15 18:14:58 +03:00
MerkushevKirill
861fd55d06 fix for unused json map when method with body, but body is null
fixes regression from b976e0ef4e
2015-06-15 17:57:16 +03:00
Kohsuke Kawaguchi
9a4eee4e7d Merge pull request #198 from lanwen/rate-reset
fix for GH Enterprise which does not have rate limit reset field
2015-06-11 10:53:11 -07:00
MerkushevKirill
ed76cdbddf fix for GH Enterprise which does not have rate limit reset field
Fixes regression from a4c1c8de24
2015-06-11 14:52:50 +03:00
Koji Habu
9d91549803 added Page Build 2015-06-10 15:43:57 -07:00
Marc Guenther
a5425a3c71 improved javadoc for listForks() 2015-05-28 22:18:02 +02:00
Chris Hut
f4b105b10f Enable creation and retrieval of org webhooks
made GHHook abstract and created two concrete subclasses for org
and repo hooks. Created utility class GHHooks to manage creation
and retrieval of org/repo hooks with minimal code duplication. These
are invoked by GHOrganization and GHRepository respectively.
2015-05-15 13:29:18 -07:00
Rob Schoening
e4de09c55b allow default branch to be set 2015-05-09 01:05:04 -07:00
Rob Schoening
d77be9d382 fixed regression that caused POST operations to be sent as GET 2015-05-09 01:01:04 -07:00
yegorius
626909addb recognize previous_file field in GHCommit.File 2015-05-01 17:59:01 +03:00
Marc Guenther
9b750bedef Fixes #183: added a method listForks() to GHRepository
listForks() will list all forks of a repository.
An optional sort argument is also supported.
2015-05-01 14:06:20 +02:00
Kohsuke Kawaguchi
b976e0ef4e Issue #180: don't write body if HTTP method is DELETE. 2015-04-26 10:52:43 -07:00
Kohsuke Kawaguchi
fd434292ad [maven-release-plugin] prepare for next development iteration 2015-04-19 17:40:39 -07:00
Kohsuke Kawaguchi
7b4d3a869b [maven-release-plugin] prepare release github-api-1.68 2015-04-19 17:40:35 -07:00
Kohsuke Kawaguchi
eeebb1b59f Added the 'sha' parameter.
Fixes issue #176
2015-04-19 17:25:13 -07:00
Julien HENRY
63136f64b7 Merge pull request #174
The merge was done manually because the original commit contains lots of
whitespace only changes.
2015-04-19 17:22:04 -07:00
Kohsuke Kawaguchi
1d2fbf2d92 Merge pull request #179 from lskillen/master
Fix NullPointerException on RateLimitHandler when handling API errors.
2015-04-19 17:03:50 -07:00
Kohsuke Kawaguchi
1e52dded14 Added a helper method 2015-04-15 08:33:18 -07:00
Lee Skillen
cfc7005275 Fix NullPointerException on RateLimiter when handling API errors. 2015-04-14 14:45:47 +01:00
Kohsuke Kawaguchi
f23afcd5aa [maven-release-plugin] prepare for next development iteration 2015-04-13 18:30:43 -07:00
Kohsuke Kawaguchi
4e88a0c91b [maven-release-plugin] prepare release github-api-1.67 2015-04-13 18:30:40 -07:00
Kohsuke Kawaguchi
d070f9deb0 TAB -> WS 2015-04-13 18:25:44 -07:00
Kohsuke Kawaguchi
b736e20a74 Added more getHtmlUrl() methods 2015-04-13 18:17:37 -07:00
Kohsuke Kawaguchi
aad20d0a03 Merge pull request #169 from KostyaSha/fixAuthLoop
Throw error for bad creds
2015-04-13 16:55:58 -07:00
Kohsuke Kawaguchi
7ff97348d9 Merge pull request #170 from KostyaSha/coverity
Improvements
2015-04-13 16:55:24 -07:00
Kohsuke Kawaguchi
68dda3a46d Merge pull request #175 2015-04-13 16:47:32 -07:00
Kohsuke Kawaguchi
2cd44f8c33 Added the pair method 2015-04-13 16:47:25 -07:00
Kohsuke Kawaguchi
9775954aff Massaging the PR.
- need to retrieve the object in full to have all the fields properly populated
- documentation fix, as this method points to the root of the forking chain, not just an upstream.
2015-04-13 16:45:38 -07:00
Kohsuke Kawaguchi
1a071b0b54 Returning null instead of throwing an exception (as a matter of taste) 2015-04-13 16:39:20 -07:00
Kohsuke Kawaguchi
8dcea59c74 Fixed javadoc errors 2015-04-13 16:36:14 -07:00
Kohsuke Kawaguchi
f482f77871 Merge pull request #177 from infm/feat/notif
Added getters for the objects notifications refer to
2015-04-13 16:38:22 -07:00
infm
b058c39ee1 Added getters for the objects notifications refer to 2015-04-09 01:21:38 +03:00
Jason Nichols
b926b6c67f Added the source attribute to GHRepository 2015-04-02 14:54:13 -04:00
Kohsuke Kawaguchi
3fb8e5f799 [maven-release-plugin] prepare for next development iteration 2015-03-24 10:26:48 -07:00
Kohsuke Kawaguchi
277ccb5188 [maven-release-plugin] prepare release github-api-1.66 2015-03-24 10:26:44 -07:00
Kanstantsin Shautsou
9ebc9c0867 Use FAIL rate-limit handler for tests
Should avoid possible Thread.sleep() for tests execution.
2015-03-23 21:14:42 +03:00
Kanstantsin Shautsou
f1f96713a4 [CID-107552] Unintended regular expression
regex_expected: The . character(s) in the pattern ".md" can match any character, because calls to replaceAll treat the pattern as a regular expression, which might be unexpected.
2015-03-23 02:35:55 +03:00
Kanstantsin Shautsou
fc3b6d2c2e [CID-107535] Missing call to superclass
Similar to other events
2015-03-23 02:23:44 +03:00
Kanstantsin Shautsou
d0d0716b3b Throw error for bad creds 2015-03-23 02:09:23 +03:00
Kohsuke Kawaguchi
73119afeff [maven-release-plugin] prepare for next development iteration 2015-03-22 15:57:32 -07:00
Kohsuke Kawaguchi
8939179be8 [maven-release-plugin] prepare release github-api-1.65 2015-03-22 15:57:29 -07:00
Kohsuke Kawaguchi
adba2e68db Renamed for consistency with other methods 2015-03-22 15:54:53 -07:00
Kohsuke Kawaguchi
0ef8b471a3 Added subscription related methods 2015-03-22 15:54:10 -07:00
Kohsuke Kawaguchi
205950fc5f Method to mark the thread as read 2015-03-22 15:50:32 -07:00
Kohsuke Kawaguchi
8835b2c745 added a method to mark all the notifications as read 2015-03-22 15:45:36 -07:00
Kohsuke Kawaguchi
74fda40764 Implemented initial notification API support.
Fixes issue #119
2015-03-22 15:40:53 -07:00
Kohsuke Kawaguchi
687a36937e Keep HttpURLConnection() in the field.
The primary motivation was to expose response headers, but this also made the code most concise by reducing the # of parameters that are passed around.
2015-03-22 14:52:34 -07:00
Kohsuke Kawaguchi
2c7b8bd6e8 report error stream even for 404 2015-03-22 14:46:38 -07:00
Kohsuke Kawaguchi
e9417f5fa1 Described how to set up persistent disk cache
This is good enough "fix" for issue #168.
2015-03-22 12:13:30 -07:00
Kohsuke Kawaguchi
5e08b34c43 added code search 2015-03-22 12:08:53 -07:00
Kohsuke Kawaguchi
7b436ffb3b support on-demand data population for the use in code search API. 2015-03-22 12:02:08 -07:00
Kohsuke Kawaguchi
1ee2ec3728 Added repository search 2015-03-22 11:48:56 -07:00
Kohsuke Kawaguchi
ed28768146 implemented user search 2015-03-22 11:41:25 -07:00
Kohsuke Kawaguchi
f931835176 refactored to introduce other search builders 2015-03-22 11:32:48 -07:00
Kohsuke Kawaguchi
0cf9bc2814 [maven-release-plugin] prepare for next development iteration 2015-03-22 11:16:03 -07:00
Kohsuke Kawaguchi
8b428f2c93 whitespace only changes for consistent indentation 2015-03-22 11:14:19 -07:00
Kohsuke Kawaguchi
10238dbcd3 Explaining why this code is the way it is. 2015-03-22 11:13:40 -07:00
Kohsuke Kawaguchi
6229e0928d Revert "Set credentials file according to documentation"
This reverts commit 0bf81f4fb9.

The point of this is to allow me to use a separate account to avoid
corrupting my event stream. GitHub.connect() does the standard handling
for those who are not me.
2015-03-22 11:11:03 -07:00
Kohsuke Kawaguchi
5c7b259fe9 Using the latest 2015-03-22 11:02:17 -07:00
Kohsuke Kawaguchi
cc84c867c0 I think our coverage is pretty good now 2015-03-22 11:01:02 -07:00
Kohsuke Kawaguchi
5bf252e12d Added markdown support
Fixes issue #165
2015-03-22 11:00:57 -07:00
Kohsuke Kawaguchi
75512ff66a Turns out the interning of GHUser wasn't working at all!
Fixes issue #166.
2015-03-22 10:38:57 -07:00
Kohsuke Kawaguchi
6f4832476a test case for #162 2015-03-22 10:21:16 -07:00
Kohsuke Kawaguchi
86b0d27299 added a method to read content.
This also fixes #162.
2015-03-22 10:21:09 -07:00
Kohsuke Kawaguchi
5a8845f7f6 added a method to return the raw unprocessed body 2015-03-22 09:17:49 -07:00
Kohsuke Kawaguchi
709e47f32f Added getDownloadUrl() method 2015-03-22 09:10:34 -07:00
Kohsuke Kawaguchi
77590b4eb3 eliminate the need for path manipulation and consolidate them to 'with' 2015-03-21 16:52:49 -07:00
Kohsuke Kawaguchi
72fc313135 improved error handling 2015-03-21 16:43:11 -07:00
Kohsuke Kawaguchi
39b32cee2e Implemented /repositories
Fixed issue #157
2015-03-21 16:35:34 -07:00
Kohsuke Kawaguchi
bdcee7c052 Merge pull request #160 with some modifications
Strategy pattern is better for API rate limit handling as the current
behavior is quite valid for batch applications.

I'm not taking OkHttp version change so as not to introduce any unneeded
version requirement.
2015-03-17 07:44:36 -07:00
Kohsuke Kawaguchi
4093e53b5b Implemented a strategy pattern to let the client determine API rate limit behavior.
The default is set to the backward compatible behaviour.
2015-03-17 07:43:51 -07:00
Kanstantsin Shautsou
a4c1c8de24 Provide reset date info for rate limit 2015-03-17 07:31:05 -07:00
Kohsuke Kawaguchi
7ed234c875 Standardize environment variable names
... that are more like typical environment variables. Reasonably unique and upper case.

Deprecate other methods. The point of a connector method is to make sure all clients of the same library uses the same environments, thereby eliminating the pain of setting credentials per app.

Allowing the app to specify the environment variable names defeat this purpose.
2015-03-15 13:17:57 -07:00
Kohsuke Kawaguchi
0359160ac6 Avoided using JDK6 method 2015-03-15 13:07:21 -07:00
Kohsuke Kawaguchi
2478dad9b5 Simplification 2015-03-15 13:04:50 -07:00
Kohsuke Kawaguchi
690292352b Simplification
But this method is insane!
2015-03-15 13:02:28 -07:00
Kohsuke Kawaguchi
271d18cddc simplification 2015-03-15 13:01:35 -07:00
Kohsuke Kawaguchi
e1465639e7 Merge pull request #156 from ashwanthkumar/endpoint-from-properties
Picking endpoint from the properties file and environment variables
2015-03-15 19:55:32 +00:00
Kohsuke Kawaguchi
ce7ca59339 Merge pull request #155 2015-03-15 12:49:30 -07:00
Kohsuke Kawaguchi
76610b25d7 Promoted GHTreeEntry to the top-level 2015-03-15 12:49:14 -07:00
Kohsuke Kawaguchi
dfce0bda7c prefer list over raw array 2015-03-15 12:48:27 -07:00
Ashwanth Kumar
41c0dd9727 Fixing the indentation for enpointVariableName 2015-03-13 21:26:49 +05:30
Ashwanth Kumar
232c0389d3 Adding fromEnvironment to maintain backword compatibility 2015-03-13 21:26:19 +05:30
Oleg Nenashev
d95c8a4ab0 Merge pull request #161 from khoa-nd/master
Add method to get the list of languages using in repository
2015-03-13 17:58:01 +03:00
khoa-nd
374fdb37e1 Change type of language bytes from Integer to Long 2015-03-06 09:11:03 +07:00
khoa-nd
f78530636e Add method to get the list of languages using in repository 2015-03-05 15:53:02 +07:00
Kanstantsin Shautsou
0bf81f4fb9 Set credentials file according to documentation 2015-03-03 19:23:06 +03:00
Kohsuke Kawaguchi
dcc3b7f36b [maven-release-plugin] prepare for next development iteration 2015-03-02 09:10:52 -08:00
Kohsuke Kawaguchi
d6722266f5 [maven-release-plugin] prepare release github-api-1.63 2015-03-02 09:10:49 -08:00
Kohsuke Kawaguchi
11566891dc Restored backward compatibility
The signature of the method can change for the future, but it still has
to return Label instances for older binaries
2015-03-02 08:33:00 -08:00
Kohsuke Kawaguchi
9aaf69cc9a Added maling list 2015-03-02 08:14:15 -08:00
Ashwanth Kumar
a716a59489 Picking endpoint from the properties file and environment variables
Helps seemless switching between public github and enterprise without any code changes
2015-02-22 09:41:58 +05:30
Daniel
bad0d1bbcf implementing github trees as described https://developer.github.com/v3/git/trees/#get-a-tree-recursively 2015-02-18 14:58:27 +01:00
Kohsuke Kawaguchi
aa43e265b7 [maven-release-plugin] prepare for next development iteration 2015-02-15 09:12:26 -08:00
139 changed files with 12288 additions and 1130 deletions

4
.gitignore vendored
View File

@@ -3,3 +3,7 @@ target
*.iml
*.ipr
*.iws
.classpath
.project
.settings/
.DS_Store

69
pom.xml
View File

@@ -3,11 +3,11 @@
<parent>
<groupId>org.kohsuke</groupId>
<artifactId>pom</artifactId>
<version>12</version>
<version>17</version>
</parent>
<artifactId>github-api</artifactId>
<version>1.62</version>
<version>1.87</version>
<name>GitHub API for Java</name>
<url>http://github-api.kohsuke.org/</url>
<description>GitHub API for Java</description>
@@ -16,7 +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.62</tag>
<tag>github-api-1.87</tag>
</scm>
<distributionManagement>
@@ -28,10 +28,39 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<findbugs-maven-plugin.version>3.0.2</findbugs-maven-plugin.version>
<findbugs-maven-plugin.failOnError>true</findbugs-maven-plugin.failOnError>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<rerunFailingTestsCount>2</rerunFailingTestsCount>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
<version>1.15</version>
<configuration>
<signature>
<groupId>org.codehaus.mojo.signature</groupId>
<artifactId>java15</artifactId>
<version>1.0</version>
</signature>
</configuration>
<executions>
<execution>
<id>ensure-java-1.5-class-library</id>
<phase>test</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.infradna.tool</groupId>
<artifactId>bridge-method-injector</artifactId>
@@ -44,6 +73,24 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>${findbugs-maven-plugin.version}</version>
<configuration>
<xmlOutput>true</xmlOutput>
<failOnError>${findbugs-maven-plugin.failOnError}</failOnError>
</configuration>
<executions>
<execution>
<id>run-findbugs</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
@@ -94,7 +141,7 @@
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp-urlconnection</artifactId>
<version>2.0.0</version>
<version>2.7.5</version>
<optional>true</optional>
</dependency>
<dependency>
@@ -109,6 +156,12 @@
<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>
@@ -139,4 +192,12 @@
<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>

View File

@@ -0,0 +1,63 @@
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 abuse limit is hit.
*
* @author Kohsuke Kawaguchi
* @see GitHubBuilder#withAbuseLimitHandler(AbuseLimitHandler)
* @see <a href="https://developer.github.com/v3/#abuse-rate-limits">documentation</a>
* @see RateLimitHandler
*/
public abstract class AbuseLimitHandler {
/**
* Called when the library encounters HTTP error indicating that the API abuse 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/#abuse-rate-limits">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;
/**
* Wait until the API abuse "wait time" is passed.
*/
public static final AbuseLimitHandler WAIT = new AbuseLimitHandler() {
@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("Retry-After");
if (v==null) return 60 * 1000; // can't tell, return 1 min
return Math.max(1000, Long.parseLong(v)*1000);
}
};
/**
* Fail immediately.
*/
public static final AbuseLimitHandler FAIL = new AbuseLimitHandler() {
@Override
public void onError(IOException e, HttpURLConnection uc) throws IOException {
throw (IOException)new IOException("Abust limit reached").initCause(e);
}
};
}

View File

@@ -23,9 +23,13 @@
*/
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* @author Kohsuke Kawaguchi
*/
@SuppressFBWarnings(value = "UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD",
justification = "Being constructed by JSON deserialization")
class DeleteToken {
public String delete_token;
}

View File

@@ -0,0 +1,17 @@
package org.kohsuke.github;
import java.util.Locale;
/**
* This was added during preview API period but it has changed since then.
*
* @author Kohsuke Kawaguchi
*/
@Deprecated
public enum EnforcementLevel {
OFF, NON_ADMINS, EVERYONE;
public String toString() {
return name().toLowerCase(Locale.ENGLISH);
}
}

View File

@@ -1,6 +1,7 @@
package org.kohsuke.github;
import java.io.IOException;
import java.net.URL;
/**
* Asset in a release.
@@ -60,6 +61,14 @@ public class GHAsset extends GHObject {
return state;
}
/**
* @deprecated This object has no HTML URL.
*/
@Override
public URL getHtmlUrl() {
return null;
}
public String getBrowserDownloadUrl() {
return browser_download_url;
}

View File

@@ -1,5 +1,6 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.net.URL;
import java.util.Collection;
import java.util.Date;
@@ -10,78 +11,104 @@ import java.util.List;
*
* @author janinko
* @see GitHub#createToken(Collection, String, String)
* @see http://developer.github.com/v3/oauth/#create-a-new-authorization
* @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";
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;
private GitHub root;
private List<String> scopes;
private String token;
private String token_last_eight;
private String hashed_token;
private App app;
private String note;
private String note_url;
private String fingerprint;
//TODO add some user class for https://developer.github.com/v3/oauth_authorizations/#check-an-authorization ?
//private GHUser user;
public GitHub getRoot() {
return root;
}
public List<String> getScopes() {
return scopes;
}
public List<String> getScopes() {
return scopes;
}
public String getToken(){
return token;
}
public String getToken() {
return token;
}
public URL getAppUrl(){
public String getTokenLastEight() {
return token_last_eight;
}
public String getHashedToken() {
return hashed_token;
}
public URL getAppUrl() {
return GitHub.parseURL(app.url);
}
}
public String getAppName() {
return app.name;
}
public URL getApiURL(){
public String getAppName() {
return app.name;
}
@SuppressFBWarnings(value = "NM_CONFUSING",
justification = "It's a part of the library API, cannot be changed")
public URL getApiURL() {
return GitHub.parseURL(url);
}
}
public String getNote() {
return note;
}
/**
* @deprecated This object has no HTML URL.
*/
@Override
public URL getHtmlUrl() {
return null;
}
public URL getNoteUrl(){
public String getNote() {
return note;
}
public URL getNoteUrl() {
return GitHub.parseURL(note_url);
}
}
/*package*/ GHAuthorization wrap(GitHub root) {
this.root = root;
return this;
}
public String getFingerprint() {
return fingerprint;
}
/*package*/ GHAuthorization wrap(GitHub root) {
this.root = root;
return this;
}
private static class App{
private String url;
private String name;
}
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD"},
justification = "JSON API")
private static class App {
private String url;
private String name;
// private String client_id; not yet used
}
}

View File

@@ -0,0 +1,64 @@
package org.kohsuke.github;
import org.apache.commons.codec.binary.Base64InputStream;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
/**
* @author Kanstantsin Shautsou
* @author Kohsuke Kawaguchi
* @see GHTreeEntry#asBlob()
* @see GHRepository#getBlob(String)
* @see <a href="https://developer.github.com/v3/git/blobs/#get-a-blob">Get a blob</a>
*/
public class GHBlob {
private String content, encoding, url, sha;
private long size;
/**
* API URL of this blob.
*/
public URL getUrl() {
return GitHub.parseURL(url);
}
public String getSha() {
return sha;
}
/**
* Number of bytes in this blob.
*/
public long getSize() {
return size;
}
public String getEncoding() {
return encoding;
}
/**
* Encoded content. You probably want {@link #read()}
*/
public String getContent() {
return content;
}
/**
* Retrieves the actual bytes of the blob.
*/
public InputStream read() {
if (encoding.equals("base64")) {
try {
return new Base64InputStream(new ByteArrayInputStream(content.getBytes("US-ASCII")), false);
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e); // US-ASCII is mandatory
}
}
throw new UnsupportedOperationException("Unrecognized encoding: "+encoding);
}
}

View File

@@ -1,19 +1,38 @@
package org.kohsuke.github;
import static org.kohsuke.github.Previews.LOKI;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import com.fasterxml.jackson.annotation.JsonProperty;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* A branch in a repository.
*
*
* @author Yusuke Kokubo
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD", "URF_UNREAD_FIELD"}, justification = "JSON API")
public class GHBranch {
private GitHub root;
private GHRepository owner;
private String name;
private Commit commit;
@JsonProperty("protected")
private boolean protection;
private String protection_url;
public static class Commit {
String sha,url;
String sha;
@SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now")
String url;
}
public GitHub getRoot() {
@@ -31,6 +50,27 @@ public class GHBranch {
return name;
}
/**
* Returns true if the push to this branch is restricted via branch protection.
*/
@Preview @Deprecated
public boolean isProtected() {
return protection;
}
/**
* Returns API URL that deals with the protection of this branch.
*/
@Preview @Deprecated
public URL getProtectionUrl() {
return GitHub.parseURL(protection_url);
}
@Preview @Deprecated
public GHBranchProtection getProtection() throws IOException {
return root.retrieve().withPreview(LOKI).to(protection_url, GHBranchProtection.class);
}
/**
* The commit that this branch currently points to.
*/
@@ -38,9 +78,49 @@ public class GHBranch {
return commit.sha;
}
/**
* Disables branch protection and allows anyone with push access to push changes.
*/
@Preview @Deprecated
public void disableProtection() throws IOException {
new Requester(root).method("DELETE").withPreview(LOKI).to(protection_url);
}
/**
* Enables branch protection to control what commit statuses are required to push.
*
* @see GHCommitStatus#getContext()
*/
@Preview @Deprecated
public GHBranchProtectionBuilder enableProtection() {
return new GHBranchProtectionBuilder(this);
}
// backward compatibility with previous signature
@Deprecated
public void enableProtection(EnforcementLevel level, Collection<String> contexts) throws IOException {
switch (level) {
case OFF:
disableProtection();
break;
case NON_ADMINS:
case EVERYONE:
enableProtection()
.addRequiredChecks(contexts)
.includeAdmins(level==EnforcementLevel.EVERYONE)
.enable();
break;
}
}
String getApiRoute() {
return owner.getApiTailUrl("/branches/"+name);
}
@Override
public String toString() {
return "Branch:" + name + " in " + owner.getUrl();
final String url = owner != null ? owner.getUrl().toString() : "unknown";
return "Branch:" + name + " in " + url;
}
/*package*/ GHBranch wrap(GHRepository repo) {

View File

@@ -0,0 +1,152 @@
package org.kohsuke.github;
import java.util.Collection;
import com.fasterxml.jackson.annotation.JsonProperty;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD",
"URF_UNREAD_FIELD" }, justification = "JSON API")
public class GHBranchProtection {
@JsonProperty("enforce_admins")
private EnforceAdmins enforceAdmins;
@JsonProperty("required_pull_request_reviews")
private RequiredReviews requiredReviews;
@JsonProperty("required_status_checks")
private RequiredStatusChecks requiredStatusChecks;
@JsonProperty
private Restrictions restrictions;
@JsonProperty
private String url;
public EnforceAdmins getEnforceAdmins() {
return enforceAdmins;
}
public RequiredReviews getRequiredReviews() {
return requiredReviews;
}
public RequiredStatusChecks getRequiredStatusChecks() {
return requiredStatusChecks;
}
public Restrictions getRestrictions() {
return restrictions;
}
public String getUrl() {
return url;
}
public static class EnforceAdmins {
@JsonProperty
private boolean enabled;
@JsonProperty
private String url;
public String getUrl() {
return url;
}
public boolean isEnabled() {
return enabled;
}
}
public static class RequiredReviews {
@JsonProperty("dismissal_restrictions")
private Restrictions dismissalRestriction;
@JsonProperty("dismiss_stale_reviews")
private boolean dismissStaleReviews;
@JsonProperty("require_code_owner_reviews")
private boolean requireCodeOwnerReviews;
@JsonProperty
private String url;
public Restrictions getDismissalRestrictions() {
return dismissalRestriction;
}
public String getUrl() {
return url;
}
public boolean isDismissStaleReviews() {
return dismissStaleReviews;
}
public boolean isRequireCodeOwnerReviews() {
return requireCodeOwnerReviews;
}
}
public static class RequiredStatusChecks {
@JsonProperty
private Collection<String> contexts;
@JsonProperty
private boolean strict;
@JsonProperty
private String url;
public Collection<String> getContexts() {
return contexts;
}
public String getUrl() {
return url;
}
public boolean isRequiresBranchUpToDate() {
return strict;
}
}
public static class Restrictions {
@JsonProperty
private Collection<GHTeam> teams;
@JsonProperty("teams_url")
private String teamsUrl;
@JsonProperty
private String url;
@JsonProperty
private Collection<GHUser> users;
@JsonProperty("users_url")
private String usersUrl;
public Collection<GHTeam> getTeams() {
return teams;
}
public String getTeamsUrl() {
return teamsUrl;
}
public String getUrl() {
return url;
}
public Collection<GHUser> getUsers() {
return users;
}
public String getUsersUrl() {
return usersUrl;
}
}
}

View File

@@ -0,0 +1,203 @@
package org.kohsuke.github;
import static org.kohsuke.github.Previews.LOKI;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* Builder to configure the branch protection settings.
*
* @see GHBranch#enableProtection()
*/
@SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD",
"URF_UNREAD_FIELD" }, justification = "JSON API")
public class GHBranchProtectionBuilder {
private final GHBranch branch;
private boolean enforceAdmins;
private Map<String, Object> prReviews;
private Restrictions restrictions;
private StatusChecks statusChecks;
GHBranchProtectionBuilder(GHBranch branch) {
this.branch = branch;
}
public GHBranchProtectionBuilder addRequiredChecks(Collection<String> checks) {
getStatusChecks().contexts.addAll(checks);
return this;
}
public GHBranchProtectionBuilder addRequiredChecks(String... checks) {
addRequiredChecks(Arrays.asList(checks));
return this;
}
public GHBranchProtectionBuilder dismissStaleReviews() {
getPrReviews().put("dismiss_stale_reviews", true);
return this;
}
public GHBranchProtection enable() throws IOException {
return requester().method("PUT")
.withNullable("required_status_checks", statusChecks)
.withNullable("required_pull_request_reviews", prReviews)
.withNullable("restrictions", restrictions)
.withNullable("enforce_admins", enforceAdmins)
.to(branch.getProtectionUrl().toString(), GHBranchProtection.class);
}
public GHBranchProtectionBuilder includeAdmins() {
return includeAdmins(true);
}
public GHBranchProtectionBuilder includeAdmins(boolean v) {
enforceAdmins = v;
return this;
}
public GHBranchProtectionBuilder requireBranchIsUpToDate() {
return requireBranchIsUpToDate(true);
}
public GHBranchProtectionBuilder requireBranchIsUpToDate(boolean v) {
getStatusChecks().strict = v;
return this;
}
public GHBranchProtectionBuilder requireCodeOwnReviews() {
return requireCodeOwnReviews(true);
}
public GHBranchProtectionBuilder requireCodeOwnReviews(boolean v) {
getPrReviews().put("require_code_owner_reviews", v);
return this;
}
public GHBranchProtectionBuilder requireReviews() {
getPrReviews();
return this;
}
public GHBranchProtectionBuilder restrictPushAccess() {
getRestrictions();
return this;
}
public GHBranchProtectionBuilder teamPushAccess(Collection<GHTeam> teams) {
for (GHTeam team : teams) {
teamPushAccess(team);
}
return this;
}
public GHBranchProtectionBuilder teamPushAccess(GHTeam... teams) {
for (GHTeam team : teams) {
getRestrictions().teams.add(team.getSlug());
}
return this;
}
public GHBranchProtectionBuilder teamReviewDismissals(Collection<GHTeam> teams) {
for (GHTeam team : teams) {
teamReviewDismissals(team);
}
return this;
}
public GHBranchProtectionBuilder teamReviewDismissals(GHTeam... teams) {
for (GHTeam team : teams) {
addReviewRestriction(team.getSlug(), true);
}
return this;
}
public GHBranchProtectionBuilder userPushAccess(Collection<GHUser> users) {
for (GHUser user : users) {
userPushAccess(user);
}
return this;
}
public GHBranchProtectionBuilder userPushAccess(GHUser... users) {
for (GHUser user : users) {
getRestrictions().users.add(user.getLogin());
}
return this;
}
public GHBranchProtectionBuilder userReviewDismissals(Collection<GHUser> users) {
for (GHUser team : users) {
userReviewDismissals(team);
}
return this;
}
public GHBranchProtectionBuilder userReviewDismissals(GHUser... users) {
for (GHUser user : users) {
addReviewRestriction(user.getLogin(), false);
}
return this;
}
private void addReviewRestriction(String restriction, boolean isTeam) {
getPrReviews();
if (!prReviews.containsKey("dismissal_restrictions")) {
prReviews.put("dismissal_restrictions", new Restrictions());
}
Restrictions restrictions = (Restrictions) prReviews.get("dismissal_restrictions");
if (isTeam) {
restrictions.teams.add(restriction);
} else {
restrictions.users.add(restriction);
}
}
private Map<String, Object> getPrReviews() {
if (prReviews == null) {
prReviews = new HashMap<String, Object>();
}
return prReviews;
}
private Restrictions getRestrictions() {
if (restrictions == null) {
restrictions = new Restrictions();
}
return restrictions;
}
private StatusChecks getStatusChecks() {
if (statusChecks == null) {
statusChecks = new StatusChecks();
}
return statusChecks;
}
private Requester requester() {
return new Requester(branch.getRoot()).withPreview(LOKI);
}
private static class Restrictions {
private Set<String> teams = new HashSet<String>();
private Set<String> users = new HashSet<String>();
}
private static class StatusChecks {
final List<String> contexts = new ArrayList<String>();
boolean strict;
}
}

View File

@@ -1,12 +1,13 @@
package org.kohsuke.github;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.net.URL;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
@@ -16,6 +17,8 @@ import java.util.List;
* @see GHRepository#getCommit(String)
* @see GHCommitComment#getCommit()
*/
@SuppressFBWarnings(value = {"NP_UNWRITTEN_FIELD", "UWF_UNWRITTEN_FIELD"},
justification = "JSON API")
public class GHCommit {
private GHRepository owner;
@@ -24,6 +27,8 @@ public class GHCommit {
/**
* Short summary of this commit.
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD", "UWF_UNWRITTEN_FIELD"}, justification = "JSON API")
public static class ShortInfo {
private GHAuthor author;
private GHAuthor committer;
@@ -32,32 +37,47 @@ public class GHCommit {
private int comment_count;
@WithBridgeMethods(value=GHAuthor.class,castRequired=true)
public GitUser getAuthor() {
return author;
}
static class Tree {
String sha;
}
@WithBridgeMethods(value=GHAuthor.class,castRequired=true)
public GitUser getCommitter() {
return committer;
}
private Tree tree;
@WithBridgeMethods(value = GHAuthor.class, castRequired = true)
public GitUser getAuthor() {
return author;
}
public Date getAuthoredDate() {
return GitHub.parseDate(author.date);
}
@WithBridgeMethods(value = GHAuthor.class, castRequired = true)
public GitUser getCommitter() {
return committer;
}
public Date getCommitDate() {
return GitHub.parseDate(committer.date);
}
/**
* Commit message.
*/
public String getMessage() {
return message;
}
public String getMessage() {
return message;
}
public int getCommentCount() {
return comment_count;
}
public int getCommentCount() {
return comment_count;
}
}
/**
* @deprecated Use {@link GitUser} instead.
*/
public static class GHAuthor extends GitUser {
private String date;
}
public static class Stats {
@@ -67,10 +87,13 @@ public class GHCommit {
/**
* A file that was modified.
*/
@SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD",
justification = "It's being initilized by JSON deserialization")
public static class File {
String status;
int changes,additions,deletions;
String raw_url, blob_url, filename, sha, patch;
String raw_url, blob_url, sha, patch;
String filename, previous_filename;
/**
* Number of lines added + removed.
@@ -94,19 +117,28 @@ public class GHCommit {
}
/**
* "modified", "added", or "deleted"
* "modified", "added", or "removed"
*/
public String getStatus() {
return status;
}
/**
* Just the base name and the extension without any directory name.
* Full path in the repository.
*/
@SuppressFBWarnings(value = "NM_CONFUSING",
justification = "It's a part of the library's API and cannot be renamed")
public String getFileName() {
return filename;
}
/**
* Previous path, in case file has moved.
*/
public String getPreviousFilename() {
return previous_filename;
}
/**
* The actual change.
*/
@@ -139,28 +171,35 @@ public class GHCommit {
}
public static class Parent {
String url,sha;
@SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now")
String url;
String 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;
@SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now")
String url,avatar_url,gravatar_id;
@SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now")
int id;
String login;
}
String url,sha;
String url,html_url,sha;
List<File> files;
Stats stats;
List<Parent> parents;
User author,committer;
public ShortInfo getCommitShortInfo() {
return commit;
}
/**
public ShortInfo getCommitShortInfo() throws IOException {
if (commit==null)
populate();
return commit;
}
/**
* The repository that contains the commit.
*/
public GHRepository getOwner() {
@@ -170,24 +209,41 @@ public class GHCommit {
/**
* Number of lines added + removed.
*/
public int getLinesChanged() {
public int getLinesChanged() throws IOException {
populate();
return stats.total;
}
/**
* Number of lines added.
*/
public int getLinesAdded() {
public int getLinesAdded() throws IOException {
populate();
return stats.additions;
}
/**
* Number of lines removed.
*/
public int getLinesDeleted() {
public int getLinesDeleted() throws IOException {
populate();
return stats.deletions;
}
/**
* Use this method to walk the tree
*/
public GHTree getTree() throws IOException {
return owner.getTree(getCommitShortInfo().tree.sha);
}
/**
* URL of this commit like "https://github.com/kohsuke/sandbox-ant/commit/8ae38db0ea5837313ab5f39d43a6f73de3bd9000"
*/
public URL getHtmlUrl() {
return GitHub.parseURL(html_url);
}
/**
* [0-9a-f]{40} SHA1 checksum.
*/
@@ -201,7 +257,8 @@ public class GHCommit {
* @return
* Can be empty but never null.
*/
public List<File> getFiles() {
public List<File> getFiles() throws IOException {
populate();
return files!=null ? Collections.unmodifiableList(files) : Collections.<File>emptyList();
}
@@ -237,10 +294,29 @@ public class GHCommit {
return resolveUser(author);
}
/**
* Gets the date the change was authored on.
* @return the date the change was authored on.
* @throws IOException if the information was not already fetched and an attempt at fetching the information failed.
*/
public Date getAuthoredDate() throws IOException {
return getCommitShortInfo().getAuthoredDate();
}
public GHUser getCommitter() throws IOException {
return resolveUser(committer);
}
/**
* Gets the date the change was committed on.
*
* @return the date the change was committed on.
* @throws IOException if the information was not already fetched and an attempt at fetching the information failed.
*/
public Date getCommitDate() throws IOException {
return getCommitShortInfo().getCommitDate();
}
private GHUser resolveUser(User author) throws IOException {
if (author==null || author.login==null) return null;
return owner.root.getUser(author.login);
@@ -251,8 +327,8 @@ public class GHCommit {
*/
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)) {
public PagedIterator<GHCommitComment> _iterator(int pageSize) {
return new PagedIterator<GHCommitComment>(owner.root.retrieve().asIterator(String.format("/repos/%s/%s/commits/%s/comments", owner.getOwnerName(), owner.getName(), sha), GHCommitComment[].class, pageSize)) {
@Override
protected void wrapUp(GHCommitComment[] page) {
for (GHCommitComment c : page)
@@ -279,7 +355,7 @@ public class GHCommit {
}
public GHCommitComment createComment(String body) throws IOException {
return createComment(body,null,null,null);
return createComment(body, null, null, null);
}
/**
@@ -296,6 +372,14 @@ public class GHCommit {
return owner.getLastCommitStatus(sha);
}
/**
* Some of the fields are not always filled in when this object is retrieved as a part of another API call.
*/
void populate() throws IOException {
if (files==null && stats==null)
owner.root.retrieve().to(owner.getApiTailUrl("commits/" + sha), this);
}
GHCommit wrapUp(GHRepository owner) {
this.owner = owner;
return this;

View File

@@ -1,9 +1,12 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.net.URL;
import java.util.Date;
import static org.kohsuke.github.Previews.SQUIRREL_GIRL;
/**
* A comment attached to a commit (or a specific line in a specific file of a commit.)
*
@@ -12,19 +15,15 @@ import java.util.Date;
* @see GHCommit#listComments()
* @see GHCommit#createComment(String, String, Integer, Integer)
*/
public class GHCommitComment extends GHObject {
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public class GHCommitComment extends GHObject implements Reactable {
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;
}
GHUser user; // not fully populated. beware.
public GHRepository getOwner() {
return owner;
@@ -69,7 +68,7 @@ public class GHCommitComment extends GHObject {
* Gets the user who put this comment.
*/
public GHUser getUser() throws IOException {
return owner.root.getUser(user.login);
return owner == null || owner.root.isOffline() ? user : owner.root.getUser(user.login);
}
/**
@@ -83,12 +82,35 @@ public class GHCommitComment extends GHObject {
* Updates the body of the commit message.
*/
public void update(String body) throws IOException {
GHCommitComment r = new Requester(owner.root)
new Requester(owner.root)
.with("body", body)
.method("PATCH").to(getApiTail(), GHCommitComment.class);
this.body = body;
}
@Preview @Deprecated
public GHReaction createReaction(ReactionContent content) throws IOException {
return new Requester(owner.root)
.withPreview(SQUIRREL_GIRL)
.with("content", content.getContent())
.to(getApiTail()+"/reactions", GHReaction.class).wrap(owner.root);
}
@Preview @Deprecated
public PagedIterable<GHReaction> listReactions() {
return new PagedIterable<GHReaction>() {
public PagedIterator<GHReaction> _iterator(int pageSize) {
return new PagedIterator<GHReaction>(owner.root.retrieve().withPreview(SQUIRREL_GIRL).asIterator(getApiTail()+"/reactions", GHReaction[].class, pageSize)) {
@Override
protected void wrapUp(GHReaction[] page) {
for (GHReaction c : page)
c.wrap(owner.root);
}
};
}
};
}
/**
* Deletes this comment.
*/
@@ -103,6 +125,9 @@ public class GHCommitComment extends GHObject {
GHCommitComment wrap(GHRepository owner) {
this.owner = owner;
if (owner.root.isOffline()) {
user.wrapUp(owner.root);
}
return this;
}
}

View File

@@ -23,6 +23,8 @@
*/
package org.kohsuke.github;
import java.io.IOException;
/**
* Identifies a commit in {@link GHPullRequest}.
*
@@ -69,6 +71,13 @@ public class GHCommitPointer {
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);

View File

@@ -92,8 +92,8 @@ public class GHCommitQueryBuilder {
*/
public PagedIterable<GHCommit> list() {
return new PagedIterable<GHCommit>() {
public PagedIterator<GHCommit> iterator() {
return new PagedIterator<GHCommit>(req.asIterator(repo.getApiTailUrl("commits"), GHCommit[].class)) {
public PagedIterator<GHCommit> _iterator(int pageSize) {
return new PagedIterator<GHCommit>(req.asIterator(repo.getApiTailUrl("commits"), GHCommit[].class, pageSize)) {
protected void wrapUp(GHCommit[] page) {
for (GHCommit c : page)
c.wrapUp(repo);

View File

@@ -0,0 +1,137 @@
package org.kohsuke.github;
import java.io.IOException;
import org.apache.commons.lang.StringUtils;
/**
* Search commits.
*
* @author Marc de Verdelhan
* @see GitHub#searchCommits()
*/
@Preview @Deprecated
public class GHCommitSearchBuilder extends GHSearchBuilder<GHCommit> {
/*package*/ GHCommitSearchBuilder(GitHub root) {
super(root,CommitSearchResult.class);
req.withPreview(Previews.CLOAK);
}
/**
* Search terms.
*/
public GHCommitSearchBuilder q(String term) {
super.q(term);
return this;
}
public GHCommitSearchBuilder author(String v) {
return q("author:"+v);
}
public GHCommitSearchBuilder committer(String v) {
return q("committer:"+v);
}
public GHCommitSearchBuilder authorName(String v) {
return q("author-name:"+v);
}
public GHCommitSearchBuilder committerName(String v) {
return q("committer-name:"+v);
}
public GHCommitSearchBuilder authorEmail(String v) {
return q("author-email:"+v);
}
public GHCommitSearchBuilder committerEmail(String v) {
return q("committer-email:"+v);
}
public GHCommitSearchBuilder authorDate(String v) {
return q("author-date:"+v);
}
public GHCommitSearchBuilder committerDate(String v) {
return q("committer-date:"+v);
}
public GHCommitSearchBuilder merge(boolean merge) {
return q("merge:"+Boolean.valueOf(merge).toString().toLowerCase());
}
public GHCommitSearchBuilder hash(String v) {
return q("hash:"+v);
}
public GHCommitSearchBuilder parent(String v) {
return q("parent:"+v);
}
public GHCommitSearchBuilder tree(String v) {
return q("tree:"+v);
}
public GHCommitSearchBuilder is(String v) {
return q("is:"+v);
}
public GHCommitSearchBuilder user(String v) {
return q("user:"+v);
}
public GHCommitSearchBuilder org(String v) {
return q("org:"+v);
}
public GHCommitSearchBuilder repo(String v) {
return q("repo:"+v);
}
public GHCommitSearchBuilder order(GHDirection v) {
req.with("order",v);
return this;
}
public GHCommitSearchBuilder sort(Sort sort) {
req.with("sort",sort);
return this;
}
public enum Sort { AUTHOR_DATE, COMMITTER_DATE }
private static class CommitSearchResult extends SearchResult<GHCommit> {
private GHCommit[] items;
@Override
/*package*/ GHCommit[] getItems(GitHub root) {
for (GHCommit commit : items) {
String repoName = getRepoName(commit.url);
try {
GHRepository repo = root.getRepository(repoName);
commit.wrapUp(repo);
} catch (IOException ioe) {}
}
return items;
}
}
/**
* @param commitUrl a commit URL
* @return the repo name ("username/reponame")
*/
private static String getRepoName(String commitUrl) {
if (StringUtils.isBlank(commitUrl)) {
return null;
}
int indexOfUsername = (GitHub.GITHUB_URL + "/repos/").length();
String[] tokens = commitUrl.substring(indexOfUsername).split("/", 3);
return tokens[0] + '/' + tokens[1];
}
@Override
protected String getApiUrl() {
return "/search/commits";
}
}

View File

@@ -1,14 +1,14 @@
package org.kohsuke.github;
import java.io.IOException;
import java.util.Date;
import java.net.URL;
/**
* Represents a status of a commit.
*
* @author Kohsuke Kawaguchi
* @see GHRepository#getCommitStatus(String)
* @see GHCommit#getStatus()
* @see GHRepository#getLastCommitStatus(String)
* @see GHCommit#getLastStatus()
* @see GHRepository#createCommitStatus(String, GHCommitState, String, String)
*/
public class GHCommitStatus extends GHObject {
String state;
@@ -49,7 +49,15 @@ public class GHCommitStatus extends GHObject {
return creator;
}
public String getContext() {
return context;
}
public String getContext() {
return context;
}
/**
* @deprecated This object has no HTML URL.
*/
@Override
public URL getHtmlUrl() {
return null;
}
}

View File

@@ -1,9 +1,9 @@
package org.kohsuke.github;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.net.URL;
import java.util.Date;
/**
* The model user for comparing 2 commits in the GitHub API.
@@ -65,15 +65,27 @@ public class GHCompare {
return merge_base_commit;
}
/**
* Gets an array of commits.
* @return A copy of the array being stored in the class.
*/
public Commit[] getCommits() {
return commits;
Commit[] newValue = new Commit[commits.length];
System.arraycopy(commits, 0, newValue, 0, commits.length);
return newValue;
}
/**
* Gets an array of commits.
* @return A copy of the array being stored in the class.
*/
public GHCommit.File[] getFiles() {
return files;
}
GHCommit.File[] newValue = new GHCommit.File[files.length];
System.arraycopy(files, 0, newValue, 0, files.length);
return newValue;
}
public GHCompare wrap(GHRepository owner) {
public GHCompare wrap(GHRepository owner) {
this.owner = owner;
for (Commit commit : commits) {
commit.wrapUp(owner);
@@ -87,6 +99,8 @@ public class GHCompare {
* Compare commits had a child commit element with additional details we want to capture.
* This extenstion of GHCommit provides that.
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD"},
justification = "JSON API")
public static class Commit extends GHCommit {
private InnerCommit commit;

View File

@@ -1,8 +1,11 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.io.InputStream;
import javax.xml.bind.DatatypeConverter;
@@ -14,7 +17,13 @@ import javax.xml.bind.DatatypeConverter;
*/
@SuppressWarnings({"UnusedDeclaration"})
public class GHContent {
private GHRepository owner;
/*
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;
@@ -26,9 +35,10 @@ public class GHContent {
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 owner;
return repository;
}
public String getType() {
@@ -58,35 +68,35 @@ public class GHContent {
/**
* 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()}
*/
@SuppressFBWarnings("DM_DEFAULT_ENCODING")
public String getContent() throws IOException {
return new String(DatatypeConverter.parseBase64Binary(getEncodedContent()));
return new String(Base64.decodeBase64(getEncodedContent()));
}
/**
* Retrieve the raw content that is stored at this location.
* 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)
if (content!=null)
return content;
GHContent retrievedContent = owner.getFileContent(path);
this.size = retrievedContent.size;
this.sha = retrievedContent.sha;
this.content = retrievedContent.content;
this.url = retrievedContent.url;
this.git_url = retrievedContent.git_url;
this.html_url = retrievedContent.html_url;
return content;
else
return Base64.encodeBase64String(IOUtils.toByteArray(read()));
}
public String getUrl() {
@@ -101,6 +111,22 @@ public class GHContent {
return html_url;
}
/**
* Retrieves the actual content stored here.
*/
public InputStream read() throws IOException {
// if the download link is encoded with a token on the query string, the default behavior of POST will fail
return new Requester(root).method("GET").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);
}
@@ -109,6 +135,16 @@ public class GHContent {
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.
*/
@@ -117,21 +153,23 @@ public class GHContent {
throw new IllegalStateException(path+" is not a directory");
return new PagedIterable<GHContent>() {
public PagedIterator<GHContent> iterator() {
return new PagedIterator<GHContent>(owner.root.retrieve().asIterator(url, GHContent[].class)) {
public PagedIterator<GHContent> _iterator(int pageSize) {
return new PagedIterator<GHContent>(root.retrieve().asIterator(url, GHContent[].class, pageSize)) {
@Override
protected void wrapUp(GHContent[] page) {
GHContent.wrap(page,owner);
GHContent.wrap(page, repository);
}
};
}
};
}
@SuppressFBWarnings("DM_DEFAULT_ENCODING")
public GHContentUpdateResponse update(String newContent, String commitMessage) throws IOException {
return update(newContent.getBytes(), commitMessage, null);
}
@SuppressFBWarnings("DM_DEFAULT_ENCODING")
public GHContentUpdateResponse update(String newContent, String commitMessage, String branch) throws IOException {
return update(newContent.getBytes(), commitMessage, branch);
}
@@ -141,9 +179,9 @@ public class GHContent {
}
public GHContentUpdateResponse update(byte[] newContentBytes, String commitMessage, String branch) throws IOException {
String encodedContent = DatatypeConverter.printBase64Binary(newContentBytes);
String encodedContent = Base64.encodeBase64String(newContentBytes);
Requester requester = new Requester(owner.root)
Requester requester = new Requester(root)
.with("path", path)
.with("message", commitMessage)
.with("sha", sha)
@@ -156,8 +194,8 @@ public class GHContent {
GHContentUpdateResponse response = requester.to(getApiRoute(), GHContentUpdateResponse.class);
response.getContent().wrap(owner);
response.getCommit().wrapUp(owner);
response.getContent().wrap(repository);
response.getCommit().wrapUp(repository);
this.content = encodedContent;
return response;
@@ -168,7 +206,7 @@ public class GHContent {
}
public GHContentUpdateResponse delete(String commitMessage, String branch) throws IOException {
Requester requester = new Requester(owner.root)
Requester requester = new Requester(root)
.with("path", path)
.with("message", commitMessage)
.with("sha", sha)
@@ -180,18 +218,26 @@ public class GHContent {
GHContentUpdateResponse response = requester.to(getApiRoute(), GHContentUpdateResponse.class);
response.getCommit().wrapUp(owner);
response.getCommit().wrapUp(repository);
return response;
}
private String getApiRoute() {
return "/repos/" + owner.getOwnerName() + "/" + owner.getName() + "/contents/" + path;
return "/repos/" + repository.getOwnerName() + "/" + repository.getName() + "/contents/" + path;
}
GHContent wrap(GHRepository owner) {
this.owner = 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) {

View 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";
}
}

View File

@@ -0,0 +1,21 @@
package org.kohsuke.github;
/**
* {@link GHContent} with license information.
*
* @author Kohsuke Kawaguchi
* @see <a href="https://developer.github.com/v3/licenses/#get-a-repositorys-license">documentation</a>
* @see GHRepository#getLicense()
*/
@Preview @Deprecated
class GHContentWithLicense extends GHContent {
GHLicense license;
@Override
GHContentWithLicense wrap(GHRepository owner) {
super.wrap(owner);
if (license!=null)
license.wrap(owner.root);
return this;
}
}

View File

@@ -0,0 +1,114 @@
package org.kohsuke.github;
import java.io.IOException;
import java.net.URL;
/**
* Creates a repository
*
* @author Kohsuke Kawaguchi
*/
public class GHCreateRepositoryBuilder {
private final GitHub root;
protected final Requester builder;
private final String apiUrlTail;
/*package*/ GHCreateRepositoryBuilder(GitHub root, String apiUrlTail, String name) {
this.root = root;
this.apiUrlTail = apiUrlTail;
this.builder = new Requester(root);
this.builder.with("name",name);
}
public GHCreateRepositoryBuilder description(String description) {
this.builder.with("description",description);
return this;
}
public GHCreateRepositoryBuilder homepage(URL homepage) {
return homepage(homepage.toExternalForm());
}
public GHCreateRepositoryBuilder homepage(String homepage) {
this.builder.with("homepage",homepage);
return this;
}
/**
* Creates a private repository
*/
public GHCreateRepositoryBuilder private_(boolean b) {
this.builder.with("private",b);
return this;
}
/**
* Enables issue tracker
*/
public GHCreateRepositoryBuilder issues(boolean b) {
this.builder.with("has_issues",b);
return this;
}
/**
* Enables wiki
*/
public GHCreateRepositoryBuilder wiki(boolean b) {
this.builder.with("has_wiki",b);
return this;
}
/**
* Enables downloads
*/
public GHCreateRepositoryBuilder downloads(boolean b) {
this.builder.with("has_downloads",b);
return this;
}
/**
* If true, create an initial commit with empty README.
*/
public GHCreateRepositoryBuilder autoInit(boolean b) {
this.builder.with("auto_init",b);
return this;
}
/**
* Creates a default .gitignore
*
* See https://developer.github.com/v3/repos/#create
*/
public GHCreateRepositoryBuilder gitignoreTemplate(String language) {
this.builder.with("gitignore_template",language);
return this;
}
/**
* Desired license template to apply
*
* See https://developer.github.com/v3/repos/#create
*/
public GHCreateRepositoryBuilder licenseTemplate(String license) {
this.builder.with("license_template",license);
return this;
}
/**
* The team that gets granted access to this repository. Only valid for creating a repository in
* an organization.
*/
public GHCreateRepositoryBuilder team(GHTeam team) {
if (team!=null)
this.builder.with("team_id",team.getId());
return this;
}
/**
* Creates a repository with all the parameters.
*/
public GHRepository create() throws IOException {
return builder.method("POST").to(apiUrlTail, GHRepository.class).wrap(root);
}
}

View File

@@ -9,7 +9,7 @@ public class GHDeployKey {
protected String url, key, title;
protected boolean verified;
protected int id;
private GHRepository owner;
private GHRepository owner;
public int getId() {
return id;
@@ -31,10 +31,10 @@ public class GHDeployKey {
return verified;
}
public GHDeployKey wrap(GHRepository repo) {
this.owner = repo;
return this;
}
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();

View File

@@ -50,4 +50,12 @@ public class GHDeployment extends GHObject {
public String getSha(){
return sha;
}
/**
* @deprecated This object has no HTML URL.
*/
@Override
public URL getHtmlUrl() {
return null;
}
}

View File

@@ -1,6 +1,7 @@
package org.kohsuke.github;
import java.net.URL;
import java.util.Locale;
public class GHDeploymentStatus extends GHObject {
private GHRepository owner;
@@ -28,9 +29,16 @@ public class GHDeploymentStatus extends GHObject {
public URL getRepositoryUrl() {
return GitHub.parseURL(repository_url);
}
public GHDeploymentState getState() {
return GHDeploymentState.valueOf(state.toUpperCase());
return GHDeploymentState.valueOf(state.toUpperCase(Locale.ENGLISH));
}
/**
* @deprecated This object has no HTML URL.
*/
@Override
public URL getHtmlUrl() {
return null;
}
}

View File

@@ -1,6 +1,7 @@
package org.kohsuke.github;
import java.io.IOException;
import java.util.Locale;
public class GHDeploymentStatusBuilder {
private final Requester builder;
@@ -11,7 +12,7 @@ public class GHDeploymentStatusBuilder {
this.repo = repo;
this.deploymentId = deploymentId;
this.builder = new Requester(repo.root);
this.builder.with("state",state.toString().toLowerCase());
this.builder.with("state",state);
}
public GHDeploymentStatusBuilder description(String description) {

View File

@@ -0,0 +1,10 @@
package org.kohsuke.github;
/**
* Sort direction
*
* @author Kohsuke Kawaguchi
*/
public enum GHDirection {
ASC, DESC
}

View File

@@ -23,12 +23,16 @@
*/
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* Represents an email of GitHub.
*
* @author Kelly Campbell
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD", "NP_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD"}, justification = "JSON API")
public class GHEmail {
protected String email;

View File

@@ -1,12 +1,13 @@
package org.kohsuke.github;
import java.util.Locale;
/**
* Hook event type.
*
* See http://developer.github.com/v3/events/types/
*
* @author Kohsuke Kawaguchi
* @see GHEventInfo
* @see <a href="https://developer.github.com/v3/activity/events/types/">Event type reference</a>
*/
public enum GHEvent {
COMMIT_COMMENT,
@@ -20,15 +21,44 @@ public enum GHEvent {
FORK_APPLY,
GIST,
GOLLUM,
INSTALLATION,
INSTALLATION_REPOSITORIES,
ISSUE_COMMENT,
ISSUES,
LABEL,
MARKETPLACE_PURCHASE,
MEMBER,
MEMBERSHIP,
MILESTONE,
ORGANIZATION,
ORG_BLOCK,
PAGE_BUILD,
PROJECT_CARD,
PROJECT_COLUMN,
PROJECT,
PUBLIC,
PULL_REQUEST,
PULL_REQUEST_REVIEW,
PULL_REQUEST_REVIEW_COMMENT,
PUSH,
RELEASE,
REPOSITORY, // only valid for org hooks
STATUS,
TEAM,
TEAM_ADD,
WATCH
WATCH,
PING,
/**
* Special event type that means "every possible event"
*/
ALL;
/**
* Returns GitHub's internal representation of this event.
*/
String symbol() {
if (this==ALL) return "*";
return name().toLowerCase(Locale.ENGLISH);
}
}

View File

@@ -4,18 +4,21 @@ import java.io.IOException;
import java.util.Date;
import com.fasterxml.jackson.databind.node.ObjectNode;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* Represents an event.
*
* @author Kohsuke Kawaguchi
*/
@SuppressFBWarnings(value = "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", justification = "JSON API")
public class GHEventInfo {
private GitHub root;
// we don't want to expose Jackson dependency to the user. This needs databinding
private ObjectNode payload;
private long id;
private String created_at;
private String type;
@@ -27,8 +30,12 @@ public class GHEventInfo {
/**
* Inside the event JSON model, GitHub uses a slightly different format.
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" }, justification = "JSON API")
public static class GHEventRepository {
@SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now")
private int id;
@SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now")
private String url; // repository API URL
private String name; // owner/repo
}
@@ -48,6 +55,10 @@ public class GHEventInfo {
return this;
}
public long getId() {
return id;
}
public Date getCreatedAt() {
return GitHub.parseDate(created_at);
}
@@ -55,10 +66,14 @@ public class GHEventInfo {
/**
* Repository where the change was made.
*/
@SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" },
justification = "The field comes from JSON deserialization")
public GHRepository getRepository() throws IOException {
return root.getRepository(repo.name);
}
@SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" },
justification = "The field comes from JSON deserialization")
public GHUser getActor() throws IOException {
return root.getUser(actor.getLogin());
}
@@ -70,6 +85,8 @@ public class GHEventInfo {
return actor.getLogin();
}
@SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" },
justification = "The field comes from JSON deserialization")
public GHOrganization getOrganization() throws IOException {
return (org==null || org.getLogin()==null) ? null : root.getOrganization(org.getLogin());
}

View File

@@ -1,5 +1,8 @@
package org.kohsuke.github;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.Reader;
import java.util.List;
@@ -13,11 +16,28 @@ import java.util.List;
public abstract class GHEventPayload {
protected GitHub root;
private GHUser sender;
/*package*/ GHEventPayload() {
}
/**
* Gets the sender or {@code null} if accessed via the events API.
* @return the sender or {@code null} if accessed via the events API.
*/
public GHUser getSender() {
return sender;
}
public void setSender(GHUser sender) {
this.sender = sender;
}
/*package*/ void wrapUp(GitHub root) {
this.root = root;
if (sender != null) {
sender.wrapUp(root);
}
}
/**
@@ -25,6 +45,8 @@ public abstract class GHEventPayload {
*
* @see <a href="http://developer.github.com/v3/activity/events/types/#pullrequestevent">authoritative source</a>
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public static class PullRequest extends GHEventPayload {
private String action;
private int number;
@@ -67,12 +89,15 @@ public abstract class GHEventPayload {
*
* @see <a href="http://developer.github.com/v3/activity/events/types/#issuecommentevent">authoritative source</a>
*/
@SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" },
justification = "Constructed by JSON deserialization")
public static class IssueComment extends GHEventPayload {
private String action;
private GHIssueComment comment;
private GHIssue issue;
private GHRepository repository;
@SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization")
public String getAction() {
return action;
}
@@ -104,23 +129,350 @@ public abstract class GHEventPayload {
@Override
void wrapUp(GitHub root) {
super.wrapUp(root);
repository.wrap(root);
issue.wrap(repository);
if (repository != null) {
repository.wrap(root);
issue.wrap(repository);
} else {
issue.wrap(root);
}
comment.wrapUp(issue);
}
}
/**
* A comment was added to a commit
*
* @see <a href="http://developer.github.com/v3/activity/events/types/#commitcommentevent">authoritative source</a>
*/
@SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" },
justification = "Constructed by JSON deserialization")
public static class CommitComment extends GHEventPayload {
private String action;
private GHCommitComment comment;
private GHRepository repository;
@SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization")
public String getAction() {
return action;
}
public GHCommitComment getComment() {
return comment;
}
public void setComment(GHCommitComment comment) {
this.comment = comment;
}
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);
comment.wrap(repository);
}
}
}
/**
* A repository, branch, or tag was created
*
* @see <a href="http://developer.github.com/v3/activity/events/types/#createevent">authoritative source</a>
*/
@SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" },
justification = "Constructed by JSON deserialization")
public static class Create extends GHEventPayload {
private String ref;
@JsonProperty("ref_type")
private String refType;
@JsonProperty("master_branch")
private String masterBranch;
private String description;
private GHRepository repository;
@SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization")
public String getRef() {
return ref;
}
@SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization")
public String getRefType() {
return refType;
}
@SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization")
public String getMasterBranch() {
return masterBranch;
}
@SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization")
public String getDescription() {
return description;
}
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);
}
}
}
/**
* A branch, or tag was deleted
*
* @see <a href="http://developer.github.com/v3/activity/events/types/#deleteevent">authoritative source</a>
*/
@SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" },
justification = "Constructed by JSON deserialization")
public static class Delete extends GHEventPayload {
private String ref;
@JsonProperty("ref_type")
private String refType;
private GHRepository repository;
@SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization")
public String getRef() {
return ref;
}
@SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization")
public String getRefType() {
return refType;
}
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);
}
}
}
/**
* A deployment
*
* @see <a href="http://developer.github.com/v3/activity/events/types/#deploymentevent">authoritative source</a>
*/
@SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" },
justification = "Constructed by JSON deserialization")
public static class Deployment extends GHEventPayload {
private GHDeployment deployment;
private GHRepository repository;
public GHDeployment getDeployment() {
return deployment;
}
public void setDeployment(GHDeployment deployment) {
this.deployment = deployment;
}
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);
deployment.wrap(repository);
}
}
}
/**
* A deployment
*
* @see <a href="http://developer.github.com/v3/activity/events/types/#deploymentstatusevent">authoritative source</a>
*/
@SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" },
justification = "Constructed by JSON deserialization")
public static class DeploymentStatus extends GHEventPayload {
@JsonProperty("deployment_status")
private GHDeploymentStatus deploymentStatus;
private GHDeployment deployment;
private GHRepository repository;
public GHDeploymentStatus getDeploymentStatus() {
return deploymentStatus;
}
public void setDeploymentStatus(GHDeploymentStatus deploymentStatus) {
this.deploymentStatus = deploymentStatus;
}
public GHDeployment getDeployment() {
return deployment;
}
public void setDeployment(GHDeployment deployment) {
this.deployment = deployment;
}
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);
deployment.wrap(repository);
deploymentStatus.wrap(repository);
}
}
}
/**
* A user forked a repository
*
* @see <a href="http://developer.github.com/v3/activity/events/types/#forkevent">authoritative source</a>
*/
@SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" },
justification = "Constructed by JSON deserialization")
public static class Fork extends GHEventPayload {
private GHRepository forkee;
private GHRepository repository;
public GHRepository getForkee() {
return forkee;
}
public void setForkee(GHRepository forkee) {
this.forkee = forkee;
}
public GHRepository getRepository() {
return repository;
}
public void setRepository(GHRepository repository) {
this.repository = repository;
}
@Override
void wrapUp(GitHub root) {
super.wrapUp(root);
forkee.wrap(root);
if (repository != null) {
repository.wrap(root);
}
}
}
/**
* A ping.
*/
public static class Ping extends GHEventPayload {
private GHRepository repository;
private GHOrganization organization;
public void setRepository(GHRepository repository) {
this.repository = repository;
}
public GHRepository getRepository() {
return repository;
}
public GHOrganization getOrganization() {
return organization;
}
public void setOrganization(GHOrganization organization) {
this.organization = organization;
}
@Override
void wrapUp(GitHub root) {
super.wrapUp(root);
if (repository!=null)
repository.wrap(root);
if (organization != null) {
organization.wrapUp(root);
}
}
}
/**
* A repository was made public.
*
* @see <a href="http://developer.github.com/v3/activity/events/types/#publicevent">authoritative source</a>
*/
public static class Public extends GHEventPayload {
private GHRepository repository;
public void setRepository(GHRepository repository) {
this.repository = repository;
}
public GHRepository getRepository() {
return repository;
}
@Override
void wrapUp(GitHub root) {
super.wrapUp(root);
if (repository!=null)
repository.wrap(root);
}
}
/**
* A commit was pushed.
*
* @see <a href="http://developer.github.com/v3/activity/events/types/#pushevent">authoritative source</a>
*/
@SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD", "UUF_UNUSED_FIELD"},
justification = "Constructed by JSON deserialization")
public static class Push extends GHEventPayload {
private String head, before;
private boolean created, deleted, forced;
private String ref;
private int size;
private List<PushCommit> commits;
private GHRepository repository;
private Pusher pusher;
/**
* The SHA of the HEAD commit on the repository
@@ -137,6 +489,11 @@ public abstract class GHEventPayload {
return before;
}
@JsonSetter // alias
private void setAfter(String after) {
head = after;
}
/**
* The full Git ref that was pushed. Example: “refs/heads/master”
*/
@@ -152,6 +509,18 @@ public abstract class GHEventPayload {
return size;
}
public boolean isCreated() {
return created;
}
public boolean isDeleted() {
return deleted;
}
public boolean isForced() {
return forced;
}
/**
* The list of pushed commits.
*/
@@ -163,24 +532,59 @@ public abstract class GHEventPayload {
return repository;
}
public Pusher getPusher() {
return pusher;
}
public void setPusher(Pusher pusher) {
this.pusher = pusher;
}
@Override
void wrapUp(GitHub root) {
super.wrapUp(root);
if (repository!=null)
repository.wrap(root);
}
public static class Pusher {
private String name, email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
/**
* Commit in a push
*/
public static class PushCommit {
private GitUser author;
private GitUser committer;
private String url, sha, message;
private boolean distinct;
private List<String> added, removed, modified;
public GitUser getAuthor() {
return author;
}
public GitUser getCommitter() {
return committer;
}
/**
* Points to the commit API resource.
*/
@@ -192,6 +596,11 @@ public abstract class GHEventPayload {
return sha;
}
@JsonSetter
private void setId(String id) {
sha = id;
}
public String getMessage() {
return message;
}
@@ -202,6 +611,61 @@ public abstract class GHEventPayload {
public boolean isDistinct() {
return distinct;
}
public List<String> getAdded() {
return added;
}
public List<String> getRemoved() {
return removed;
}
public List<String> getModified() {
return modified;
}
}
}
/**
* A repository was created, deleted, made public, or made private.
*
* @see <a href="http://developer.github.com/v3/activity/events/types/#repositoryevent">authoritative source</a>
*/
@SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD", "UWF_UNWRITTEN_FIELD"},
justification = "Constructed by JSON deserialization")
public static class Repository extends GHEventPayload {
private String action;
private GHRepository repository;
private GHOrganization organization;
public String getAction() {
return action;
}
public void setRepository(GHRepository repository) {
this.repository = repository;
}
public GHRepository getRepository() {
return repository;
}
public GHOrganization getOrganization() {
return organization;
}
public void setOrganization(GHOrganization organization) {
this.organization = organization;
}
@Override
void wrapUp(GitHub root) {
super.wrapUp(root);
repository.wrap(root);
if (organization != null) {
organization.wrapUp(root);
}
}
}
}

View File

@@ -3,6 +3,7 @@ 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;
@@ -60,8 +61,8 @@ public class GHGist extends GHObject {
return git_push_url;
}
public String getHtmlUrl() {
return html_url;
public URL getHtmlUrl() {
return GitHub.parseURL(html_url);
}
public boolean isPublic() {
@@ -139,8 +140,8 @@ public class GHGist extends GHObject {
public PagedIterable<GHGist> listForks() {
return new PagedIterable<GHGist>() {
public PagedIterator<GHGist> iterator() {
return new PagedIterator<GHGist>(root.retrieve().asIterator(getApiTailUrl("forks"), GHGist[].class)) {
public PagedIterator<GHGist> _iterator(int pageSize) {
return new PagedIterator<GHGist>(root.retrieve().asIterator(getApiTailUrl("forks"), GHGist[].class, pageSize)) {
@Override
protected void wrapUp(GHGist[] page) {
try {

View File

@@ -1,6 +1,8 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
@@ -10,30 +12,24 @@ import java.util.Map;
/**
* @author Kohsuke Kawaguchi
*/
public class GHHook extends GHObject {
/**
* Repository that the hook belongs to.
*/
/*package*/ transient GHRepository repository;
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public abstract class GHHook extends GHObject {
String name;
List<String> events;
boolean active;
Map<String,String> config;
/*package*/ GHHook wrap(GHRepository owner) {
this.repository = owner;
return this;
}
public String getName() {
return name;
}
public EnumSet<GHEvent> getEvents() {
EnumSet<GHEvent> s = EnumSet.noneOf(GHEvent.class);
for (String e : events)
s.add(Enum.valueOf(GHEvent.class,e.toUpperCase(Locale.ENGLISH)));
for (String e : events) {
if (e.equals("*")) s.add(GHEvent.ALL);
else s.add(Enum.valueOf(GHEvent.class, e.toUpperCase(Locale.ENGLISH)));
}
return s;
}
@@ -45,10 +41,29 @@ public class GHHook extends GHObject {
return Collections.unmodifiableMap(config);
}
/**
* @see <a href="https://developer.github.com/v3/repos/hooks/#ping-a-hook">Ping hook</a>
*/
public void ping() throws IOException {
new Requester(getRoot()).method("POST").to(getApiRoute() + "/pings");
}
/**
* Deletes this hook.
*/
public void delete() throws IOException {
new Requester(repository.root).method("DELETE").to(String.format("/repos/%s/%s/hooks/%d", repository.getOwnerName(), repository.getName(), id));
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();
}

View File

@@ -0,0 +1,131 @@
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 {
GHHook [] hookArray = root.retrieve().to(collection(),collectionClass()); // jdk/eclipse bug requires this to be on separate line
List<GHHook> list = new ArrayList<GHHook>(Arrays.asList(hookArray));
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.symbol());
}
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);
}
}

View File

@@ -24,14 +24,21 @@
package org.kohsuke.github;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import static org.kohsuke.github.Previews.*;
/**
* Represents an issue on GitHub.
*
@@ -41,18 +48,21 @@ import java.util.Locale;
* @see GitHub#searchIssues()
* @see GHIssueSearchBuilder
*/
public class GHIssue extends GHObject {
public class GHIssue extends GHObject implements Reactable{
GitHub root;
GHRepository owner;
// API v3
protected GHUser assignee;
// API v3
protected GHUser assignee; // not sure what this field is now that 'assignees' exist
protected GHUser[] assignees;
protected String state;
protected int number;
protected String closed_at;
protected int comments;
@SkipFromToString
protected String body;
protected List<GHLabel> labels;
// 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;
@@ -73,9 +83,10 @@ public class GHIssue extends GHObject {
/*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);
if(assignee != null) assignee.wrapUp(root);
if(assignees!=null) GHUser.wrap(assignees,root);
if(user != null) user.wrapUp(root);
if(closed_by != null) closed_by.wrapUp(root);
return this;
}
@@ -126,22 +137,27 @@ public class GHIssue extends GHObject {
if(labels == null){
return Collections.emptyList();
}
return Collections.unmodifiableList(labels);
return Collections.<GHLabel>unmodifiableList(labels);
}
public Date getClosedAt() {
return GitHub.parseDate(closed_at);
}
public URL getApiURL(){
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 Requester(root).with("body",message).to(getIssuesApiRoute() + "/comments");
@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 {
@@ -175,7 +191,7 @@ public class GHIssue extends GHObject {
}
public void assignTo(GHUser user) throws IOException {
editIssue("assignee",user.getLogin());
setAssignees(user);
}
public void setLabels(String... labels) throws IOException {
@@ -184,20 +200,20 @@ public class GHIssue extends GHObject {
/**
* Obtains all the comments associated with this issue.
*
* @see #listComments()
*
* @see #listComments()
*/
public List<GHIssueComment> getComments() throws IOException {
return listComments().asList();
}
/**
* Obtains all the comments associated with this issue.
*/
public List<GHIssueComment> getComments() throws IOException {
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)) {
public PagedIterator<GHIssueComment> _iterator(int pageSize) {
return new PagedIterator<GHIssueComment>(root.retrieve().asIterator(getIssuesApiRoute() + "/comments", GHIssueComment[].class, pageSize)) {
protected void wrapUp(GHIssueComment[] page) {
for (GHIssueComment c : page)
c.wrapUp(GHIssue.this);
@@ -207,6 +223,63 @@ public class GHIssue extends GHObject {
};
}
@Preview @Deprecated
public GHReaction createReaction(ReactionContent content) throws IOException {
return new Requester(owner.root)
.withPreview(SQUIRREL_GIRL)
.with("content", content.getContent())
.to(getApiRoute()+"/reactions", GHReaction.class).wrap(root);
}
@Preview @Deprecated
public PagedIterable<GHReaction> listReactions() {
return new PagedIterable<GHReaction>() {
public PagedIterator<GHReaction> _iterator(int pageSize) {
return new PagedIterator<GHReaction>(owner.root.retrieve().withPreview(SQUIRREL_GIRL).asIterator(getApiRoute()+"/reactions", GHReaction[].class, pageSize)) {
@Override
protected void wrapUp(GHReaction[] page) {
for (GHReaction c : page)
c.wrap(owner.root);
}
};
}
};
}
public void addAssignees(GHUser... assignees) throws IOException {
addAssignees(Arrays.asList(assignees));
}
public void addAssignees(Collection<GHUser> assignees) throws IOException {
List<String> names = toLogins(assignees);
root.retrieve().method("POST").with("assignees",names).to(getIssuesApiRoute()+"/assignees",this);
}
public void setAssignees(GHUser... assignees) throws IOException {
setAssignees(Arrays.asList(assignees));
}
public void setAssignees(Collection<GHUser> assignees) throws IOException {
editIssue("assignees",toLogins(assignees));
}
public void removeAssignees(GHUser... assignees) throws IOException {
removeAssignees(Arrays.asList(assignees));
}
public void removeAssignees(Collection<GHUser> assignees) throws IOException {
List<String> names = toLogins(assignees);
root.retrieve().method("DELETE").with("assignees",names).inBody().to(getIssuesApiRoute()+"/assignees",this);
}
private List<String> toLogins(Collection<GHUser> assignees) {
List<String> names = new ArrayList<String>(assignees.size());
for (GHUser a : assignees) {
names.add(a.getLogin());
}
return names;
}
protected String getApiRoute() {
return getIssuesApiRoute();
}
@@ -215,16 +288,20 @@ public class GHIssue extends GHObject {
return "/repos/"+owner.getOwnerName()+"/"+owner.getName()+"/issues/"+number;
}
public GHUser getAssignee() {
return assignee;
}
public GHUser getAssignee() {
return assignee;
}
public List<GHUser> getAssignees() {
return Collections.unmodifiableList(Arrays.asList(assignees));
}
/**
* User who submitted the issue.
*/
public GHUser getUser() {
public GHUser getUser() {
return user;
}
}
/**
* Reports who has closed the issue.
@@ -234,46 +311,48 @@ public class GHIssue extends GHObject {
* 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;
}
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 PullRequest getPullRequest() {
return pull_request;
}
public boolean isPullRequest() {
return pull_request!=null;
}
public GHMilestone getMilestone() {
return milestone;
}
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);
}
}
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD"},
justification = "JSON API")
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);
}
}
}

View File

@@ -11,6 +11,7 @@ public class GHIssueBuilder {
private final GHRepository repo;
private final Requester builder;
private List<String> labels = new ArrayList<String>();
private List<String> assignees = new ArrayList<String>();
GHIssueBuilder(GHRepository repo, String title) {
this.repo = repo;
@@ -28,13 +29,13 @@ public class GHIssueBuilder {
public GHIssueBuilder assignee(GHUser user) {
if (user!=null)
builder.with("assignee",user.getLogin());
assignees.add(user.getLogin());
return this;
}
public GHIssueBuilder assignee(String user) {
if (user!=null)
builder.with("assignee",user);
assignees.add(user);
return this;
}
@@ -54,6 +55,6 @@ public class GHIssueBuilder {
* Creates a new issue.
*/
public GHIssue create() throws IOException {
return builder.with("labels",labels).to(repo.getApiTailUrl("issues"),GHIssue.class).wrap(repo);
return builder.with("labels",labels).with("assignees",assignees).to(repo.getApiTailUrl("issues"),GHIssue.class).wrap(repo);
}
}

View File

@@ -25,18 +25,19 @@ package org.kohsuke.github;
import java.io.IOException;
import java.net.URL;
import java.util.Date;
import static org.kohsuke.github.Previews.SQUIRREL_GIRL;
/**
* Comment to the issue
*
* @author Kohsuke Kawaguchi
*/
public class GHIssueComment extends GHObject {
public class GHIssueComment extends GHObject implements Reactable {
GHIssue owner;
private String body, gravatar_id;
private GHUser user;
private GHUser user; // not fully populated. beware.
/*package*/ GHIssueComment wrapUp(GHIssue owner) {
this.owner = owner;
@@ -69,6 +70,56 @@ public class GHIssueComment extends GHObject {
* Gets the user who posted this comment.
*/
public GHUser getUser() throws IOException {
return owner.root.getUser(user.getLogin());
return owner == null || owner.root.isOffline() ? user : 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());
}
@Preview @Deprecated
public GHReaction createReaction(ReactionContent content) throws IOException {
return new Requester(owner.root)
.withPreview(SQUIRREL_GIRL)
.with("content", content.getContent())
.to(getApiRoute()+"/reactions", GHReaction.class).wrap(owner.root);
}
@Preview @Deprecated
public PagedIterable<GHReaction> listReactions() {
return new PagedIterable<GHReaction>() {
public PagedIterator<GHReaction> _iterator(int pageSize) {
return new PagedIterator<GHReaction>(owner.root.retrieve().withPreview(SQUIRREL_GIRL).asIterator(getApiRoute()+"/reactions", GHReaction[].class, pageSize)) {
@Override
protected void wrapUp(GHReaction[] page) {
for (GHReaction c : page)
c.wrap(owner.root);
}
};
}
};
}
private String getApiRoute() {
return "/repos/"+owner.getRepository().getOwnerName()+"/"+owner.getRepository().getName()+"/issues/comments/" + id;
}
}

View File

@@ -1,9 +1,5 @@
package org.kohsuke.github;
import org.apache.commons.lang.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
@@ -12,21 +8,16 @@ import java.util.Locale;
* @author Kohsuke Kawaguchi
* @see GitHub#searchIssues()
*/
public class GHIssueSearchBuilder {
private final GitHub root;
private final Requester req;
private final List<String> terms = new ArrayList<String>();
public class GHIssueSearchBuilder extends GHSearchBuilder<GHIssue> {
/*package*/ GHIssueSearchBuilder(GitHub root) {
this.root = root;
req = root.retrieve();
super(root,IssueSearchResult.class);
}
/**
* Search terms.
*/
public GHIssueSearchBuilder q(String term) {
terms.add(term);
super.q(term);
return this;
}
@@ -50,8 +41,13 @@ public class GHIssueSearchBuilder {
return q("is:merged");
}
public GHIssueSearchBuilder order(GHDirection v) {
req.with("order",v);
return this;
}
public GHIssueSearchBuilder sort(Sort sort) {
req.with("sort",sort.toString().toLowerCase(Locale.ENGLISH));
req.with("sort",sort);
return this;
}
@@ -61,25 +57,15 @@ public class GHIssueSearchBuilder {
private GHIssue[] items;
@Override
public GHIssue[] getItems() {
/*package*/ GHIssue[] getItems(GitHub root) {
for (GHIssue i : items)
i.wrap(root);
return items;
}
}
/**
* Lists up the issues with the criteria built so far.
*/
public PagedSearchIterable<GHIssue> list() {
return new PagedSearchIterable<GHIssue>() {
public PagedIterator<GHIssue> iterator() {
req.set("q", StringUtils.join(terms," "));
return new PagedIterator<GHIssue>(adapt(req.asIterator("/search/issues", IssueSearchResult.class))) {
protected void wrapUp(GHIssue[] page) {
for (GHIssue c : page)
c.wrap(root);
}
};
}
};
@Override
protected String getApiUrl() {
return "/search/issues";
}
}

View File

@@ -24,7 +24,11 @@
package org.kohsuke.github;
/**
* @see GHPullRequestQueryBuilder#state(GHIssueState)
*/
public enum GHIssueState {
OPEN,
CLOSED
CLOSED,
ALL
}

View File

@@ -1,5 +1,6 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.lang.builder.ToStringBuilder;
/**
@@ -7,6 +8,7 @@ import org.apache.commons.lang.builder.ToStringBuilder;
*
* @author Kohsuke Kawaguchi
*/
@SuppressFBWarnings(value = "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", justification = "JSON API")
public class GHKey {
/*package almost final*/ GitHub root;
@@ -33,6 +35,10 @@ public class GHKey {
return url;
}
public GitHub getRoot() {
return root;
}
public boolean isVerified() {
return verified;
}

View File

@@ -0,0 +1,168 @@
/*
* The MIT License
*
* Copyright (c) 2016, Duncan Dickinson
*
* 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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import static org.kohsuke.github.Previews.DRAX;
/**
* The GitHub Preview API's license information
* <p>
* WARNING: This uses a PREVIEW API - subject to change.
*
* @author Duncan Dickinson
* @see GitHub#getLicense(String)
* @see GHRepository#getLicense()
* @see <a href="https://developer.github.com/v3/licenses/">https://developer.github.com/v3/licenses/</a>
*/
@Preview @Deprecated
@SuppressWarnings({"UnusedDeclaration"})
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public class GHLicense extends GHObject {
@SuppressFBWarnings("IS2_INCONSISTENT_SYNC") // root is set before the object is returned to the app
/*package almost final*/ GitHub root;
// these fields are always present, even in the short form
protected String key, name;
// the rest is only after populated
protected Boolean featured;
protected String html_url, description, category, implementation, body;
protected List<String> required = new ArrayList<String>();
protected List<String> permitted = new ArrayList<String>();
protected List<String> forbidden = new ArrayList<String>();
/**
* @return a mnemonic for the license
*/
public String getKey() {
return key;
}
/**
* @return the license name
*/
public String getName() {
return name;
}
/**
* @return API URL of this object.
*/
@WithBridgeMethods(value = String.class, adapterMethod = "urlToString")
public URL getUrl() {
return GitHub.parseURL(url);
}
/**
* Featured licenses are bold in the new repository drop-down
*
* @return True if the license is featured, false otherwise
*/
public Boolean isFeatured() throws IOException {
populate();
return featured;
}
public URL getHtmlUrl() throws IOException {
populate();
return GitHub.parseURL(html_url);
}
public String getDescription() throws IOException {
populate();
return description;
}
public String getCategory() throws IOException {
populate();
return category;
}
public String getImplementation() throws IOException {
populate();
return implementation;
}
public List<String> getRequired() throws IOException {
populate();
return required;
}
public List<String> getPermitted() throws IOException {
populate();
return permitted;
}
public List<String> getForbidden() throws IOException {
populate();
return forbidden;
}
public String getBody() throws IOException {
populate();
return body;
}
/**
* 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 (description!=null) return; // already populated
root.retrieve().withPreview(DRAX).to(url, this);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof GHLicense)) return false;
GHLicense that = (GHLicense) o;
return this.url.equals(that.url);
}
@Override
public int hashCode() {
return url.hashCode();
}
/*package*/ GHLicense wrap(GitHub root) {
this.root = root;
return this;
}
}

View File

@@ -0,0 +1,84 @@
package org.kohsuke.github;
import java.io.IOException;
import java.net.URL;
import java.util.Locale;
/**
* Represents a membership of a user in an organization.
*
* @author Kohsuke Kawaguchi
* @see GHMyself#listOrgMemberships()
*/
public class GHMembership /* extends GHObject --- but it doesn't have id, created_at, etc. */ {
GitHub root;
String url;
String state;
String role;
GHUser user;
GHOrganization organization;
public URL getUrl() {
return GitHub.parseURL(url);
}
public State getState() {
return Enum.valueOf(State.class, state.toUpperCase(Locale.ENGLISH));
}
public Role getRole() {
return Enum.valueOf(Role.class, role.toUpperCase(Locale.ENGLISH));
}
public GHUser getUser() {
return user;
}
public GHOrganization getOrganization() {
return organization;
}
/**
* Accepts a pending invitation to an organization.
*
* @see GHMyself#getMembership(GHOrganization)
*/
public void activate() throws IOException {
root.retrieve().method("PATCH").with("state",State.ACTIVE).to(url,this);
}
/*package*/ GHMembership wrap(GitHub root) {
this.root = root;
if (user!=null) user = root.getUser(user.wrapUp(root));
if (organization!=null) organization.wrapUp(root);
return this;
}
/*package*/ static void wrap(GHMembership[] page, GitHub root) {
for (GHMembership m : page)
m.wrap(root);
}
/**
* Role of a user in an organization.
*/
public enum Role {
/**
* Organization owner.
*/
ADMIN,
/**
* Non-owner organization member.
*/
MEMBER;
}
/**
* Whether a role is currently active or waiting for acceptance (pending)
*/
public enum State {
ACTIVE,
PENDING;
}
}

View File

@@ -1,6 +1,7 @@
package org.kohsuke.github;
import java.io.IOException;
import java.net.URL;
import java.util.Date;
import java.util.Locale;
@@ -11,60 +12,79 @@ import java.util.Locale;
*/
public class GHMilestone extends GHObject {
GitHub root;
GHRepository owner;
GHRepository owner;
GHUser creator;
private String state, due_on, title, description;
private int closed_issues, open_issues, number;
GHUser creator;
private String state, due_on, title, description, html_url;
private int closed_issues, open_issues, number;
protected String closed_at;
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 GHMilestoneState getState() {
return Enum.valueOf(GHMilestoneState.class, state.toUpperCase(Locale.ENGLISH));
}
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);
}
/**
* Closes this issue.
* When was this milestone closed?
*/
public Date getClosedAt() throws IOException {
return GitHub.parseDate(closed_at);
}
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 milestone.
*/
public void close() throws IOException {
edit("state", "closed");
}
/**
* Reopens this milestone.
*/
public void reopen() throws IOException {
edit("state", "open");
}
private void edit(String key, Object value) throws IOException {
new Requester(root)._with(key, value).method("PATCH").to(getApiRoute());
}
@@ -73,9 +93,9 @@ public class GHMilestone extends GHObject {
return "/repos/"+owner.getOwnerName()+"/"+owner.getName()+"/milestones/"+number;
}
public GHMilestone wrap(GHRepository repo) {
this.owner = repo;
this.root = repo.root;
return this;
}
public GHMilestone wrap(GHRepository repo) {
this.owner = repo;
this.root = repo.root;
return this;
}
}

View File

@@ -5,8 +5,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
@@ -17,6 +17,33 @@ import java.util.TreeMap;
* @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()}
@@ -109,16 +136,30 @@ public class GHMyself extends GHUser {
}
/**
* Lists up all the repositories this user owns (public and private) using the specified page size.
* 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, GHRepository[].class)) {
public PagedIterator<GHRepository> _iterator(int pageSize) {
return new PagedIterator<GHRepository>(root.retrieve().with("type",repoType).asIterator("/user/repos", GHRepository[].class, pageSize)) {
@Override
protected void wrapUp(GHRepository[] page) {
for (GHRepository c : page)
@@ -126,7 +167,7 @@ public class GHMyself extends GHUser {
}
};
}
};
}.withPageSize(pageSize);
}
/**
@@ -137,6 +178,39 @@ public class GHMyself extends GHUser {
return listRepositories();
}
/**
* List your organization memberships
*/
public PagedIterable<GHMembership> listOrgMemberships() {
return listOrgMemberships(null);
}
/**
* List your organization memberships
*
* @param state
* Filter by a specific state
*/
public PagedIterable<GHMembership> listOrgMemberships(final GHMembership.State state) {
return new PagedIterable<GHMembership>() {
public PagedIterator<GHMembership> _iterator(int pageSize) {
return new PagedIterator<GHMembership>(root.retrieve().with("state",state).asIterator("/user/memberships/orgs", GHMembership[].class, pageSize)) {
@Override
protected void wrapUp(GHMembership[] page) {
GHMembership.wrap(page,root);
}
};
}
};
}
/**
* Gets your membership in a specific organization.
*/
public GHMembership getMembership(GHOrganization o) throws IOException {
return root.retrieve().to("/user/memberships/orgs/"+o.getLogin(),GHMembership.class).wrap(root);
}
// public void addEmails(Collection<String> emails) throws IOException {
//// new Requester(root,ApiVersion.V3).withCredential().to("/user/emails");
// root.retrieveWithAuth3()

View File

@@ -0,0 +1,208 @@
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.min(Math.max(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";
long seconds = Integer.parseInt(v);
return System.currentTimeMillis() + seconds*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];
}

View File

@@ -1,14 +1,22 @@
package org.kohsuke.github;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.lang.reflect.FieldUtils;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Date;
/**
* Most (all?) domain objects in GitHub seems to have these 4 properties.
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public abstract class GHObject {
protected String url;
protected int id;
@@ -26,6 +34,7 @@ public abstract class GHObject {
return GitHub.parseDate(created_at);
}
@SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification = "Bridge method of getCreatedAt")
private Object createdAtStr(Date id, Class type) {
return created_at;
}
@@ -38,6 +47,12 @@ public abstract class GHObject {
return GitHub.parseURL(url);
}
/**
* URL of this object for humans, which renders some HTML.
*/
@WithBridgeMethods(value=String.class, adapterMethod="urlToString")
public abstract URL getHtmlUrl() throws IOException;
/**
* When was this resource last updated?
*/
@@ -53,11 +68,48 @@ public abstract class GHObject {
return id;
}
@SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification = "Bridge method of getId")
private Object intToString(int id, Class type) {
return String.valueOf(id);
}
@SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification = "Bridge method of getHtmlUrl")
private Object urlToString(URL url, Class type) {
return url==null ? null : url.toString();
}
/**
* String representation to assist debugging and inspection. The output format of this string
* is not a committed part of the API and is subject to change.
*/
@Override
public String toString() {
return new ReflectionToStringBuilder(this, TOSTRING_STYLE, null, null, false, false) {
@Override
protected boolean accept(Field field) {
return super.accept(field) && !field.isAnnotationPresent(SkipFromToString.class);
}
}.toString();
}
private static final ToStringStyle TOSTRING_STYLE = new ToStringStyle() {
{
this.setUseShortClassName(true);
}
@Override
public void append(StringBuffer buffer, String fieldName, Object value, Boolean fullDetail) {
// skip unimportant properties. '_' is a heuristics as important properties tend to have short names
if (fieldName.contains("_"))
return;
// avoid recursing other GHObject
if (value instanceof GHObject)
return;
// likewise no point in showing root
if (value instanceof GitHub)
return;
super.append(buffer,fieldName,value,fullDetail);
}
};
}

View 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);
}
}

View File

@@ -1,9 +1,11 @@
package org.kohsuke.github;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@@ -21,21 +23,35 @@ public class GHOrganization extends GHPerson {
*
* @return
* Newly created repository.
* @deprecated
* Use {@link #createRepository(String)} that uses a builder pattern to let you control every aspect.
*/
public GHRepository createRepository(String name, String description, String homepage, String team, boolean isPublic) throws IOException {
GHTeam t = getTeams().get(team);
if (t==null)
throw new IllegalArgumentException("No such team: "+team);
return createRepository(name,description,homepage,t,isPublic);
return createRepository(name, description, homepage, t, isPublic);
}
/**
* @deprecated
* Use {@link #createRepository(String)} that uses a builder pattern to let you control every aspect.
*/
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 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);
return createRepository(name).description(description).homepage(homepage).private_(!isPublic).team(team).create();
}
/**
* Starts a builder that creates a new repository.
*
* <p>
* You use the returned builder to set various properties, then call {@link GHCreateRepositoryBuilder#create()}
* to finally createa repository.
*/
public GHCreateRepositoryBuilder createRepository(String name) throws IOException {
return new GHCreateRepositoryBuilder(root,"/orgs/"+login+"/repos",name);
}
/**
@@ -54,8 +70,8 @@ public class GHOrganization extends GHPerson {
*/
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)) {
public PagedIterator<GHTeam> _iterator(int pageSize) {
return new PagedIterator<GHTeam>(root.retrieve().asIterator(String.format("/orgs/%s/teams", login), GHTeam[].class, pageSize)) {
@Override
protected void wrapUp(GHTeam[] page) {
for (GHTeam c : page)
@@ -77,6 +93,17 @@ public class GHOrganization extends GHPerson {
return null;
}
/**
* Finds a team that has the given slug in its {@link GHTeam#getSlug()}
*/
public GHTeam getTeamBySlug(String slug) throws IOException {
for (GHTeam t : listTeams()) {
if(t.getSlug().equals(slug))
return t;
}
return null;
}
/**
* Checks if this organization has the specified user as a member.
*/
@@ -147,9 +174,9 @@ public class GHOrganization extends GHPerson {
private PagedIterable<GHUser> listMembers(final String suffix, final String filter) throws IOException {
return new PagedIterable<GHUser>() {
public PagedIterator<GHUser> iterator() {
public PagedIterator<GHUser> _iterator(int pageSize) {
String filterParams = (filter == null) ? "" : ("?filter=" + filter);
return new PagedIterator<GHUser>(root.retrieve().asIterator(String.format("/orgs/%s/%s%s", login, suffix, filterParams), GHUser[].class)) {
return new PagedIterator<GHUser>(root.retrieve().asIterator(String.format("/orgs/%s/%s%s", login, suffix, filterParams), GHUser[].class, pageSize)) {
@Override
protected void wrapUp(GHUser[] users) {
GHUser.wrap(users, root);
@@ -172,7 +199,7 @@ 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 {
Requester post = new Requester(root).with("name", name).with("permission", p.name().toLowerCase());
Requester post = new Requester(root).with("name", name).with("permission", p);
List<String> repo_names = new ArrayList<String>();
for (GHRepository r : repositories) {
repo_names.add(r.getName());
@@ -182,7 +209,7 @@ public class GHOrganization extends GHPerson {
}
public GHTeam createTeam(String name, Permission p, GHRepository... repositories) throws IOException {
return createTeam(name,p, Arrays.asList(repositories));
return createTeam(name, p, Arrays.asList(repositories));
}
/**
@@ -193,7 +220,7 @@ public class GHOrganization extends GHPerson {
*/
public List<GHRepository> getRepositoriesWithOpenPullRequests() throws IOException {
List<GHRepository> r = new ArrayList<GHRepository>();
for (GHRepository repository : listRepositories()) {
for (GHRepository repository : listRepositories(100)) {
repository.wrap(root);
List<GHPullRequest> pullRequests = repository.getPullRequests(GHIssueState.OPEN);
if (pullRequests.size() > 0) {
@@ -219,8 +246,8 @@ public class GHOrganization extends GHPerson {
*/
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)) {
public PagedIterator<GHEventInfo> _iterator(int pageSize) {
return new PagedIterator<GHEventInfo>(root.retrieve().asIterator(String.format("/orgs/%s/events", login), GHEventInfo[].class, pageSize)) {
@Override
protected void wrapUp(GHEventInfo[] page) {
for (GHEventInfo c : page)
@@ -241,8 +268,8 @@ public class GHOrganization extends GHPerson {
@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)) {
public PagedIterator<GHRepository> _iterator(int pageSize) {
return new PagedIterator<GHRepository>(root.retrieve().asIterator("/orgs/" + login + "/repos?per_page=" + pageSize, GHRepository[].class, pageSize)) {
@Override
protected void wrapUp(GHRepository[] page) {
for (GHRepository c : page)
@@ -252,4 +279,39 @@ public class GHOrganization extends GHPerson {
}
};
}
/**
* 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);
}
}

View File

@@ -0,0 +1,59 @@
/*
* The MIT License
*
* Copyright 2016 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.kohsuke.github;
import java.util.Locale;
/**
* Permission for a user in a repository.
* @see <a href="https://developer.github.com/v3/repos/collaborators/#review-a-users-permission-level">API</a>
*/
/*package*/ class GHPermission {
private String permission;
private GHUser user;
/**
* @return one of {@code admin}, {@code write}, {@code read}, or {@code none}
*/
public String getPermission() {
return permission;
}
public GHPermissionType getPermissionType() {
return Enum.valueOf(GHPermissionType.class, permission.toUpperCase(Locale.ENGLISH));
}
public GHUser getUser() {
return user;
}
void wrapUp(GitHub root) {
if (user != null) {
user.root = root;
}
}
}

View File

@@ -0,0 +1,11 @@
package org.kohsuke.github;
/**
* @author Kohsuke Kawaguchi
*/
public enum GHPermissionType {
ADMIN,
WRITE,
READ,
NONE
}

View File

@@ -2,6 +2,7 @@ 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;
@@ -37,8 +38,12 @@ public abstract class GHPerson extends GHObject {
* 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
if (created_at!=null) {
return; // already populated
}
if (root.isOffline()) {
return; // cannot populate, will have to live with what we have
}
root.retrieve().to(url, this);
}
@@ -51,7 +56,7 @@ public abstract class GHPerson extends GHObject {
*/
public synchronized Map<String,GHRepository> getRepositories() throws IOException {
Map<String,GHRepository> repositories = new TreeMap<String, GHRepository>();
for (GHRepository r : listRepositories()) {
for (GHRepository r : listRepositories(100)) {
repositories.put(r.getName(),r);
}
return Collections.unmodifiableMap(repositories);
@@ -75,8 +80,8 @@ public abstract class GHPerson extends GHObject {
*/
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)) {
public PagedIterator<GHRepository> _iterator(int pageSize) {
return new PagedIterator<GHRepository>(root.retrieve().asIterator("/users/" + login + "/repos?per_page=" + pageSize, GHRepository[].class, pageSize)) {
@Override
protected void wrapUp(GHRepository[] page) {
for (GHRepository c : page)
@@ -88,10 +93,10 @@ public abstract class GHPerson extends GHObject {
}
/**
* Loads repository list in a pagenated fashion.
* Loads repository list in a paginated fashion.
*
* <p>
* For a person with a lot of repositories, GitHub returns the list of repositories in a pagenated fashion.
* For a person with a lot of repositories, GitHub returns the list of repositories in a paginated 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
@@ -103,7 +108,7 @@ public abstract class GHPerson extends GHObject {
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);
final Iterator<GHRepository[]> pager = root.retrieve().asIterator("/users/" + login + "/repos?per_page="+pageSize,GHRepository[].class, pageSize);
return new Iterator<List<GHRepository>>() {
public boolean hasNext() {
@@ -203,7 +208,7 @@ public abstract class GHPerson extends GHObject {
public Date getUpdatedAt() throws IOException {
populate();
return super.getCreatedAt();
return super.getUpdatedAt();
}
/**
@@ -214,8 +219,9 @@ public abstract class GHPerson extends GHObject {
return blog;
}
public String getHtmlUrl() {
return html_url;
@Override
public URL getHtmlUrl() {
return GitHub.parseURL(html_url);
}
/**

View File

@@ -10,6 +10,8 @@ import java.util.HashSet;
* @author Kohsuke Kawaguchi
*/
public class GHPersonSet<T extends GHPerson> extends HashSet<T> {
private static final long serialVersionUID = 1L;
public GHPersonSet() {
}

View File

@@ -25,9 +25,14 @@ package org.kohsuke.github;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Locale;
import java.util.List;
import javax.annotation.CheckForNull;
import static org.kohsuke.github.Previews.BLACK_CAT;
/**
* A pull request.
@@ -37,20 +42,21 @@ import java.util.Locale;
*/
@SuppressWarnings({"UnusedDeclaration"})
public class GHPullRequest extends GHIssue {
private String patch_url, diff_url, issue_url;
private GHCommitPointer base;
private String merged_at;
private GHCommitPointer head;
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;
private int review_comments, additions;
private boolean merged;
private Boolean mergeable;
private int deletions;
private String mergeable_state;
private int changed_files;
private String merge_commit_sha;
/**
* GitHub doesn't return some properties of {@link GHIssue} when requesting the GET on the 'pulls' API
@@ -60,15 +66,15 @@ public class GHPullRequest extends GHIssue {
private transient boolean fetchedIssueDetails;
GHPullRequest wrapUp(GHRepository owner) {
this.wrap(owner);
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 (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;
}
@@ -85,8 +91,8 @@ public class GHPullRequest extends GHIssue {
public URL getPatchUrl() {
return GitHub.parseURL(patch_url);
}
/**
/**
* The URL of the patch file.
* like https://github.com/jenkinsci/jenkins/pull/100.patch
*/
@@ -108,8 +114,8 @@ public class GHPullRequest extends GHIssue {
public GHCommitPointer getHead() {
return head;
}
@Deprecated
@Deprecated
public Date getIssueUpdatedAt() throws IOException {
return super.getUpdatedAt();
}
@@ -126,65 +132,73 @@ public class GHPullRequest extends GHIssue {
return GitHub.parseDate(merged_at);
}
@Override
public Collection<GHLabel> getLabels() throws IOException {
@Override
public Collection<GHLabel> getLabels() throws IOException {
fetchIssue();
return super.getLabels();
}
return super.getLabels();
}
@Override
public GHUser getClosedBy() {
return null;
}
public GHUser getClosedBy() {
return null;
}
@Override
public PullRequest getPullRequest() {
return null;
}
@Override
public PullRequest getPullRequest() {
return null;
}
//
// details that are only available via get with ID
//
//
// details that are only available via get with ID
//
//
public GHUser getMergedBy() throws IOException {
populate();
return merged_by;
}
return merged_by;
}
public int getReviewComments() throws IOException {
public int getReviewComments() throws IOException {
populate();
return review_comments;
}
return review_comments;
}
public int getAdditions() throws IOException {
public int getAdditions() throws IOException {
populate();
return additions;
}
return additions;
}
public boolean isMerged() throws IOException {
populate();
return merged;
}
return merged;
}
public Boolean getMergeable() throws IOException {
public Boolean getMergeable() throws IOException {
populate();
return mergeable;
}
return mergeable;
}
public int getDeletions() throws IOException {
public int getDeletions() throws IOException {
populate();
return deletions;
}
return deletions;
}
public String getMergeableState() throws IOException {
public String getMergeableState() throws IOException {
populate();
return mergeable_state;
}
return mergeable_state;
}
public int getChangedFiles() throws IOException {
public int getChangedFiles() throws IOException {
populate();
return changed_files;
}
return changed_files;
}
/**
* See <a href="https://developer.github.com/changes/2013-04-25-deprecating-merge-commit-sha">GitHub blog post</a>
*/
public String getMergeCommitSha() throws IOException {
populate();
return merge_commit_sha;
}
/**
* Fully populate the data by retrieving missing data.
@@ -193,27 +207,122 @@ public class GHPullRequest extends GHIssue {
*/
private void populate() throws IOException {
if (merged_by!=null) return; // already populated
if (root.isOffline()) {
return; // cannot populate, will have to live with what we have
}
root.retrieve().to(url, this).wrapUp(owner);
}
/**
* Retrieves all the files associated to this pull request.
*/
public PagedIterable<GHPullRequestFileDetail> listFiles() {
return new PagedIterable<GHPullRequestFileDetail>() {
public PagedIterator<GHPullRequestFileDetail> _iterator(int pageSize) {
return new PagedIterator<GHPullRequestFileDetail>(root.retrieve().asIterator(String.format("%s/files", getApiRoute()),
GHPullRequestFileDetail[].class, pageSize)) {
@Override
protected void wrapUp(GHPullRequestFileDetail[] page) {
}
};
}
};
}
/**
* Retrieves all the reviews associated to this pull request.
*/
public PagedIterable<GHPullRequestReview> listReviews() {
return new PagedIterable<GHPullRequestReview>() {
public PagedIterator<GHPullRequestReview> _iterator(int pageSize) {
return new PagedIterator<GHPullRequestReview>(root.retrieve()
.withPreview(BLACK_CAT)
.asIterator(String.format("%s/reviews", getApiRoute()),
GHPullRequestReview[].class, pageSize)) {
@Override
protected void wrapUp(GHPullRequestReview[] page) {
for (GHPullRequestReview r: page) {
r.wrapUp(GHPullRequest.this);
}
}
};
}
};
}
/**
* Obtains all the review comments associated with this pull request.
*/
public PagedIterable<GHPullRequestReviewComment> listReviewComments() throws IOException {
return new PagedIterable<GHPullRequestReviewComment>() {
public PagedIterator<GHPullRequestReviewComment> _iterator(int pageSize) {
return new PagedIterator<GHPullRequestReviewComment>(root.retrieve().asIterator(getApiRoute() + "/comments",
GHPullRequestReviewComment[].class, pageSize)) {
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() {
public PagedIterator<GHPullRequestCommitDetail> _iterator(int pageSize) {
return new PagedIterator<GHPullRequestCommitDetail>(root.retrieve().asIterator(
String.format("%s/commits", getApiURL()),
GHPullRequestCommitDetail[].class)) {
String.format("%s/commits", getApiRoute()),
GHPullRequestCommitDetail[].class, pageSize)) {
@Override
protected void wrapUp(GHPullRequestCommitDetail[] page) {
for (GHPullRequestCommitDetail c : page)
c.wrapUp(GHPullRequest.this);
}
};
}
};
}
@Preview
@Deprecated
public GHPullRequestReview createReview(String body, @CheckForNull GHPullRequestReviewState event,
GHPullRequestReviewComment... comments)
throws IOException {
return createReview(body, event, Arrays.asList(comments));
}
@Preview
@Deprecated
public GHPullRequestReview createReview(String body, @CheckForNull GHPullRequestReviewState event,
List<GHPullRequestReviewComment> comments)
throws IOException {
// if (event == null) {
// event = GHPullRequestReviewState.PENDING;
// }
List<DraftReviewComment> draftComments = new ArrayList<DraftReviewComment>(comments.size());
for (GHPullRequestReviewComment c : comments) {
draftComments.add(new DraftReviewComment(c.getBody(), c.getPath(), c.getPosition()));
}
return new Requester(root).method("POST")
.with("body", body)
//.with("event", event.name())
._with("comments", draftComments)
.withPreview(BLACK_CAT)
.to(getApiRoute() + "/reviews", GHPullRequestReview.class).wrapUp(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.
*
@@ -223,7 +332,21 @@ public class GHPullRequest extends GHIssue {
* Commit message. If null, the default one will be used.
*/
public void merge(String msg) throws IOException {
new Requester(root).method("PUT").with("commit_message",msg).to(getApiRoute()+"/merge");
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 {
@@ -232,4 +355,28 @@ public class GHPullRequest extends GHIssue {
fetchedIssueDetails = true;
}
}
private static class DraftReviewComment {
private String body;
private String path;
private int position;
public DraftReviewComment(String body, String path, int position) {
this.body = body;
this.path = path;
this.position = position;
}
public String getBody() {
return body;
}
public String getPath() {
return path;
}
public int getPosition() {
return position;
}
}
}

View File

@@ -24,6 +24,7 @@
package org.kohsuke.github;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.net.URL;
@@ -31,99 +32,119 @@ import java.net.URL;
* Commit detail inside a {@link GHPullRequest}.
*
* @author Luca Milanesio
* @see GHPullRequest#listCommits()
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD", "URF_UNREAD_FIELD"}, justification = "JSON API")
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 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 Tree getTree() {
return tree;
}
}
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;
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;
Commit commit;
String url;
String html_url;
public URL getUrl() {
return GitHub.parseURL(url);
}
public URL getHtml_url() {
return GitHub.parseURL(html_url);
}
String comments_url;
CommitPointer[] parents;
public String getSha() {
return sha;
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;
}
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() {
CommitPointer[] newValue = new CommitPointer[parents.length];
System.arraycopy(parents, 0, newValue, 0, parents.length);
return newValue;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,58 @@
package org.kohsuke.github;
/**
* Lists up pull requests with some filtering and sorting.
*
* @author Kohsuke Kawaguchi
* @see GHRepository#queryPullRequests()
*/
public class GHPullRequestQueryBuilder extends GHQueryBuilder<GHPullRequest> {
private final GHRepository repo;
/*package*/ GHPullRequestQueryBuilder(GHRepository repo) {
super(repo.root);
this.repo = repo;
}
public GHPullRequestQueryBuilder state(GHIssueState state) {
req.with("state",state);
return this;
}
public GHPullRequestQueryBuilder head(String head) {
req.with("head",head);
return this;
}
public GHPullRequestQueryBuilder base(String base) {
req.with("base",base);
return this;
}
public GHPullRequestQueryBuilder sort(Sort sort) {
req.with("sort",sort);
return this;
}
public enum Sort { CREATED, UPDATED, POPULARITY, LONG_RUNNING }
public GHPullRequestQueryBuilder direction(GHDirection d) {
req.with("direction",d);
return this;
}
@Override
public PagedIterable<GHPullRequest> list() {
return new PagedIterable<GHPullRequest>() {
public PagedIterator<GHPullRequest> _iterator(int pageSize) {
return new PagedIterator<GHPullRequest>(req.asIterator(repo.getApiTailUrl("pulls"), GHPullRequest[].class, pageSize)) {
@Override
protected void wrapUp(GHPullRequest[] page) {
for (GHPullRequest pr : page)
pr.wrapUp(repo);
}
};
}
};
}
}

View File

@@ -0,0 +1,149 @@
/*
* The MIT License
*
* Copyright (c) 2017, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.kohsuke.github;
import java.io.IOException;
import java.net.URL;
import static org.kohsuke.github.Previews.BLACK_CAT;
/**
* Review to the pull request
*
* @see GHPullRequest#listReviews()
* @see GHPullRequest#createReview(String, GHPullRequestReviewState, GHPullRequestReviewComment...)
*/
public class GHPullRequestReview extends GHObject {
GHPullRequest owner;
private String body;
private GHUser user;
private String commit_id;
private GHPullRequestReviewState state;
/*package*/ GHPullRequestReview wrapUp(GHPullRequest owner) {
this.owner = owner;
return this;
}
/**
* Gets the pull request to which this review is associated.
*/
public GHPullRequest getParent() {
return owner;
}
/**
* The comment itself.
*/
public String getBody() {
return body;
}
/**
* Gets the user who posted this review.
*/
public GHUser getUser() throws IOException {
return owner.root.getUser(user.getLogin());
}
public String getCommitId() {
return commit_id;
}
public GHPullRequestReviewState getState() {
return state;
}
@Override
public URL getHtmlUrl() {
return null;
}
protected String getApiRoute() {
return owner.getApiRoute()+"/reviews/"+id;
}
/**
* Updates the comment.
*/
@Preview
@Deprecated
public void submit(String body, GHPullRequestReviewState event) throws IOException {
new Requester(owner.root).method("POST")
.with("body", body)
.with("event", event.action())
.withPreview("application/vnd.github.black-cat-preview+json")
.to(getApiRoute()+"/events",this);
this.body = body;
this.state = event;
}
/**
* Deletes this review.
*/
@Preview
@Deprecated
public void delete() throws IOException {
new Requester(owner.root).method("DELETE")
.withPreview(BLACK_CAT)
.to(getApiRoute());
}
/**
* Dismisses this review.
*/
@Preview
@Deprecated
public void dismiss(String message) throws IOException {
new Requester(owner.root).method("PUT")
.with("message", message)
.withPreview(BLACK_CAT)
.to(getApiRoute()+"/dismissals");
state = GHPullRequestReviewState.DISMISSED;
}
/**
* Obtains all the review comments associated with this pull request review.
*/
@Preview
@Deprecated
public PagedIterable<GHPullRequestReviewComment> listReviewComments() throws IOException {
return new PagedIterable<GHPullRequestReviewComment>() {
public PagedIterator<GHPullRequestReviewComment> _iterator(int pageSize) {
return new PagedIterator<GHPullRequestReviewComment>(
owner.root.retrieve()
.withPreview(BLACK_CAT)
.asIterator(getApiRoute() + "/comments",
GHPullRequestReviewComment[].class, pageSize)) {
protected void wrapUp(GHPullRequestReviewComment[] page) {
for (GHPullRequestReviewComment c : page)
c.wrapUp(owner);
}
};
}
};
}
}

View File

@@ -0,0 +1,139 @@
/*
* 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;
import static org.kohsuke.github.Previews.*;
/**
* Review comment to the pull request
*
* @author Julien Henry
* @see GHPullRequest#listReviewComments()
* @see GHPullRequest#createReviewComment(String, String, String, int)
*/
public class GHPullRequestReviewComment extends GHObject implements Reactable {
GHPullRequest owner;
private String body;
private GHUser user;
private String path;
private int position;
private int originalPosition;
public static GHPullRequestReviewComment draft(String body, String path, int position) {
GHPullRequestReviewComment result = new GHPullRequestReviewComment();
result.body = body;
result.path = path;
result.position = position;
return result;
}
/*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());
}
@Preview @Deprecated
public GHReaction createReaction(ReactionContent content) throws IOException {
return new Requester(owner.root)
.withPreview(SQUIRREL_GIRL)
.with("content", content.getContent())
.to(getApiRoute()+"/reactions", GHReaction.class).wrap(owner.root);
}
@Preview @Deprecated
public PagedIterable<GHReaction> listReactions() {
return new PagedIterable<GHReaction>() {
public PagedIterator<GHReaction> _iterator(int pageSize) {
return new PagedIterator<GHReaction>(owner.root.retrieve().withPreview(SQUIRREL_GIRL).asIterator(getApiRoute()+"/reactions", GHReaction[].class, pageSize)) {
@Override
protected void wrapUp(GHReaction[] page) {
for (GHReaction c : page)
c.wrap(owner.root);
}
};
}
};
}
}

View File

@@ -0,0 +1,19 @@
package org.kohsuke.github;
public enum GHPullRequestReviewState {
PENDING(null),
APPROVED("APPROVE"),
REQUEST_CHANGES("REQUEST_CHANGES"),
COMMENTED("COMMENT"),
DISMISSED(null);
private final String _action;
GHPullRequestReviewState(String action) {
_action = action;
}
public String action() {
return _action;
}
}

View File

@@ -0,0 +1,21 @@
package org.kohsuke.github;
/**
* Used to specify filters, sort order, etc for listing items in a collection.
*
* @author Kohsuke Kawaguchi
*/
public abstract class GHQueryBuilder<T> {
protected final GitHub root;
protected final Requester req;
/*package*/ GHQueryBuilder(GitHub root) {
this.root = root;
this.req = root.retrieve();
}
/**
* Start listing items by using the settings built up on this object.
*/
public abstract PagedIterable<T> list();
}

View File

@@ -1,5 +1,8 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Date;
/**
* Rate limit.
* @author Kohsuke Kawaguchi
@@ -10,12 +13,30 @@ public class GHRateLimit {
*/
public int remaining;
/**
* Alotted API call per hour.
* 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
*/
@SuppressFBWarnings(value = "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR",
justification = "The value comes from JSON deserialization")
public Date getResetDate() {
return new Date(reset.getTime() * 1000);
}
@Override
public String toString() {
return remaining+"/"+limit;
return "GHRateLimit{" +
"remaining=" + remaining +
", limit=" + limit +
", resetDate=" + getResetDate() +
'}';
}
}

View File

@@ -0,0 +1,55 @@
package org.kohsuke.github;
import java.io.IOException;
import java.net.URL;
import static org.kohsuke.github.Previews.SQUIRREL_GIRL;
/**
* Reaction to issue, comment, PR, and so on.
*
* @author Kohsuke Kawaguchi
* @see Reactable
*/
@Preview @Deprecated
public class GHReaction extends GHObject {
private GitHub root;
private GHUser user;
private ReactionContent content;
/*package*/ GHReaction wrap(GitHub root) {
this.root = root;
user.wrapUp(root);
return this;
}
/**
* The kind of reaction left.
*/
public ReactionContent getContent() {
return content;
}
/**
* User who left the reaction.
*/
public GHUser getUser() {
return user;
}
/**
* Reaction has no HTML URL. Don't call this method.
*/
@Deprecated
public URL getHtmlUrl() {
return null;
}
/**
* Removes this reaction.
*/
public void delete() throws IOException {
new Requester(root).method("DELETE").withPreview(SQUIRREL_GIRL).to("/reactions/"+id);
}
}

View File

@@ -1,5 +1,6 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.net.URL;
@@ -77,7 +78,8 @@ public class GHRef {
return in;
}
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public static class GHObject {
private String type, sha, url;

View File

@@ -3,6 +3,7 @@ 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;
@@ -44,8 +45,14 @@ public class GHRelease extends GHObject {
return draft;
}
public String getHtmlUrl() {
return html_url;
public GHRelease setDraft(boolean draft) throws IOException {
edit("draft", draft);
this.draft = draft;
return this;
}
public URL getHtmlUrl() {
return GitHub.parseURL(html_url);
}
public String getName() {
@@ -69,7 +76,7 @@ public class GHRelease extends GHObject {
}
public Date getPublished_at() {
return published_at;
return new Date(published_at.getTime());
}
public GitHub getRoot() {
@@ -114,9 +121,7 @@ public class GHRelease extends GHObject {
* 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);
@@ -143,6 +148,13 @@ public class GHRelease extends GHObject {
new Requester(root).method("DELETE").to(owner.getApiTailUrl("releases/"+id));
}
/**
* Edit this release.
*/
private void edit(String key, Object value) throws IOException {
new Requester(root)._with(key, value).method("PATCH").to(owner.getApiTailUrl("releases/"+id));
}
private String getApiTailUrl(String end) {
return owner.getApiTailUrl(format("releases/%s/%s",id,end));
}

View File

@@ -33,7 +33,6 @@ public class GHReleaseBuilder {
*
* @param commitish Defaults to the repositorys default branch (usually "master"). Unused if the Git tag
* already exists.
* @return
*/
public GHReleaseBuilder commitish(String commitish) {
if (commitish != null) {

View 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

View File

@@ -0,0 +1,87 @@
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 order(GHDirection v) {
req.with("order",v);
return this;
}
public GHRepositorySearchBuilder sort(Sort sort) {
req.with("sort",sort);
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";
}
}

View File

@@ -0,0 +1,52 @@
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> extends GHQueryBuilder<T> {
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) {
super(root);
this.receiverType = receiverType;
}
/**
* Search terms.
*/
public GHQueryBuilder<T> q(String term) {
terms.add(term);
return this;
}
/**
* Performs the search.
*/
@Override
public PagedSearchIterable<T> list() {
return new PagedSearchIterable<T>(root) {
public PagedIterator<T> _iterator(int pageSize) {
req.set("q", StringUtils.join(terms, " "));
return new PagedIterator<T>(adapt(req.asIterator(getApiUrl(), receiverType, pageSize))) {
protected void wrapUp(T[] page) {
// SearchResult.getItems() should do it
}
};
}
};
}
protected abstract String getApiUrl();
}

View File

@@ -0,0 +1,51 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Date;
/**
* A stargazer at a repository on GitHub.
*
* @author noctarius
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public class GHStargazer {
private GHRepository repository;
private String starred_at;
private GHUser user;
/**
* Gets the repository that is stargazed
*
* @return the starred repository
*/
public GHRepository getRepository() {
return repository;
}
/**
* Gets the date when the repository was starred, however old stars before
* August 2012, will all show the date the API was changed to support starred_at.
*
* @return the date the stargazer was added
*/
public Date getStarredAt() {
return GitHub.parseDate(starred_at);
}
/**
* Gets the user that starred the repository
*
* @return the stargazer user
*/
public GHUser getUser() {
return user;
}
void wrapUp(GHRepository repository) {
this.repository = repository;
user.wrapUp(repository.root);
}
}

View File

@@ -4,9 +4,11 @@ import java.io.IOException;
import java.util.Date;
/**
* Represents your subscribing to a repository.
* 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;

View File

@@ -1,10 +1,14 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* Represents a tag in {@link GHRepository}
*
* @see GHRepository#listTags()
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public class GHTag {
private GHRepository owner;
private GitHub root;

View File

@@ -0,0 +1,60 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* Represents an annotated tag in a {@link GHRepository}
*
* @see GHRepository#getAnnotatedTag()
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public class GHTagObject {
private GHRepository owner;
private GitHub root;
private String tag;
private String sha;
private String url;
private String message;
private GitUser tagger;
private GHRef.GHObject object;
/*package*/ GHTagObject wrap(GHRepository owner) {
this.owner = owner;
this.root = owner.root;
return this;
}
public GHRepository getOwner() {
return owner;
}
public GitHub getRoot() {
return root;
}
public String getTag() {
return tag;
}
public String getSha() {
return sha;
}
public String getUrl() {
return url;
}
public String getMessage() {
return message;
}
public GitUser getTagger() {
return tagger;
}
public GHRef.GHObject getObject() {
return object;
}
}

View File

@@ -12,7 +12,7 @@ import java.util.TreeMap;
* @author Kohsuke Kawaguchi
*/
public class GHTeam {
private String name,permission;
private String name,permission,slug;
private int id;
private GHOrganization organization; // populated by GET /user/teams where Teams+Orgs are returned together
@@ -43,17 +43,21 @@ public class GHTeam {
return permission;
}
public String getSlug() {
return slug;
}
public int getId() {
return id;
}
/**
* Retrieves the current members.
*/
public PagedIterable<GHUser> listMembers() throws IOException {
/**
* 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)) {
public PagedIterator<GHUser> _iterator(int pageSize) {
return new PagedIterator<GHUser>(org.root.retrieve().asIterator(api("/members"), GHUser[].class, pageSize)) {
@Override
protected void wrapUp(GHUser[] page) {
GHUser.wrap(page, org.root);
@@ -64,8 +68,8 @@ public class GHTeam {
}
public Set<GHUser> getMembers() throws IOException {
return Collections.unmodifiableSet(listMembers().asSet());
}
return Collections.unmodifiableSet(listMembers().asSet());
}
/**
* Checks if this team has the specified user as a member.
@@ -89,8 +93,8 @@ public class GHTeam {
public PagedIterable<GHRepository> listRepositories() {
return new PagedIterable<GHRepository>() {
public PagedIterator<GHRepository> iterator() {
return new PagedIterator<GHRepository>(org.root.retrieve().asIterator(api("/repos"), GHRepository[].class)) {
public PagedIterator<GHRepository> _iterator(int pageSize) {
return new PagedIterator<GHRepository>(org.root.retrieve().asIterator(api("/repos"), GHRepository[].class, pageSize)) {
@Override
protected void wrapUp(GHRepository[] page) {
for (GHRepository r : page)
@@ -120,12 +124,25 @@ public class GHTeam {
}
public void add(GHRepository r) throws IOException {
org.root.retrieve().method("PUT").to(api("/repos/" + r.getOwnerName() + '/' + r.getName()), null);
add(r,null);
}
public void add(GHRepository r, GHOrganization.Permission permission) throws IOException {
org.root.retrieve().method("PUT")
.with("permission",permission)
.to(api("/repos/" + r.getOwnerName() + '/' + r.getName()), null);
}
public void remove(GHRepository r) throws IOException {
org.root.retrieve().method("DELETE").to(api("/repos/" + r.getOwnerName() + '/' + r.getName()), null);
}
/**
* Deletes this team.
*/
public void delete() throws IOException {
org.root.retrieve().method("DELETE").to(api(""));
}
private String api(String tail) {
return "/teams/"+id+tail;

View File

@@ -0,0 +1,149 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
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
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API")
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;
}
public String getLastCommentUrl() {
return subject.latest_comment_url;
}
/**
* 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;
}
}
}

View File

@@ -0,0 +1,75 @@
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 GHCommit#getTree()
* @see GHRepository#getTree(String)
* @see GHTreeEntry#asTree()
*/
public class GHTree {
/* package almost final */GHRepository repo;
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
*/
public List<GHTreeEntry> getTree() {
return Collections.unmodifiableList(Arrays.asList(tree));
}
/**
* Finds a tree entry by its name.
*
* IOW, find a directory entry by a file name.
*/
public GHTreeEntry getEntry(String path) {
for (GHTreeEntry e : tree) {
if (e.getPath().equals(path))
return e;
}
return null;
}
/**
* 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(GHRepository repo) {
this.repo = repo;
for (GHTreeEntry e : tree) {
e.tree = this;
}
return this;
}
}

View File

@@ -0,0 +1,108 @@
package org.kohsuke.github;
import java.io.IOException;
import java.io.InputStream;
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 {
/* package almost final */GHTree tree;
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", "tree", etc.
*
* @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);
}
/**
* If this tree entry represents a file, then return its information.
* Otherwise null.
*/
public GHBlob asBlob() throws IOException {
if (type.equals("blob"))
return tree.repo.getBlob(sha);
else
return null;
}
/**
* If this tree entry represents a file, then return its content.
* Otherwise null.
*/
public InputStream readAsBlob() throws IOException {
if (type.equals("blob"))
return tree.repo.readBlob(sha);
else
return null;
}
/**
* If this tree entry represents a directory, then return it.
* Otherwise null.
*/
public GHTree asTree() throws IOException {
if (type.equals("tree"))
return tree.repo.getTree(sha);
else
return null;
}
}

View File

@@ -26,7 +26,6 @@ package org.kohsuke.github;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@@ -56,8 +55,14 @@ public class GHUser extends GHPerson {
*/
@WithBridgeMethods(Set.class)
public GHPersonSet<GHUser> getFollows() throws IOException {
GHUser[] followers = root.retrieve().to("/users/" + login + "/following", GHUser[].class);
return new GHPersonSet<GHUser>(Arrays.asList(wrap(followers,root)));
return new GHPersonSet<GHUser>(listFollows().asList());
}
/**
* Lists the users that this user is following
*/
public PagedIterable<GHUser> listFollows() {
return listUser("following");
}
/**
@@ -65,8 +70,26 @@ public class GHUser extends GHPerson {
*/
@WithBridgeMethods(Set.class)
public GHPersonSet<GHUser> getFollowers() throws IOException {
GHUser[] followers = root.retrieve().to("/users/" + login + "/followers", GHUser[].class);
return new GHPersonSet<GHUser>(Arrays.asList(wrap(followers,root)));
return new GHPersonSet<GHUser>(listFollowers().asList());
}
/**
* Lists the users who are following this user.
*/
public PagedIterable<GHUser> listFollowers() {
return listUser("followers");
}
private PagedIterable<GHUser> listUser(final String suffix) {
return new PagedIterable<GHUser>() {
public PagedIterator<GHUser> _iterator(int pageSize) {
return new PagedIterator<GHUser>(root.retrieve().asIterator(getApiTailUrl(suffix), GHUser[].class, pageSize)) {
protected void wrapUp(GHUser[] page) {
GHUser.wrap(page,root);
}
};
}
};
}
/**
@@ -75,9 +98,20 @@ public class GHUser extends GHPerson {
* https://developer.github.com/v3/activity/watching/
*/
public PagedIterable<GHRepository> listSubscriptions() {
return listRepositories("subscriptions");
}
/**
* Lists all the repositories that this user has starred.
*/
public PagedIterable<GHRepository> listStarredRepositories() {
return listRepositories("starred");
}
private PagedIterable<GHRepository> listRepositories(final String suffix) {
return new PagedIterable<GHRepository>() {
public PagedIterator<GHRepository> iterator() {
return new PagedIterator<GHRepository>(root.retrieve().asIterator(getApiTailUrl("subscriptions"), GHRepository[].class)) {
public PagedIterator<GHRepository> _iterator(int pageSize) {
return new PagedIterator<GHRepository>(root.retrieve().asIterator(getApiTailUrl(suffix), GHRepository[].class, pageSize)) {
protected void wrapUp(GHRepository[] page) {
for (GHRepository c : page)
c.wrap(root);
@@ -133,8 +167,8 @@ public class GHUser extends GHPerson {
*/
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)) {
public PagedIterator<GHEventInfo> _iterator(int pageSize) {
return new PagedIterator<GHEventInfo>(root.retrieve().asIterator(String.format("/users/%s/events", login), GHEventInfo[].class, pageSize)) {
@Override
protected void wrapUp(GHEventInfo[] page) {
for (GHEventInfo c : page)
@@ -150,8 +184,8 @@ public class GHUser extends GHPerson {
*/
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)) {
public PagedIterator<GHGist> _iterator(int pageSize) {
return new PagedIterator<GHGist>(root.retrieve().asIterator(String.format("/users/%s/gists", login), GHGist[].class, pageSize)) {
@Override
protected void wrapUp(GHGist[] page) {
for (GHGist c : page)
@@ -162,11 +196,6 @@ public class GHUser extends GHPerson {
};
}
@Override
public String toString() {
return "User:"+login;
}
@Override
public int hashCode() {
return login.hashCode();
@@ -185,4 +214,9 @@ public class GHUser extends GHPerson {
if (tail.length()>0 && !tail.startsWith("/")) tail='/'+tail;
return "/users/" + login + tail;
}
/*package*/ GHUser wrapUp(GitHub root) {
super.wrapUp(root);
return this;
}
}

View File

@@ -0,0 +1,77 @@
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 order(GHDirection v) {
req.with("order",v);
return this;
}
public GHUserSearchBuilder sort(Sort sort) {
req.with("sort",sort);
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";
}
}

View File

@@ -23,12 +23,16 @@
*/
package org.kohsuke.github;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;
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;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
@@ -43,13 +47,18 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import org.apache.commons.codec.Charsets;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
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;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
import static java.util.logging.Level.FINE;
import static org.kohsuke.github.Previews.DRAX;
/**
* Root of the GitHub API.
@@ -58,7 +67,7 @@ import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
* <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.
* operations to GitHub, such as updating &amp; deleting a repository at the same time.
*
* @author Kohsuke Kawaguchi
*/
@@ -75,8 +84,15 @@ public class GitHub {
private final String apiUrl;
/*package*/ final RateLimitHandler rateLimitHandler;
/*package*/ final AbuseLimitHandler abuseLimitHandler;
private HttpConnector connector = HttpConnector.DEFAULT;
private final Object headerRateLimitLock = new Object();
private GHRateLimit headerRateLimit = null;
private volatile GHRateLimit rateLimit = null;
/**
* Creates a client API root object.
*
@@ -113,7 +129,7 @@ public class GitHub {
* @param connector
* HttpConnector to use. Pass null to use default connector.
*/
/* package */ GitHub(String apiUrl, String login, String oauthAccessToken, String password, HttpConnector connector) throws IOException {
/* package */ GitHub(String apiUrl, String login, String oauthAccessToken, String password, HttpConnector connector, RateLimitHandler rateLimitHandler, AbuseLimitHandler abuseLimitHandler) throws IOException {
if (apiUrl.endsWith("/")) apiUrl = apiUrl.substring(0, apiUrl.length()-1); // normalize
this.apiUrl = apiUrl;
if (null != connector) this.connector = connector;
@@ -123,12 +139,16 @@ public class GitHub {
} else {
if (password!=null) {
String authorization = (login + ':' + password);
encodedAuthorization = "Basic "+new String(Base64.encodeBase64(authorization.getBytes()));
String charsetName = Charsets.UTF_8.name();
encodedAuthorization = "Basic "+new String(Base64.encodeBase64(authorization.getBytes(charsetName)), charsetName);
} else {// anonymous access
encodedAuthorization = null;
}
}
this.rateLimitHandler = rateLimitHandler;
this.abuseLimitHandler = abuseLimitHandler;
if (login==null && encodedAuthorization!=null)
login = getMyself().getLogin();
this.login = login;
@@ -190,6 +210,33 @@ public class GitHub {
return new GitHubBuilder().build();
}
/**
* Connects to GitHub Enterprise anonymously.
*
* All operations that requires authentication will fail.
*/
public static GitHub connectToEnterpriseAnonymously(String apiUrl) throws IOException {
return new GitHubBuilder().withEndpoint(apiUrl).build();
}
/**
* An offline-only {@link GitHub} useful for parsing event notification from an unknown source.
*
* All operations that require a connection will fail.
*
* @return An offline-only {@link GitHub}.
*/
public static GitHub offline() {
try {
return new GitHubBuilder()
.withEndpoint("https://api.github.invalid")
.withConnector(HttpConnector.OFFLINE)
.build();
} catch (IOException e) {
throw new IllegalStateException("The offline implementation constructor should not connect", e);
}
}
/**
* Is this an anonymous connection
* @return {@code true} if operations that require authentication will fail.
@@ -198,10 +245,22 @@ public class GitHub {
return login==null && encodedAuthorization==null;
}
/**
* Is this an always offline "connection".
* @return {@code true} if this is an always offline "connection".
*/
public boolean isOffline() {
return connector == HttpConnector.OFFLINE;
}
public HttpConnector getConnector() {
return connector;
}
public String getApiUrl() {
return apiUrl;
}
/**
* Sets the custom connector used to make requests to GitHub.
*/
@@ -235,23 +294,69 @@ public class GitHub {
*/
public GHRateLimit getRateLimit() throws IOException {
try {
return retrieve().to("/rate_limit", JsonRateLimit.class).rate;
return rateLimit = 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;
return r;
long hour = 60L * 60L; // this is madness, storing the date as seconds in a Date object
r.reset = new Date(System.currentTimeMillis() / 1000L + hour);
return rateLimit = r;
}
}
/*package*/ void updateRateLimit(@Nonnull GHRateLimit observed) {
synchronized (headerRateLimitLock) {
if (headerRateLimit == null
|| headerRateLimit.getResetDate().getTime() < observed.getResetDate().getTime()
|| headerRateLimit.remaining > observed.remaining) {
headerRateLimit = observed;
LOGGER.log(FINE, "Rate limit now: {0}", headerRateLimit);
}
}
}
/**
* Gets the {@link GHUser} that represents yourself.
*/
* Returns the most recently observed rate limit data or {@code null} if either there is no rate limit
* (for example GitHub Enterprise) or if no requests have been made.
*
* @return the most recently observed rate limit data or {@code null}.
*/
@CheckForNull
public GHRateLimit lastRateLimit() {
synchronized (headerRateLimitLock) {
return headerRateLimit;
}
}
/**
* Gets the current rate limit while trying not to actually make any remote requests unless absolutely necessary.
*
* @return the current rate limit data.
* @throws IOException if we couldn't get the current rate limit data.
*/
@Nonnull
public GHRateLimit rateLimit() throws IOException {
synchronized (headerRateLimitLock) {
if (headerRateLimit != null) {
return headerRateLimit;
}
}
GHRateLimit rateLimit = this.rateLimit;
if (rateLimit == null || rateLimit.getResetDate().getTime() < System.currentTimeMillis()) {
rateLimit = getRateLimit();
}
return rateLimit;
}
/**
* Gets the {@link GHUser} that represents yourself.
*/
@WithBridgeMethods(GHUser.class)
public GHMyself getMyself() throws IOException {
requireCredential();
public GHMyself getMyself() throws IOException {
requireCredential();
GHMyself u = retrieve().to("/user", GHMyself.class);
@@ -259,20 +364,20 @@ public class GitHub {
users.put(u.getLogin(), 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) {
/**
* 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;
}
}
return u;
}
/**
@@ -286,11 +391,11 @@ public class GitHub {
/**
* Interns the given {@link GHUser}.
*/
protected GHUser getUser(GHUser orig) throws IOException {
protected GHUser getUser(GHUser orig) {
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;
@@ -314,6 +419,62 @@ public class GitHub {
String[] tokens = name.split("/");
return retrieve().to("/repos/" + tokens[0] + '/' + tokens[1], GHRepository.class).wrap(this);
}
/**
* Returns a list of popular open source licenses
*
* WARNING: This uses a PREVIEW API.
*
* @see <a href="https://developer.github.com/v3/licenses/">GitHub API - Licenses</a>
*
* @return a list of popular open source licenses
*/
@Preview @Deprecated
public PagedIterable<GHLicense> listLicenses() throws IOException {
return new PagedIterable<GHLicense>() {
public PagedIterator<GHLicense> _iterator(int pageSize) {
return new PagedIterator<GHLicense>(retrieve().withPreview(DRAX).asIterator("/licenses", GHLicense[].class, pageSize)) {
@Override
protected void wrapUp(GHLicense[] page) {
for (GHLicense c : page)
c.wrap(GitHub.this);
}
};
}
};
}
/**
* Returns a list of all users.
*/
public PagedIterable<GHUser> listUsers() throws IOException {
return new PagedIterable<GHUser>() {
public PagedIterator<GHUser> _iterator(int pageSize) {
return new PagedIterator<GHUser>(retrieve().asIterator("/users", GHUser[].class, pageSize)) {
@Override
protected void wrapUp(GHUser[] page) {
for (GHUser u : page)
u.wrapUp(GitHub.this);
}
};
}
};
}
/**
* Returns the full details for a license
*
* WARNING: This uses a PREVIEW API.
*
* @param key The license key provided from the API
* @return The license details
* @see GHLicense#getKey()
*/
@Preview @Deprecated
public GHLicense getLicense(String key) throws IOException {
return retrieve().withPreview(DRAX).to("/licenses/" + key, GHLicense.class);
}
/**
/**
* This method returns a shallowly populated organizations.
@@ -331,27 +492,27 @@ public class GitHub {
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);
/**
* 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;
}
return allMyTeams;
}
/**
* Public events visible to you. Equivalent of what's displayed on https://github.com/
@@ -390,17 +551,28 @@ public class GitHub {
/**
* Creates a new repository.
*
* To create a repository in an organization, see
* {@link GHOrganization#createRepository(String, String, String, GHTeam, boolean)}
*
* @return
* Newly created repository.
* @deprecated
* Use {@link #createRepository(String)} that uses a builder pattern to let you control every aspect.
*/
public GHRepository createRepository(String name, String description, String homepage, boolean isPublic) throws IOException {
Requester requester = new Requester(this)
.with("name", name).with("description", description).with("homepage", homepage)
.with("public", isPublic ? 1 : 0);
return requester.method("POST").to("/user/repos", GHRepository.class).wrap(this);
return createRepository(name).description(description).homepage(homepage).private_(!isPublic).create();
}
/**
* Starts a builder that creates a new repository.
*
* <p>
* You use the returned builder to set various properties, then call {@link GHCreateRepositoryBuilder#create()}
* to finally createa repository.
*
* <p>
* To create a repository in an organization, see
* {@link GHOrganization#createRepository(String, String, String, GHTeam, boolean)}
*/
public GHCreateRepositoryBuilder createRepository(String name) {
return new GHCreateRepositoryBuilder(this,"/user/repos",name);
}
/**
@@ -410,14 +582,50 @@ public class GitHub {
*
* @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);
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);
}
return requester.method("POST").to("/authorizations", GHAuthorization.class).wrap(this);
}
/**
* @see <a href="https://developer.github.com/v3/oauth_authorizations/#get-or-create-an-authorization-for-a-specific-app">docs</a>
*/
public GHAuthorization createOrGetAuth(String clientId, String clientSecret, List<String> scopes, String note,
String note_url)
throws IOException {
Requester requester = new Requester(this)
.with("client_secret", clientSecret)
.with("scopes", scopes)
.with("note", note)
.with("note_url", note_url);
return requester.method("PUT").to("/authorizations/clients/" + clientId, GHAuthorization.class);
}
/**
* @see <a href="https://developer.github.com/v3/oauth_authorizations/#delete-an-authorization">Delete an authorization</a>
*/
public void deleteAuth(long id) throws IOException {
retrieve().method("DELETE").to("/authorizations/" + id);
}
/**
* @see <a href="https://developer.github.com/v3/oauth_authorizations/#check-an-authorization">Check an authorization</a>
*/
public GHAuthorization checkAuth(@Nonnull String clientId, @Nonnull String accessToken) throws IOException {
return retrieve().to("/applications/" + clientId + "/tokens/" + accessToken, GHAuthorization.class);
}
/**
* @see <a href="https://developer.github.com/v3/oauth_authorizations/#reset-an-authorization">Reset an authorization</a>
*/
public GHAuthorization resetAuth(@Nonnull String clientId, @Nonnull String accessToken) throws IOException {
return retrieve().method("POST").to("/applications/" + clientId + "/tokens/" + accessToken, GHAuthorization.class);
}
/**
* Ensures that the credential is valid.
@@ -427,10 +635,98 @@ public class GitHub {
retrieve().to("/user", GHUser.class);
return true;
} catch (IOException e) {
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, "Exception validating credentials on " + this.apiUrl + " with login '" + this.login + "' " + e, e);
return false;
}
}
private static class GHApiInfo {
private String rate_limit_url;
void check(String apiUrl) throws IOException {
if (rate_limit_url==null)
throw new IOException(apiUrl+" doesn't look like GitHub API URL");
// make sure that the URL is legitimate
new URL(rate_limit_url);
}
}
/**
* Tests the connection.
*
* <p>
* Verify that the API URL and credentials are valid to access this GitHub.
*
* <p>
* This method returns normally if the endpoint is reachable and verified to be GitHub API URL.
* Otherwise this method throws {@link IOException} to indicate the problem.
*/
public void checkApiUrlValidity() throws IOException {
try {
retrieve().to("/", GHApiInfo.class).check(apiUrl);
} catch (IOException e) {
if (isPrivateModeEnabled()) {
throw (IOException)new IOException("GitHub Enterprise server (" + apiUrl + ") with private mode enabled").initCause(e);
}
throw e;
}
}
/**
* Ensures if a GitHub Enterprise server is configured in private mode.
*
* @return {@code true} if private mode is enabled. If it tries to use this method with GitHub, returns {@code
* false}.
*/
private boolean isPrivateModeEnabled() {
try {
HttpURLConnection uc = getConnector().connect(getApiURL("/"));
/*
$ curl -i https://github.mycompany.com/api/v3/
HTTP/1.1 401 Unauthorized
Server: GitHub.com
Date: Sat, 05 Mar 2016 19:45:01 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 130
Status: 401 Unauthorized
X-GitHub-Media-Type: github.v3
X-XSS-Protection: 1; mode=block
X-Frame-Options: deny
Content-Security-Policy: default-src 'none'
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
Access-Control-Allow-Origin: *
X-GitHub-Request-Id: dbc70361-b11d-4131-9a7f-674b8edd0411
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
X-Content-Type-Options: nosniff
*/
try {
return uc.getResponseCode() == HTTP_UNAUTHORIZED
&& uc.getHeaderField("X-GitHub-Media-Type") != null;
} finally {
// ensure that the connection opened by getResponseCode gets closed
try {
IOUtils.closeQuietly(uc.getInputStream());
} catch (IOException ignore) {
// ignore
}
IOUtils.closeQuietly(uc.getErrorStream());
}
} catch (IOException e) {
return false;
}
}
/**
* Search commits.
*/
@Preview @Deprecated
public GHCommitSearchBuilder searchCommits() {
return new GHCommitSearchBuilder(this);
}
/**
* Search issues.
*/
@@ -438,6 +734,81 @@ public class GitHub {
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 youve 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(int pageSize) {
return new PagedIterator<GHRepository>(retrieve().with("since",since).asIterator("/repositories", GHRepository[].class, pageSize)) {
@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) {
try {
@@ -475,4 +846,6 @@ public class GitHub {
}
/* package */ static final String GITHUB_URL = "https://api.github.com";
private static final Logger LOGGER = Logger.getLogger(GitHub.class.getName());
}

View File

@@ -1,6 +1,7 @@
package org.kohsuke.github;
import org.apache.commons.io.IOUtils;
import org.kohsuke.github.extras.ImpatientHttpConnector;
import java.io.File;
import java.io.FileInputStream;
@@ -9,102 +10,120 @@ import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL;
import java.util.Map;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.Properties;
/**
* Configures connection details and produces {@link GitHub}.
*
* @since 1.59
*/
public class GitHubBuilder {
private String endpoint = GitHub.GITHUB_URL;
// 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;
private AbuseLimitHandler abuseLimitHandler = AbuseLimitHandler.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 {
GitHubBuilder builder;
try {
builder = fromPropertyFile();
if (builder.user != null)
return builder;
else {
// this is the case where the ~/.github file exists but has no content.
builder = fromEnvironment();
if (builder.user != null)
return builder;
else
throw new IOException("Failed to resolve credentials from ~/.github or the environment.");
}
} catch (FileNotFoundException e) {
builder = fromEnvironment();
if (builder.user != null)
return builder;
else
throw new IOException("Failed to resolve credentials from ~/.github or the environment.", e);
}
Exception cause = null;
GitHubBuilder builder;
try {
builder = fromPropertyFile();
if (builder.oauthToken != null || builder.user != null)
return builder;
} catch (FileNotFoundException e) {
// fall through
cause = e;
}
builder = fromEnvironment();
if (builder.oauthToken != null || 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 {
Properties env = new Properties();
Object loginValue = System.getenv(loginVariableName);
if (loginValue != null)
env.put("login", loginValue);
Object passwordValue = System.getenv(passwordVariableName);
if (passwordValue != null)
env.put("password", passwordValue);
Object oauthValue = System.getenv(oauthVariableName);
if (oauthValue != null)
env.put("oauth", oauthValue);
return fromProperties(env);
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();
Map<String, String> env = System.getenv();
for (Map.Entry<String, String> element : env.entrySet()) {
props.put(element.getKey(), element.getValue());
}
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);
}
@@ -113,7 +132,7 @@ public class GitHubBuilder {
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;
@@ -131,6 +150,7 @@ public class GitHubBuilder {
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;
}
@@ -155,6 +175,14 @@ public class GitHubBuilder {
this.connector = connector;
return this;
}
public GitHubBuilder withRateLimitHandler(RateLimitHandler handler) {
this.rateLimitHandler = handler;
return this;
}
public GitHubBuilder withAbuseLimitHandler(AbuseLimitHandler handler) {
this.abuseLimitHandler = handler;
return this;
}
/**
* Configures {@linkplain #withConnector(HttpConnector) connector}
@@ -162,14 +190,14 @@ public class GitHubBuilder {
* the system default one.
*/
public GitHubBuilder withProxy(final Proxy p) {
return withConnector(new HttpConnector() {
return withConnector(new ImpatientHttpConnector(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);
return new GitHub(endpoint, user, oauthToken, password, connector, rateLimitHandler, abuseLimitHandler);
}
}

View File

@@ -1,5 +1,6 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Date;
/**
@@ -11,6 +12,8 @@ import java.util.Date;
*
* @author Kohsuke Kawaguchi
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public class GitUser {
private String name, email, date;

View File

@@ -1,8 +1,11 @@
package org.kohsuke.github;
import org.kohsuke.github.extras.ImpatientHttpConnector;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.TimeUnit;
/**
* Pluggability for customizing HTTP request behaviors or using altogether different library.
@@ -21,9 +24,18 @@ public interface HttpConnector {
/**
* Default implementation that uses {@link URL#openConnection()}.
*/
HttpConnector DEFAULT = new HttpConnector() {
HttpConnector DEFAULT = new ImpatientHttpConnector(new HttpConnector() {
public HttpURLConnection connect(URL url) throws IOException {
return (HttpURLConnection) url.openConnection();
}
});
/**
* Stub implementation that is always off-line.
*/
HttpConnector OFFLINE = new HttpConnector() {
public HttpURLConnection connect(URL url) throws IOException {
throw new IOException("Offline");
}
};
}

View File

@@ -0,0 +1,118 @@
package org.kohsuke.github;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.annotation.CheckForNull;
/**
* {@link IOException} for http exceptions because {@link HttpURLConnection} throws un-discerned
* {@link IOException} and it can help to know the http response code to decide how to handle an
* http exceptions.
*
* @author <a href="mailto:cleclerc@cloudbees.com">Cyrille Le Clerc</a>
*/
public class HttpException extends IOException {
static final long serialVersionUID = 1L;
private final int responseCode;
private final String responseMessage;
private final String url;
/**
* @param message The detail message (which is saved for later retrieval
* by the {@link #getMessage()} method)
* @param responseCode Http response code. {@code -1} if no code can be discerned.
* @param responseMessage Http response message
* @param url The url that was invoked
* @see HttpURLConnection#getResponseCode()
* @see HttpURLConnection#getResponseMessage()
*/
public HttpException(String message, int responseCode, String responseMessage, String url) {
super(message);
this.responseCode = responseCode;
this.responseMessage = responseMessage;
this.url = url;
}
/**
* @param message The detail message (which is saved for later retrieval
* by the {@link #getMessage()} method)
* @param responseCode Http response code. {@code -1} if no code can be discerned.
* @param responseMessage Http response message
* @param url The url that was invoked
* @param cause The cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A null value is permitted,
* and indicates that the cause is nonexistent or unknown.)
* @see HttpURLConnection#getResponseCode()
* @see HttpURLConnection#getResponseMessage()
*/
public HttpException(String message, int responseCode, String responseMessage, String url, Throwable cause) {
super(message);
initCause(cause);
this.responseCode = responseCode;
this.responseMessage = responseMessage;
this.url = url;
}
/**
* @param responseCode Http response code. {@code -1} if no code can be discerned.
* @param responseMessage Http response message
* @param url The url that was invoked
* @param cause The cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A null value is permitted,
* and indicates that the cause is nonexistent or unknown.)
* @see HttpURLConnection#getResponseCode()
* @see HttpURLConnection#getResponseMessage()
*/
public HttpException(int responseCode, String responseMessage, String url, Throwable cause) {
super("Server returned HTTP response code: " + responseCode + ", message: '" + responseMessage + "'" +
" for URL: " + url);
initCause(cause);
this.responseCode = responseCode;
this.responseMessage = responseMessage;
this.url = url;
}
/**
* @param responseCode Http response code. {@code -1} if no code can be discerned.
* @param responseMessage Http response message
* @param url The url that was invoked
* @param cause The cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A null value is permitted,
* and indicates that the cause is nonexistent or unknown.)
* @see HttpURLConnection#getResponseCode()
* @see HttpURLConnection#getResponseMessage()
*/
public HttpException(int responseCode, String responseMessage, @CheckForNull URL url, Throwable cause) {
this(responseCode, responseMessage, url == null ? null : url.toString(), cause);
}
/**
* Http response code of the request that cause the exception
*
* @return {@code -1} if no code can be discerned.
*/
public int getResponseCode() {
return responseCode;
}
/**
* Http response message of the request that cause the exception
*
* @return {@code null} if no response message can be discerned.
*/
public String getResponseMessage() {
return responseMessage;
}
/**
* The http URL that caused the exception
*
* @return url
*/
public String getUrl() {
return url;
}
}

View 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);
}
}

View File

@@ -1,5 +1,6 @@
package org.kohsuke.github;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
@@ -11,7 +12,27 @@ import java.util.Set;
* @author Kohsuke Kawaguchi
*/
public abstract class PagedIterable<T> implements Iterable<T> {
public abstract PagedIterator<T> iterator();
/**
* Page size. 0 is default.
*/
private int size = 0;
/**
* Sets the pagination size.
*
* <p>
* When set to non-zero, each API call will retrieve this many entries.
*/
public PagedIterable<T> withPageSize(int size) {
this.size = size;
return this;
}
public final PagedIterator<T> iterator() {
return _iterator(size);
}
public abstract PagedIterator<T> _iterator(int pageSize);
/**
* Eagerly walk {@link Iterable} and return the result in a list.

View File

@@ -6,7 +6,7 @@ import java.util.List;
import java.util.NoSuchElementException;
/**
* Iterator over a pagenated data source.
* Iterator over a paginated data source.
*
* Aside from the normal iterator operation, this method exposes {@link #nextPage()}
* that allows the caller to retrieve items per page.

View File

@@ -1,5 +1,8 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.util.Iterator;
/**
@@ -7,12 +10,25 @@ import java.util.Iterator;
*
* @author Kohsuke Kawaguchi
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR"}, justification = "Constructed by JSON API")
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;
}
@Override
public PagedSearchIterable<T> withPageSize(int size) {
return (PagedSearchIterable<T>)super.withPageSize(size);
}
/**
* Returns the total number of hit, including the results that's not yet fetched.
*/
@@ -43,7 +59,7 @@ public abstract class PagedSearchIterable<T> extends PagedIterable<T> {
public T[] next() {
SearchResult<T> v = base.next();
if (result==null) result = v;
return v.getItems();
return v.getItems(root);
}
public void remove() {

View File

@@ -0,0 +1,18 @@
package org.kohsuke.github;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Indicates that the method/class/etc marked maps to GitHub API in the preview period.
*
* These APIs are subject to change and not a part of the backward compatibility commitment.
* Always used in conjunction with 'deprecated' to raise awareness to clients.
*
* @author Kohsuke Kawaguchi
*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Preview {
}

View File

@@ -0,0 +1,12 @@
package org.kohsuke.github;
/**
* @author Kohsuke Kawaguchi
*/
/*package*/ class Previews {
static final String LOKI = "application/vnd.github.loki-preview+json";
static final String DRAX = "application/vnd.github.drax-preview+json";
static final String SQUIRREL_GIRL = "application/vnd.github.squirrel-girl-preview";
static final String CLOAK = "application/vnd.github.cloak-preview";
static final String BLACK_CAT = "application/vnd.github.black-cat-preview+json";
}

View File

@@ -0,0 +1,62 @@
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)
* @see AbuseLimitHandler
*/
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);
}
};
}

View File

@@ -0,0 +1,23 @@
package org.kohsuke.github;
import java.io.IOException;
/**
* Those {@link GHObject}s that can have {@linkplain GHReaction reactions}.
*
* @author Kohsuke Kawaguchi
*/
@Preview @Deprecated
public interface Reactable {
/**
* List all the reactions left to this object.
*/
@Preview @Deprecated
PagedIterable<GHReaction> listReactions();
/**
* Leaves a reaction to this object.
*/
@Preview @Deprecated
GHReaction createReaction(ReactionContent content) throws IOException;
}

View File

@@ -0,0 +1,40 @@
package org.kohsuke.github;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* Content of reactions.
*
* @author Kohsuke Kawaguchi
* @see <a href="https://developer.github.com/v3/reactions/">API documentation</a>
* @see GHReaction
*/
public enum ReactionContent {
PLUS_ONE("+1"),
MINUS_ONE("-1"),
LAUGH("laugh"),
CONFUSED("confused"),
HEART("heart"),
HOORAY("hooray");
private final String content;
ReactionContent(String content) {
this.content = content;
}
@JsonValue
public String getContent() {
return content;
}
@JsonCreator
public static ReactionContent forContent(String content) {
for (ReactionContent c : ReactionContent.values()) {
if (c.getContent().equals(content))
return c;
}
return null;
}
}

View File

@@ -23,13 +23,12 @@
*/
package org.kohsuke.github;
import static org.kohsuke.github.GitHub.MAPPER;
import com.fasterxml.jackson.databind.JsonMappingException;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
@@ -37,24 +36,31 @@ import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import javax.annotation.WillClose;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import javax.net.ssl.HttpsURLConnection;
import static java.util.Arrays.asList;
import java.util.logging.Level;
import static java.util.logging.Level.*;
import static org.kohsuke.github.GitHub.MAPPER;
/**
* A builder pattern for making HTTP call and parsing its output.
@@ -64,6 +70,7 @@ import javax.net.ssl.HttpsURLConnection;
class Requester {
private final GitHub root;
private final List<Entry> args = new ArrayList<Entry>();
private final Map<String,String> headers = new LinkedHashMap<String, String>();
/**
* Request method.
@@ -72,6 +79,12 @@ class Requester {
private String contentType = "application/x-www-form-urlencoded";
private InputStream body;
/**
* Current connection.
*/
private HttpURLConnection uc;
private boolean forceBody;
private static class Entry {
String key;
Object value;
@@ -86,6 +99,24 @@ class Requester {
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);
}
public Requester withHeader(String name, String value) {
setHeader(name,value);
return this;
}
/*package*/ Requester withPreview(String name) {
return withHeader("Accept",name);
}
/**
* Makes a request with authentication credential.
*/
@@ -102,13 +133,25 @@ class Requester {
public Requester with(String key, Integer value) {
if (value!=null)
_with(key, value.intValue());
_with(key, value);
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, Enum e) {
if (e==null) return _with(key, null);
// by convention Java constant names are upper cases, but github uses
// lower-case constants. GitHub also uses '-', which in Java we always
// replace by '_'
return with(key, e.toString().toLowerCase(Locale.ENGLISH).replace('_', '-'));
}
public Requester with(String key, String value) {
return _with(key, value);
@@ -122,11 +165,16 @@ class Requester {
return _with(key, value);
}
public Requester with(InputStream body) {
public Requester with(@WillClose/*later*/ InputStream body) {
this.body = body;
return this;
}
public Requester withNullable(String key, Object value) {
args.add(new Entry(key, value));
return this;
}
public Requester _with(String key, Object value) {
if (value!=null) {
args.add(new Entry(key,value));
@@ -157,6 +205,16 @@ class Requester {
return this;
}
/**
* Small number of GitHub APIs use HTTP methods somewhat inconsistently, and use a body where it's not expected.
* Normally whether parameters go as query parameters or a body depends on the HTTP verb in use,
* but this method forces the parameters to be sent as a body.
*/
/*package*/ Requester inBody() {
forceBody = true;
return this;
}
public void to(String tailApiUrl) throws IOException {
to(tailApiUrl,null);
}
@@ -185,17 +243,30 @@ class Requester {
*/
@Deprecated
public <T> T to(String tailApiUrl, Class<T> type, String method) throws IOException {
return method(method).to(tailApiUrl,type);
return method(method).to(tailApiUrl, type);
}
@SuppressFBWarnings("SBSC_USE_STRINGBUFFER_CONCATENATION")
private <T> T _to(String tailApiUrl, Class<T> type, T instance) throws IOException {
while (true) {// loop while API rate limit is hit
HttpURLConnection uc = setupConnection(root.getApiURL(tailApiUrl));
if (!isMethodWithBody() && !args.isEmpty()) {
boolean questionMarkFound = tailApiUrl.indexOf('?') != -1;
tailApiUrl += questionMarkFound ? '&' : '?';
for (Iterator<Entry> it = args.listIterator(); it.hasNext();) {
Entry arg = it.next();
tailApiUrl += arg.key + '=' + URLEncoder.encode(arg.value.toString(),"UTF-8");
if (it.hasNext()) {
tailApiUrl += '&';
}
}
}
buildRequest(uc);
while (true) {// loop while API rate limit is hit
setupConnection(root.getApiURL(tailApiUrl));
buildRequest();
try {
T result = parse(uc, type, instance);
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\"")) {
@@ -216,7 +287,9 @@ class Requester {
}
return result;
} catch (IOException e) {
handleApiError(e,uc);
handleApiError(e);
} finally {
noteRateLimit(tailApiUrl);
}
}
}
@@ -226,20 +299,98 @@ class Requester {
*/
public int asHttpStatusCode(String tailApiUrl) throws IOException {
while (true) {// loop while API rate limit is hit
HttpURLConnection uc = setupConnection(root.getApiURL(tailApiUrl));
method("GET");
setupConnection(root.getApiURL(tailApiUrl));
buildRequest(uc);
buildRequest();
try {
return uc.getResponseCode();
} catch (IOException e) {
handleApiError(e,uc);
handleApiError(e);
} finally {
noteRateLimit(tailApiUrl);
}
}
}
private void buildRequest(HttpURLConnection uc) throws IOException {
if (!method.equals("GET")) {
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);
} finally {
noteRateLimit(tailApiUrl);
}
}
}
private void noteRateLimit(String tailApiUrl) {
if ("/rate_limit".equals(tailApiUrl)) {
// the rate_limit API is "free"
return;
}
if (tailApiUrl.startsWith("/search")) {
// the search API uses a different rate limit
return;
}
String limit = uc.getHeaderField("X-RateLimit-Limit");
if (StringUtils.isBlank(limit)) {
// if we are missing a header, return fast
return;
}
String remaining = uc.getHeaderField("X-RateLimit-Remaining");
if (StringUtils.isBlank(remaining)) {
// if we are missing a header, return fast
return;
}
String reset = uc.getHeaderField("X-RateLimit-Reset");
if (StringUtils.isBlank(reset)) {
// if we are missing a header, return fast
return;
}
GHRateLimit observed = new GHRateLimit();
try {
observed.limit = Integer.parseInt(limit);
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(FINEST)) {
LOGGER.log(FINEST, "Malformed X-RateLimit-Limit header value " + limit, e);
}
return;
}
try {
observed.remaining = Integer.parseInt(remaining);
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(FINEST)) {
LOGGER.log(FINEST, "Malformed X-RateLimit-Remaining header value " + remaining, e);
}
return;
}
try {
observed.reset = new Date(Long.parseLong(reset)); // this is madness, storing the date as seconds
root.updateRateLimit(observed);
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(FINEST)) {
LOGGER.log(FINEST, "Malformed X-RateLimit-Reset header value " + reset, 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);
@@ -263,117 +414,147 @@ class Requester {
}
}
private boolean isMethodWithBody() {
return forceBody || !METHODS_WITHOUT_BODY.contains(method);
}
/**
* Loads pagenated resources.
* Loads paginated resources.
*
* Every iterator call reports a new batch.
*/
/*package*/ <T> Iterator<T> asIterator(String _tailApiUrl, final Class<T> type) {
/*package*/ <T> Iterator<T> asIterator(String tailApiUrl, Class<T> type, int pageSize) {
method("GET");
if (pageSize!=0)
args.add(new Entry("per_page",pageSize));
StringBuilder s = new StringBuilder(tailApiUrl);
if (!args.isEmpty()) {
boolean first=true;
boolean first = true;
try {
for (Entry a : args) {
_tailApiUrl += first ? '?' : '&';
s.append(first ? '?' : '&');
first = false;
_tailApiUrl += URLEncoder.encode(a.key,"UTF-8")+'='+URLEncoder.encode(a.value.toString(),"UTF-8");
s.append(URLEncoder.encode(a.key, "UTF-8"));
s.append('=');
s.append(URLEncoder.encode(a.value.toString(), "UTF-8"));
}
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e); // UTF-8 is mandatory
}
}
final String tailApiUrl = _tailApiUrl;
try {
return new PagingIterator<T>(type, tailApiUrl, root.getApiURL(s.toString()));
} catch (IOException e) {
throw new Error(e);
}
}
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;
class PagingIterator<T> implements Iterator<T> {
{
try {
url = root.getApiURL(tailApiUrl);
} catch (IOException e) {
throw new Error(e);
}
}
private final Class<T> type;
private final String tailApiUrl;
public boolean hasNext() {
fetch();
return next!=null;
}
/**
* The next batch to be returned from {@link #next()}.
*/
private T next;
public T next() {
fetch();
T r = next;
if (r==null) throw new NoSuchElementException();
next = null;
return r;
}
/**
* URL of the next resource to be retrieved, or null if no more data is available.
*/
private URL url;
public void remove() {
throw new UnsupportedOperationException();
}
PagingIterator(Class<T> type, String tailApiUrl, URL url) {
this.type = type;
this.tailApiUrl = tailApiUrl;
this.url = url;
}
private void fetch() {
if (next!=null) return; // already fetched
if (url==null) return; // no more data to fetch
public boolean hasNext() {
fetch();
return next!=null;
}
try {
while (true) {// loop while API rate limit is hit
HttpURLConnection uc = setupConnection(url);
try {
next = parse(uc,type,null);
assert next!=null;
findNextURL(uc);
return;
} catch (IOException e) {
handleApiError(e,uc);
}
}
} catch (IOException e) {
throw new Error(e);
}
}
public T next() {
fetch();
T r = next;
if (r==null) throw new NoSuchElementException();
next = null;
return r;
}
/**
* Locate the next page from the pagination "Link" tag.
*/
private void findNextURL(HttpURLConnection uc) throws MalformedURLException {
url = null; // start defensively
String link = uc.getHeaderField("Link");
if (link==null) return;
public void remove() {
throw new UnsupportedOperationException();
}
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));
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);
} finally {
noteRateLimit(tailApiUrl);
}
}
// no more "next" link. we are done.
} 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 HttpURLConnection setupConnection(URL url) throws IOException {
HttpURLConnection uc = root.getConnector().connect(url);
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);
}
setRequestMethod(uc);
uc.setRequestProperty("Accept-Encoding", "gzip");
}
private void setRequestMethod(HttpURLConnection uc) throws IOException {
try {
uc.setRequestMethod(method);
} catch (ProtocolException e) {
@@ -385,21 +566,65 @@ class Requester {
} catch (Exception x) {
throw (IOException)new IOException("Failed to set the custom verb").initCause(x);
}
// sun.net.www.protocol.https.DelegatingHttpsURLConnection delegates to another HttpURLConnection
try {
Field $delegate = uc.getClass().getDeclaredField("delegate");
$delegate.setAccessible(true);
Object delegate = $delegate.get(uc);
if (delegate instanceof HttpURLConnection) {
HttpURLConnection nested = (HttpURLConnection) delegate;
setRequestMethod(nested);
}
} catch (NoSuchFieldException x) {
// no problem
} catch (IllegalAccessException x) {
throw (IOException)new IOException("Failed to set the custom verb").initCause(x);
}
}
uc.setRequestProperty("Accept-Encoding", "gzip");
return uc;
if (!uc.getRequestMethod().equals(method))
throw new IllegalStateException("Failed to set the request method to "+method);
}
private <T> T parse(HttpURLConnection uc, Class<T> type, T instance) throws IOException {
private <T> T parse(Class<T> type, T instance) throws IOException {
return parse(type, instance, 2);
}
private <T> T parse(Class<T> type, T instance, int timeouts) throws IOException {
InputStreamReader r = null;
int responseCode = -1;
String responseMessage = null;
try {
r = new InputStreamReader(wrapStream(uc, uc.getInputStream()), "UTF-8");
responseCode = uc.getResponseCode();
responseMessage = uc.getResponseMessage();
if (responseCode == 304) {
return null; // special case handling for 304 unmodified, as the content will be ""
}
if (responseCode == 204 && type!=null && type.isArray()) {
// no content
return type.cast(Array.newInstance(type.getComponentType(),0));
}
r = new InputStreamReader(wrapStream(uc.getInputStream()), "UTF-8");
String data = IOUtils.toString(r);
if (type!=null)
return MAPPER.readValue(data,type);
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;
} catch (FileNotFoundException e) {
// java.net.URLConnection handles 404 exception has FileNotFoundException, don't wrap exception in HttpException
// to preserve backward compatibility
throw e;
} catch (IOException e) {
if (e instanceof SocketTimeoutException && timeouts > 0) {
LOGGER.log(Level.INFO, "timed out accessing " + uc.getURL() + "; will try " + timeouts + " more time(s)", e);
return parse(type, instance, timeouts - 1);
}
throw new HttpException(responseCode, responseMessage, uc.getURL(), e);
} finally {
IOUtils.closeQuietly(r);
}
@@ -408,7 +633,7 @@ class Requester {
/**
* Handles the "Content-Encoding" header.
*/
private InputStream wrapStream(HttpURLConnection uc, InputStream in) throws IOException {
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);
@@ -417,38 +642,56 @@ class Requester {
}
/**
* If the error is because of the API limit, wait 10 sec and return normally.
* Otherwise throw an exception reporting an error.
* Handle API error by either throwing it or by returning normally to retry.
*/
/*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
/*package*/ void handleApiError(IOException e) throws IOException {
int responseCode;
try {
responseCode = uc.getResponseCode();
} catch (IOException e2) {
// likely to be a network exception (e.g. SSLHandshakeException),
// uc.getResponseCode() and any other getter on the response will cause an exception
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, "Silently ignore exception retrieving response code for '" + uc.getURL() + "'" +
" handling exception " + e, e);
throw e;
}
InputStream es = wrapStream(uc.getErrorStream());
if (es != null) {
try {
Thread.sleep(10000);
return;
} catch (InterruptedException _) {
throw (InterruptedIOException)new InterruptedIOException().initCause(e);
String error = IOUtils.toString(es, "UTF-8");
if (e instanceof FileNotFoundException) {
// pass through 404 Not Found to allow the caller to handle it intelligently
e = (IOException) new FileNotFoundException(error).initCause(e);
} else if (e instanceof HttpException) {
HttpException http = (HttpException) e;
e = new HttpException(error, http.getResponseCode(), http.getResponseMessage(),
http.getUrl(), e);
} else {
e = (IOException) new IOException(error).initCause(e);
}
} finally {
IOUtils.closeQuietly(es);
}
}
if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) // 401 / Unauthorized == bad creds
throw e;
if (e instanceof FileNotFoundException)
throw e; // pass through 404 Not Found to allow the caller to handle it intelligently
InputStream es = wrapStream(uc, uc.getErrorStream());
try {
if (es!=null)
throw (IOException)new IOException(IOUtils.toString(es,"UTF-8")).initCause(e);
else
throw e;
} finally {
IOUtils.closeQuietly(es);
if ("0".equals(uc.getHeaderField("X-RateLimit-Remaining"))) {
root.rateLimitHandler.onError(e,uc);
return;
}
// Retry-After is not documented but apparently that field exists
if (responseCode == HttpURLConnection.HTTP_FORBIDDEN &&
uc.getHeaderField("Retry-After") != null) {
this.root.abuseLimitHandler.onError(e,uc);
return;
}
throw e;
}
private Set<String> toSet(String s) {
Set<String> r = new HashSet<String>();
for (String t : s.split(","))
r.add(t.trim());
return r;
}
private static final List<String> METHODS_WITHOUT_BODY = asList("GET", "DELETE");
private static final Logger LOGGER = Logger.getLogger(Requester.class.getName());
}

View File

@@ -1,13 +1,21 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* Represents the result of a search
*
* @author Kohsuke Kawaguchi
*/
abstract class SearchResult<T> {
@SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization")
int total_count;
@SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization")
boolean incomplete_results;
public abstract T[] getItems();
/**
* Wraps up the retrieved object and return them. Only called once.
*/
/*package*/ abstract T[] getItems(GitHub root);
}

View File

@@ -0,0 +1,16 @@
package org.kohsuke.github;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Ignores this field for {@link GHObject#toString()}
*
* @author Kohsuke Kawaguchi
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface SkipFromToString {
}

View File

@@ -0,0 +1,58 @@
package org.kohsuke.github.extras;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.kohsuke.github.HttpConnector;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.TimeUnit;
/**
* {@link HttpConnector} wrapper that sets timeout
*
* @author Kohsuke Kawaguchi
*/
public class ImpatientHttpConnector implements HttpConnector {
private final HttpConnector base;
private final int readTimeout, connectTimeout;
/**
* @param connectTimeout
* HTTP connection timeout in milliseconds
* @param readTimeout
* HTTP read timeout in milliseconds
*/
public ImpatientHttpConnector(HttpConnector base, int connectTimeout, int readTimeout) {
this.base = base;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
}
public ImpatientHttpConnector(HttpConnector base, int timeout) {
this(base,timeout,timeout);
}
public ImpatientHttpConnector(HttpConnector base) {
this(base,CONNECT_TIMEOUT,READ_TIMEOUT);
}
public HttpURLConnection connect(URL url) throws IOException {
HttpURLConnection con = base.connect(url);
con.setConnectTimeout(connectTimeout);
con.setReadTimeout(readTimeout);
return con;
}
/**
* Default connection timeout in milliseconds
*/
@SuppressFBWarnings("MS_SHOULD_BE_FINAL")
public static int CONNECT_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(10);
/**
* Default read timeout in milliseconds
*/
@SuppressFBWarnings("MS_SHOULD_BE_FINAL")
public static int READ_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(10);
}

View File

@@ -9,8 +9,7 @@ are used in favor of using string handle (such as `GHUser.isMemberOf(GHOrganizat
The library supports both github.com and GitHub Enterprise.
There are some corners of the GitHub API that's not yet implemented, but
the library is implemented with the right abstractions and libraries to make it very easy to improve the coverage.
Most of the GitHub APIs are covered, although there are some corners that are still not yet implemented.
Sample Usage
-----
@@ -42,4 +41,11 @@ This library comes with a pluggable connector to use different HTTP client imple
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).
[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();

View File

@@ -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">

View File

@@ -1,4 +1,6 @@
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GHRepository.Contributor;
import org.kohsuke.github.GHUser;
import org.kohsuke.github.GitHub;
import java.util.Collection;
@@ -8,10 +10,16 @@ import java.util.Collection;
*/
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());
GitHub gh = GitHub.connect();
for (Contributor c : gh.getRepository("kohsuke/yo").listContributors()) {
System.out.println(c);
}
}
private static void testRateLimit() throws Exception {
GitHub g = GitHub.connectAnonymously();
for (GHUser u : g.getOrganization("jenkinsci").listMembers()) {
u.getFollowersCount();
}
System.out.println(lst.size());
}
}

Some files were not shown because too many files have changed in this diff Show More