Compare commits

...

215 Commits

Author SHA1 Message Date
Kohsuke Kawaguchi
47409a9a99 [maven-release-plugin] prepare release github-api-1.89 2017-09-09 13:28:31 -07:00
Kohsuke Kawaguchi
60bfea2d3b Bug fix 2017-09-09 13:22:16 -07:00
Kohsuke Kawaguchi
d3ed8eaed5 Merge pull request #339 2017-09-09 13:07:18 -07:00
Kohsuke Kawaguchi
692dccf110 Massaging the change a bit 2017-09-09 13:03:18 -07:00
Kohsuke Kawaguchi
92caf98683 Reverting java1.6 change which I assume is accidental.
Not that I really care about Java5 but I think that change should
be done separatel & intentionally
2017-09-09 12:57:52 -07:00
Kohsuke Kawaguchi
6178d38895 connector usage is unsynchronized 2017-09-09 12:47:18 -07:00
Kohsuke Kawaguchi
40fb38a9ba Window focus problem 2017-09-09 12:45:58 -07:00
Kohsuke Kawaguchi
20e68d53fd Merge pull request #283 2017-09-09 12:45:02 -07:00
Kohsuke Kawaguchi
2d3557e049 Improved the intern logic
if the user record does not exist yet, there's no need to fetch that
eagerly, as they are fetched on demand via the populate() method.
2017-09-09 12:44:12 -07:00
Kohsuke Kawaguchi
d8f4bc7395 Added updater
This solves #331 differently
2017-09-09 12:31:10 -07:00
Kohsuke Kawaguchi
353f9bb809 pointless null check since the with method already does it 2017-09-09 12:23:40 -07:00
Kohsuke Kawaguchi
ccfe3ad4f7 Merge pull request #333 2017-09-09 12:17:39 -07:00
Kohsuke Kawaguchi
9012820c03 Massage the signature a bit.
AFAICT sha and merge_method are not mutually exclusive.
2017-09-09 12:17:21 -07:00
Kohsuke Kawaguchi
fe2af19e42 Unused constant 2017-09-09 12:15:21 -07:00
Kohsuke Kawaguchi
f721e053f1 Added convenience connector for OkHttp3
Note that the existing one needs to be kept for compatibility with OkHttp2
2017-09-09 12:11:36 -07:00
Kohsuke Kawaguchi
e6ad9feb84 Keeping Findbugs happy 2017-09-09 12:05:39 -07:00
Kohsuke Kawaguchi
635350c40e Additional naming consistency change 2017-09-09 12:02:44 -07:00
Kohsuke Kawaguchi
17edd33703 Reorganized imports following #337 2017-09-09 12:00:23 -07:00
Kohsuke Kawaguchi
b0f2a871c6 Merge pull request #337 2017-09-09 11:58:13 -07:00
Kohsuke Kawaguchi
8928a8a1dc Content type should be JSON by default when sending JSON.
This solves #350 a little differently.
2017-09-09 11:51:55 -07:00
Kohsuke Kawaguchi
2b6f37a6cc Merge pull request #361 2017-09-09 11:48:33 -07:00
Kohsuke Kawaguchi
f3a3b87861 Defined entry points 2017-09-09 11:48:25 -07:00
Kohsuke Kawaguchi
9cf6ee78d4 Pointless string conversion 2017-09-09 11:44:09 -07:00
Kohsuke Kawaguchi
bbc2f3962f Proper access control modifier 2017-09-09 11:44:02 -07:00
Kohsuke Kawaguchi
be49eb22d2 Merge pull request #368 2017-09-09 11:40:41 -07:00
Kohsuke Kawaguchi
fb47067215 Naming changes to emphasize that these are just traffic info 2017-09-09 11:40:28 -07:00
Kohsuke Kawaguchi
2c80ef178d Capture commonality between total and daily 2017-09-09 11:39:12 -07:00
Kohsuke Kawaguchi
9af8112148 Tightening up access control and use primitive type 2017-09-09 11:37:18 -07:00
Kohsuke Kawaguchi
57c36f437a [maven-release-plugin] prepare for next development iteration 2017-09-09 08:19:29 -07:00
Kohsuke Kawaguchi
5ed8a34566 [maven-release-plugin] prepare release github-api-1.88 2017-09-09 08:19:20 -07:00
Kohsuke Kawaguchi
ea8df9bd61 javadoc fix 2017-09-09 08:13:51 -07:00
Kohsuke Kawaguchi
b0c51e03b7 [maven-release-plugin] prepare for next development iteration 2017-09-09 08:06:27 -07:00
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
Matt Mitchell
f2a2ad90b7 Switch to a concurrent hash map 2017-08-29 11:30:25 -07:00
Matt Mitchell
cfe4c0c510 Merge remote-tracking branch 'upstream/master' into synchro-api 2017-08-29 10:38:54 -07: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
adw1n
a1819bf232 Added getClones method to GHRepository (https://developer.github.com/v3/repos/traffic/#clones). 2017-08-01 06:38:28 +02:00
adw1n
8accf07d46 Changed timestamp (GHRepositoryViews.DayViews.timestamp) field type from String to Date. 2017-08-01 04:02:10 +02:00
adw1n
6dcbace572 Added getViews method to GHRepository.
getViews implements https://developer.github.com/v3/repos/traffic/#views
2017-08-01 03:38:46 +02:00
Greg Gianforcaro
e90c86ec2f Remove Preview status, merge_method is now out of preview 2017-07-18 11:36:38 -04:00
Greg Gianforcaro
971ae1fa4d Merge branch 'master' into pr-merge-method 2017-07-18 11:33:20 -04: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
Serban Iordache
b537f9925b issue #360: Add support for committing multiple files 2017-07-07 16:21:11 +02: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
Kanstantsin Shautsou
be081eec3f Inject responce headers in GHObject and Exceptions.
GH has specific to GET/POST headers required for analysing in case of error.

Signed-off-by: Kanstantsin Shautsou <kanstantsin.sha@gmail.com>
2017-02-10 04:11:20 +03:00
Kanstantsin Shautsou
55b00a87f6 Set 1.6 level. I'm not so old.
Signed-off-by: Kanstantsin Shautsou <kanstantsin.sha@gmail.com>
2017-02-10 03:28:06 +03:00
Sebastian Kürten
429b26cee8 Ignore eclipse files 2017-02-09 18:18:08 +01:00
Sebastian Kürten
fafe6b0ff7 Remove unused imports
Especially also remove the unsued import of
javax.xml.bind.DatatypeConverter from GHContent which is non-public API
as of Java 8
2017-02-09 18:15:20 +01:00
Greg Gianforcaro
5b156006fb Add 'Preview' support for MergeMethod on GHPullRequest
- Add 'polaris' preview
- Add MergeMethod Enum
- Add merge method to GHPullRequest which takes a MergeMethod
2017-01-27 23:36:54 -05: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
Matt Mitchell
9f3f644b83 Add some level of synchronization to the root of the API
This adds some synchronization to the maps at the root of the API to avoid duplicated calls to the actual GH REST API.  Specifically this is targeted around the two maps, orgs and users.  This fix makes the GHPRB jenkins plugin behave much better when there are lots of projects that could build for a specific repo (even if only a few are actually triggered)

There are also a few fixes around GHUser and GHPullRequest
* GHPullRequest was checking a field that may be null (merged_by) when determining whether to fetch details.  An unmerged PR would make a bunch of Github API calls for each property accessed.
* Where GHUser was returned in various objects, we weren't going through the caching mechanism at the root, so calls to APIs on GHUSer often resulted in new REST calls.  Instead, return from the cache wherever possible.
2016-06-08 10:43:40 -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
Marcin Zajaczkowski
ce140460af [#269] Add reopen method on GHMilestone 2016-04-04 17:54:55 +02:00
121 changed files with 9247 additions and 277 deletions

4
.gitignore vendored
View File

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

26
pom.xml
View File

@@ -3,11 +3,11 @@
<parent> <parent>
<groupId>org.kohsuke</groupId> <groupId>org.kohsuke</groupId>
<artifactId>pom</artifactId> <artifactId>pom</artifactId>
<version>14</version> <version>17</version>
</parent> </parent>
<artifactId>github-api</artifactId> <artifactId>github-api</artifactId>
<version>1.75</version> <version>1.89</version>
<name>GitHub API for Java</name> <name>GitHub API for Java</name>
<url>http://github-api.kohsuke.org/</url> <url>http://github-api.kohsuke.org/</url>
<description>GitHub API for Java</description> <description>GitHub API for Java</description>
@@ -16,7 +16,7 @@
<connection>scm:git:git@github.com/kohsuke/${project.artifactId}.git</connection> <connection>scm:git:git@github.com/kohsuke/${project.artifactId}.git</connection>
<developerConnection>scm:git:ssh://git@github.com/kohsuke/${project.artifactId}.git</developerConnection> <developerConnection>scm:git:ssh://git@github.com/kohsuke/${project.artifactId}.git</developerConnection>
<url>http://${project.artifactId}.kohsuke.org/</url> <url>http://${project.artifactId}.kohsuke.org/</url>
<tag>github-api-1.75</tag> <tag>github-api-1.89</tag>
</scm> </scm>
<distributionManagement> <distributionManagement>
@@ -34,6 +34,12 @@
<build> <build>
<plugins> <plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<rerunFailingTestsCount>2</rerunFailingTestsCount>
</configuration>
</plugin>
<plugin> <plugin>
<groupId>org.codehaus.mojo</groupId> <groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId> <artifactId>animal-sniffer-maven-plugin</artifactId>
@@ -105,6 +111,12 @@
<version>4.11</version> <version>4.11</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
@@ -135,7 +147,13 @@
<dependency> <dependency>
<groupId>com.squareup.okhttp</groupId> <groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp-urlconnection</artifactId> <artifactId>okhttp-urlconnection</artifactId>
<version>2.0.0</version> <version>2.7.5</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp-urlconnection</artifactId>
<version>3.4.0</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>

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

@@ -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,9 +1,9 @@
package org.kohsuke.github; package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.net.URL; import java.net.URL;
import java.util.Collection; import java.util.Collection;
import java.util.Date;
import java.util.List; import java.util.List;
/** /**
@@ -36,9 +36,14 @@ public class GHAuthorization extends GHObject {
private GitHub root; private GitHub root;
private List<String> scopes; private List<String> scopes;
private String token; private String token;
private String token_last_eight;
private String hashed_token;
private App app; private App app;
private String note; private String note;
private String note_url; 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() { public GitHub getRoot() {
return root; return root;
@@ -52,6 +57,14 @@ public class GHAuthorization extends GHObject {
return token; return token;
} }
public String getTokenLastEight() {
return token_last_eight;
}
public String getHashedToken() {
return hashed_token;
}
public URL getAppUrl() { public URL getAppUrl() {
return GitHub.parseURL(app.url); return GitHub.parseURL(app.url);
} }
@@ -82,6 +95,10 @@ public class GHAuthorization extends GHObject {
return GitHub.parseURL(note_url); return GitHub.parseURL(note_url);
} }
public String getFingerprint() {
return fingerprint;
}
/*package*/ GHAuthorization wrap(GitHub root) { /*package*/ GHAuthorization wrap(GitHub root) {
this.root = root; this.root = root;
return this; return this;
@@ -92,5 +109,6 @@ public class GHAuthorization extends GHObject {
private static class App { private static class App {
private String url; private String url;
private String name; 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

@@ -0,0 +1,49 @@
package org.kohsuke.github;
import org.apache.commons.codec.binary.Base64;
import java.io.IOException;
/**
* Builder pattern for creating a new blob.
* Based on https://developer.github.com/v3/git/blobs/#create-a-blob
*/
public class GHBlobBuilder {
private final GHRepository repo;
private final Requester req;
GHBlobBuilder(GHRepository repo) {
this.repo = repo;
req = new Requester(repo.root);
}
/**
* Configures a blob with the specified text {@code content}.
*/
public GHBlobBuilder textContent(String content) {
req.with("content", content);
req.with("encoding", "utf-8");
return this;
}
/**
* Configures a blob with the specified binary {@code content}.
*/
public GHBlobBuilder binaryContent(byte[] content) {
String base64Content = Base64.encodeBase64String(content);
req.with("content", base64Content);
req.with("encoding", "base64");
return this;
}
private String getApiTail() {
return String.format("/repos/%s/%s/git/blobs", repo.getOwnerName(), repo.getName());
}
/**
* Creates a blob based on the parameters specified thus far.
*/
public GHBlob create() throws IOException {
return req.method("POST").to(getApiTail(), GHBlob.class);
}
}

View File

@@ -1,24 +1,35 @@
package org.kohsuke.github; package org.kohsuke.github;
import com.fasterxml.jackson.annotation.JsonProperty;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import static org.kohsuke.github.Previews.*;
/** /**
* A branch in a repository. * A branch in a repository.
* *
* @author Yusuke Kokubo * @author Yusuke Kokubo
*/ */
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", @SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API") "NP_UNWRITTEN_FIELD", "URF_UNREAD_FIELD"}, justification = "JSON API")
public class GHBranch { public class GHBranch {
private GitHub root; private GitHub root;
private GHRepository owner; private GHRepository owner;
private String name; private String name;
private Commit commit; private Commit commit;
@JsonProperty("protected")
private boolean protection;
private String protection_url;
public static class Commit { public static class Commit {
String sha; String sha;
@SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now")
String url; String url;
} }
@@ -38,13 +49,73 @@ public class GHBranch {
return name; 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. * The commit that this branch currently points to.
*/ */
public String getSHA1() { public String getSHA1() {
return commit.sha; 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 @Override
public String toString() { public String toString() {
final String url = owner != null ? owner.getUrl().toString() : "unknown"; final String url = owner != null ? owner.getUrl().toString() : "unknown";

View File

@@ -0,0 +1,151 @@
package org.kohsuke.github;
import com.fasterxml.jackson.annotation.JsonProperty;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Collection;
@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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
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 static org.kohsuke.github.Previews.*;
/**
* 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

@@ -8,6 +8,7 @@ import java.net.URL;
import java.util.AbstractList; import java.util.AbstractList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
/** /**
@@ -37,16 +38,30 @@ public class GHCommit {
private int comment_count; private int comment_count;
static class Tree {
String sha;
}
private Tree tree;
@WithBridgeMethods(value = GHAuthor.class, castRequired = true) @WithBridgeMethods(value = GHAuthor.class, castRequired = true)
public GitUser getAuthor() { public GitUser getAuthor() {
return author; return author;
} }
public Date getAuthoredDate() {
return GitHub.parseDate(author.date);
}
@WithBridgeMethods(value = GHAuthor.class, castRequired = true) @WithBridgeMethods(value = GHAuthor.class, castRequired = true)
public GitUser getCommitter() { public GitUser getCommitter() {
return committer; return committer;
} }
public Date getCommitDate() {
return GitHub.parseDate(committer.date);
}
/** /**
* Commit message. * Commit message.
*/ */
@@ -63,6 +78,7 @@ public class GHCommit {
* @deprecated Use {@link GitUser} instead. * @deprecated Use {@link GitUser} instead.
*/ */
public static class GHAuthor extends GitUser { public static class GHAuthor extends GitUser {
private String date;
} }
public static class Stats { public static class Stats {
@@ -179,7 +195,8 @@ public class GHCommit {
public ShortInfo getCommitShortInfo() throws IOException { public ShortInfo getCommitShortInfo() throws IOException {
populate(); if (commit==null)
populate();
return commit; return commit;
} }
@@ -214,6 +231,13 @@ public class GHCommit {
return stats.deletions; 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" * URL of this commit like "https://github.com/kohsuke/sandbox-ant/commit/8ae38db0ea5837313ab5f39d43a6f73de3bd9000"
*/ */
@@ -271,10 +295,29 @@ public class GHCommit {
return resolveUser(author); 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 { public GHUser getCommitter() throws IOException {
return resolveUser(committer); 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 { private GHUser resolveUser(User author) throws IOException {
if (author==null || author.login==null) return null; if (author==null || author.login==null) return null;
return owner.root.getUser(author.login); return owner.root.getUser(author.login);

View File

@@ -0,0 +1,92 @@
package org.kohsuke.github;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
/**
* Builder pattern for creating a new commit.
* Based on https://developer.github.com/v3/git/commits/#create-a-commit
*/
public class GHCommitBuilder {
private final GHRepository repo;
private final Requester req;
private final List<String> parents = new ArrayList<String>();
private static final class UserInfo {
private final String name;
private final String email;
private final String date;
private UserInfo(String name, String email, Date date) {
this.name = name;
this.email = email;
TimeZone tz = TimeZone.getTimeZone("UTC");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
df.setTimeZone(tz);
this.date = df.format((date != null) ? date : new Date());
}
}
GHCommitBuilder(GHRepository repo) {
this.repo = repo;
req = new Requester(repo.root);
}
/**
* @param message the commit message
*/
public GHCommitBuilder message(String message) {
req.with("message", message);
return this;
}
/**
* @param tree the SHA of the tree object this commit points to
*/
public GHCommitBuilder tree(String tree) {
req.with("tree", tree);
return this;
}
/**
* @param parent the SHA of a parent commit.
*/
public GHCommitBuilder parent(String parent) {
parents.add(parent);
return this;
}
/**
* Configures the author of this commit.
*/
public GHCommitBuilder author(String name, String email, Date date) {
req._with("author", new UserInfo(name, email, date));
return this;
}
/**
* Configures the committer of this commit.
*/
public GHCommitBuilder committer(String name, String email, Date date) {
req._with("committer", new UserInfo(name, email, date));
return this;
}
private String getApiTail() {
return String.format("/repos/%s/%s/git/commits", repo.getOwnerName(), repo.getName());
}
/**
* Creates a blob based on the parameters specified thus far.
*/
public GHCommit create() throws IOException {
req._with("parents", parents);
return req.method("POST").to(getApiTail(), GHCommit.class).wrapUp(repo);
}
}

View File

@@ -1,9 +1,11 @@
package org.kohsuke.github; package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.Date;
import static org.kohsuke.github.Previews.*;
/** /**
* A comment attached to a commit (or a specific line in a specific file of a commit.) * A comment attached to a commit (or a specific line in a specific file of a commit.)
@@ -15,23 +17,13 @@ import java.util.Date;
*/ */
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", @SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API") "NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public class GHCommitComment extends GHObject { public class GHCommitComment extends GHObject implements Reactable {
private GHRepository owner; private GHRepository owner;
String body, html_url, commit_id; String body, html_url, commit_id;
Integer line; Integer line;
String path; String path;
User user; GHUser user; // not fully populated. beware.
static class User {
// TODO: what if someone who doesn't have an account on GitHub makes a commit?
@SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now")
String url,avatar_url,gravatar_id;
@SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now")
int id;
String login;
}
public GHRepository getOwner() { public GHRepository getOwner() {
return owner; return owner;
@@ -76,7 +68,7 @@ public class GHCommitComment extends GHObject {
* Gets the user who put this comment. * Gets the user who put this comment.
*/ */
public GHUser getUser() throws IOException { public GHUser getUser() throws IOException {
return owner.root.getUser(user.login); return owner == null || owner.root.isOffline() ? user : owner.root.getUser(user.login);
} }
/** /**
@@ -96,6 +88,29 @@ public class GHCommitComment extends GHObject {
this.body = body; 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. * Deletes this comment.
*/ */
@@ -110,6 +125,9 @@ public class GHCommitComment extends GHObject {
GHCommitComment wrap(GHRepository owner) { GHCommitComment wrap(GHRepository owner) {
this.owner = owner; this.owner = owner;
if (owner.root.isOffline()) {
user.wrapUp(owner.root);
}
return this; return this;
} }
} }

View File

@@ -39,7 +39,8 @@ public class GHCommitPointer {
* This points to the user who owns * This points to the user who owns
* the {@link #getRepository()}. * the {@link #getRepository()}.
*/ */
public GHUser getUser() { public GHUser getUser() throws IOException {
if (user != null) return user.root.intern(user);
return user; return user;
} }

View File

@@ -0,0 +1,137 @@
package org.kohsuke.github;
import org.apache.commons.lang.StringUtils;
import java.io.IOException;
/**
* 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

@@ -2,7 +2,6 @@ package org.kohsuke.github;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.Date;
/** /**
* Represents a status of a commit. * Represents a status of a commit.
@@ -10,6 +9,7 @@ import java.util.Date;
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
* @see GHRepository#getLastCommitStatus(String) * @see GHRepository#getLastCommitStatus(String)
* @see GHCommit#getLastStatus() * @see GHCommit#getLastStatus()
* @see GHRepository#createCommitStatus(String, GHCommitState, String, String)
*/ */
public class GHCommitStatus extends GHObject { public class GHCommitStatus extends GHObject {
String state; String state;
@@ -46,8 +46,8 @@ public class GHCommitStatus extends GHObject {
return description; return description;
} }
public GHUser getCreator() { public GHUser getCreator() throws IOException {
return creator; return root.intern(creator);
} }
public String getContext() { public String getContext() {

View File

@@ -7,8 +7,6 @@ import org.apache.commons.io.IOUtils;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import javax.xml.bind.DatatypeConverter;
/** /**
* A Content of a repository. * A Content of a repository.
* *

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

@@ -1,9 +1,9 @@
package org.kohsuke.github; package org.kohsuke.github;
import java.io.IOException;
import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringBuilder;
import java.io.IOException;
public class GHDeployKey { public class GHDeployKey {
protected String url, key, title; protected String url, key, title;

View File

@@ -1,6 +1,6 @@
package org.kohsuke.github; package org.kohsuke.github;
import java.io.IOException;
import java.net.URL; import java.net.URL;
public class GHDeployment extends GHObject { public class GHDeployment extends GHObject {
@@ -41,8 +41,8 @@ public class GHDeployment extends GHObject {
public String getEnvironment() { public String getEnvironment() {
return environment; return environment;
} }
public GHUser getCreator() { public GHUser getCreator() throws IOException {
return creator; return root.intern(creator);
} }
public String getRef() { public String getRef() {
return ref; return ref;

View File

@@ -1,7 +1,6 @@
package org.kohsuke.github; package org.kohsuke.github;
import java.io.IOException; import java.io.IOException;
import java.util.Locale;
public class GHDeploymentStatusBuilder { public class GHDeploymentStatusBuilder {
private final Requester builder; private final Requester builder;

View File

@@ -5,10 +5,9 @@ import java.util.Locale;
/** /**
* Hook event type. * Hook event type.
* *
* See http://developer.github.com/v3/events/types/
*
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
* @see GHEventInfo * @see GHEventInfo
* @see <a href="https://developer.github.com/v3/activity/events/types/">Event type reference</a>
*/ */
public enum GHEvent { public enum GHEvent {
COMMIT_COMMENT, COMMIT_COMMENT,
@@ -22,17 +21,30 @@ public enum GHEvent {
FORK_APPLY, FORK_APPLY,
GIST, GIST,
GOLLUM, GOLLUM,
INSTALLATION,
INSTALLATION_REPOSITORIES,
ISSUE_COMMENT, ISSUE_COMMENT,
ISSUES, ISSUES,
LABEL,
MARKETPLACE_PURCHASE,
MEMBER, MEMBER,
MEMBERSHIP,
MILESTONE,
ORGANIZATION,
ORG_BLOCK,
PAGE_BUILD, PAGE_BUILD,
PROJECT_CARD,
PROJECT_COLUMN,
PROJECT,
PUBLIC, PUBLIC,
PULL_REQUEST, PULL_REQUEST,
PULL_REQUEST_REVIEW,
PULL_REQUEST_REVIEW_COMMENT, PULL_REQUEST_REVIEW_COMMENT,
PUSH, PUSH,
RELEASE, RELEASE,
REPOSITORY, // only valid for org hooks REPOSITORY, // only valid for org hooks
STATUS, STATUS,
TEAM,
TEAM_ADD, TEAM_ADD,
WATCH, WATCH,
PING, PING,

View File

@@ -1,11 +1,11 @@
package org.kohsuke.github; package org.kohsuke.github;
import java.io.IOException;
import java.util.Date;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.util.Date;
/** /**
* Represents an event. * Represents an event.
* *

View File

@@ -1,6 +1,9 @@
package org.kohsuke.github; package org.kohsuke.github;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.Reader; import java.io.Reader;
import java.util.List; import java.util.List;
@@ -14,11 +17,28 @@ import java.util.List;
public abstract class GHEventPayload { public abstract class GHEventPayload {
protected GitHub root; protected GitHub root;
private GHUser sender;
/*package*/ GHEventPayload() { /*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) { /*package*/ void wrapUp(GitHub root) {
this.root = root; this.root = root;
if (sender != null) {
sender.wrapUp(root);
}
} }
/** /**
@@ -58,7 +78,7 @@ public abstract class GHEventPayload {
throw new IllegalStateException("Expected pull_request payload, but got something else. Maybe we've got another type of event?"); throw new IllegalStateException("Expected pull_request payload, but got something else. Maybe we've got another type of event?");
if (repository!=null) { if (repository!=null) {
repository.wrap(root); repository.wrap(root);
pull_request.wrap(repository); pull_request.wrapUp(repository);
} else { } else {
pull_request.wrapUp(root); pull_request.wrapUp(root);
} }
@@ -120,17 +140,340 @@ public abstract class GHEventPayload {
} }
} }
/**
* 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. * A commit was pushed.
* *
* @see <a href="http://developer.github.com/v3/activity/events/types/#pushevent">authoritative source</a> * @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 { public static class Push extends GHEventPayload {
private String head, before; private String head, before;
private boolean created, deleted, forced;
private String ref; private String ref;
private int size; private int size;
private List<PushCommit> commits; private List<PushCommit> commits;
private GHRepository repository; private GHRepository repository;
private Pusher pusher;
/** /**
* The SHA of the HEAD commit on the repository * The SHA of the HEAD commit on the repository
@@ -147,6 +490,11 @@ public abstract class GHEventPayload {
return before; return before;
} }
@JsonSetter // alias
private void setAfter(String after) {
head = after;
}
/** /**
* The full Git ref that was pushed. Example: “refs/heads/master” * The full Git ref that was pushed. Example: “refs/heads/master”
*/ */
@@ -162,6 +510,18 @@ public abstract class GHEventPayload {
return size; return size;
} }
public boolean isCreated() {
return created;
}
public boolean isDeleted() {
return deleted;
}
public boolean isForced() {
return forced;
}
/** /**
* The list of pushed commits. * The list of pushed commits.
*/ */
@@ -173,6 +533,14 @@ public abstract class GHEventPayload {
return repository; return repository;
} }
public Pusher getPusher() {
return pusher;
}
public void setPusher(Pusher pusher) {
this.pusher = pusher;
}
@Override @Override
void wrapUp(GitHub root) { void wrapUp(GitHub root) {
super.wrapUp(root); super.wrapUp(root);
@@ -180,18 +548,44 @@ public abstract class GHEventPayload {
repository.wrap(root); 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 * Commit in a push
*/ */
public static class PushCommit { public static class PushCommit {
private GitUser author; private GitUser author;
private GitUser committer;
private String url, sha, message; private String url, sha, message;
private boolean distinct; private boolean distinct;
private List<String> added, removed, modified;
public GitUser getAuthor() { public GitUser getAuthor() {
return author; return author;
} }
public GitUser getCommitter() {
return committer;
}
/** /**
* Points to the commit API resource. * Points to the commit API resource.
*/ */
@@ -203,6 +597,11 @@ public abstract class GHEventPayload {
return sha; return sha;
} }
@JsonSetter
private void setId(String id) {
sha = id;
}
public String getMessage() { public String getMessage() {
return message; return message;
} }
@@ -213,6 +612,61 @@ public abstract class GHEventPayload {
public boolean isDistinct() { public boolean isDistinct() {
return distinct; 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

@@ -0,0 +1,34 @@
package org.kohsuke.github;
import javax.annotation.CheckForNull;
import java.io.FileNotFoundException;
import java.net.HttpURLConnection;
import java.util.List;
import java.util.Map;
/**
* Request/responce contains useful metadata.
* Custom exception allows store info for next diagnostics.
*
* @author Kanstantsin Shautsou
*/
public class GHFileNotFoundException extends FileNotFoundException {
protected Map<String, List<String>> responseHeaderFields;
public GHFileNotFoundException() {
}
public GHFileNotFoundException(String s) {
super(s);
}
@CheckForNull
public Map<String, List<String>> getResponseHeaderFields() {
return responseHeaderFields;
}
GHFileNotFoundException withResponseHeaderFields(HttpURLConnection urlConnection) {
this.responseHeaderFields = urlConnection.getHeaderFields();
return this;
}
}

View File

@@ -38,8 +38,8 @@ public class GHGist extends GHObject {
/** /**
* User that owns this Gist. * User that owns this Gist.
*/ */
public GHUser getOwner() { public GHUser getOwner() throws IOException {
return owner; return root.intern(owner);
} }
public String getForksUrl() { public String getForksUrl() {

View File

@@ -1,6 +1,7 @@
package org.kohsuke.github; package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.Collections; import java.util.Collections;
@@ -41,6 +42,13 @@ public abstract class GHHook extends GHObject {
return Collections.unmodifiableMap(config); 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. * Deletes this hook.
*/ */

View File

@@ -5,7 +5,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
/** /**

View File

@@ -0,0 +1,34 @@
package org.kohsuke.github;
import javax.annotation.CheckForNull;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.List;
import java.util.Map;
/**
* Request/responce contains useful metadata.
* Custom exception allows store info for next diagnostics.
*
* @author Kanstantsin Shautsou
*/
public class GHIOException extends IOException {
protected Map<String, List<String>> responseHeaderFields;
public GHIOException() {
}
public GHIOException(String message) {
super(message);
}
@CheckForNull
public Map<String, List<String>> getResponseHeaderFields() {
return responseHeaderFields;
}
GHIOException withResponseHeaderFields(HttpURLConnection urlConnection) {
this.responseHeaderFields = urlConnection.getHeaderFields();
return this;
}
}

View File

@@ -29,12 +29,16 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import static org.kohsuke.github.Previews.*;
/** /**
* Represents an issue on GitHub. * Represents an issue on GitHub.
* *
@@ -44,16 +48,18 @@ import java.util.Locale;
* @see GitHub#searchIssues() * @see GitHub#searchIssues()
* @see GHIssueSearchBuilder * @see GHIssueSearchBuilder
*/ */
public class GHIssue extends GHObject { public class GHIssue extends GHObject implements Reactable{
GitHub root; GitHub root;
GHRepository owner; GHRepository owner;
// API v3 // API v3
protected GHUser assignee; protected GHUser assignee; // not sure what this field is now that 'assignees' exist
protected GHUser[] assignees;
protected String state; protected String state;
protected int number; protected int number;
protected String closed_at; protected String closed_at;
protected int comments; protected int comments;
@SkipFromToString
protected String body; protected String body;
// for backward compatibility with < 1.63, this collection needs to hold instances of Label, not GHLabel // for backward compatibility with < 1.63, this collection needs to hold instances of Label, not GHLabel
protected List<Label> labels; protected List<Label> labels;
@@ -78,6 +84,7 @@ public class GHIssue extends GHObject {
/*package*/ GHIssue wrap(GitHub root) { /*package*/ GHIssue wrap(GitHub root) {
this.root = root; this.root = root;
if(assignee != null) assignee.wrapUp(root); if(assignee != null) assignee.wrapUp(root);
if(assignees!=null) GHUser.wrap(assignees,root);
if(user != null) user.wrapUp(root); if(user != null) user.wrapUp(root);
if(closed_by != null) closed_by.wrapUp(root); if(closed_by != null) closed_by.wrapUp(root);
return this; return this;
@@ -184,7 +191,7 @@ public class GHIssue extends GHObject {
} }
public void assignTo(GHUser user) throws IOException { public void assignTo(GHUser user) throws IOException {
editIssue("assignee", user.getLogin()); setAssignees(user);
} }
public void setLabels(String... labels) throws IOException { public void setLabels(String... labels) throws IOException {
@@ -216,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() { protected String getApiRoute() {
return getIssuesApiRoute(); return getIssuesApiRoute();
} }
@@ -224,15 +288,19 @@ public class GHIssue extends GHObject {
return "/repos/"+owner.getOwnerName()+"/"+owner.getName()+"/issues/"+number; return "/repos/"+owner.getOwnerName()+"/"+owner.getName()+"/issues/"+number;
} }
public GHUser getAssignee() { public GHUser getAssignee() throws IOException {
return assignee; return root.intern(assignee);
} }
public List<GHUser> getAssignees() {
return Collections.unmodifiableList(Arrays.asList(assignees));
}
/** /**
* User who submitted the issue. * User who submitted the issue.
*/ */
public GHUser getUser() { public GHUser getUser() throws IOException {
return user; return root.intern(user);
} }
/** /**
@@ -243,12 +311,16 @@ public class GHIssue extends GHObject {
* even for an issue that's already closed. See * even for an issue that's already closed. See
* https://github.com/kohsuke/github-api/issues/60. * https://github.com/kohsuke/github-api/issues/60.
*/ */
public GHUser getClosedBy() { public GHUser getClosedBy() throws IOException {
if(!"closed".equals(state)) return null; if(!"closed".equals(state)) return null;
if(closed_by != null) return closed_by;
//TODO
//TODO closed_by = owner.getIssue(number).getClosed_by(); /*
return closed_by; if (closed_by==null) {
closed_by = owner.getIssue(number).getClosed_by();
}
*/
return root.intern(closed_by);
} }
public int getCommentsCount(){ public int getCommentsCount(){

View File

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

@@ -26,16 +26,18 @@ package org.kohsuke.github;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import static org.kohsuke.github.Previews.*;
/** /**
* Comment to the issue * Comment to the issue
* *
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
*/ */
public class GHIssueComment extends GHObject { public class GHIssueComment extends GHObject implements Reactable {
GHIssue owner; GHIssue owner;
private String body, gravatar_id; private String body, gravatar_id;
private GHUser user; private GHUser user; // not fully populated. beware.
/*package*/ GHIssueComment wrapUp(GHIssue owner) { /*package*/ GHIssueComment wrapUp(GHIssue owner) {
this.owner = owner; this.owner = owner;
@@ -68,7 +70,7 @@ public class GHIssueComment extends GHObject {
* Gets the user who posted this comment. * Gets the user who posted this comment.
*/ */
public GHUser getUser() throws IOException { public GHUser getUser() throws IOException {
return owner.root.getUser(user.getLogin()); return owner == null || owner.root.isOffline() ? user : owner.root.getUser(user.getLogin());
} }
/** /**
@@ -93,7 +95,30 @@ public class GHIssueComment extends GHObject {
public void delete() throws IOException { public void delete() throws IOException {
new Requester(owner.root).method("DELETE").to(getApiRoute()); 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() { private String getApiRoute() {
return "/repos/"+owner.getRepository().getOwnerName()+"/"+owner.getRepository().getName()+"/issues/comments/" + id; return "/repos/"+owner.getRepository().getOwnerName()+"/"+owner.getRepository().getName()+"/issues/comments/" + id;
} }

View File

@@ -1,7 +1,5 @@
package org.kohsuke.github; package org.kohsuke.github;
import java.util.Locale;
/** /**
* Search issues. * Search issues.
* *
@@ -41,6 +39,11 @@ public class GHIssueSearchBuilder extends GHSearchBuilder<GHIssue> {
return q("is:merged"); return q("is:merged");
} }
public GHIssueSearchBuilder order(GHDirection v) {
req.with("order",v);
return this;
}
public GHIssueSearchBuilder sort(Sort sort) { public GHIssueSearchBuilder sort(Sort sort) {
req.with("sort",sort); req.with("sort",sort);
return this; return this;

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.*;
/**
* 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

@@ -27,8 +27,8 @@ public class GHMilestone extends GHObject {
return owner; return owner;
} }
public GHUser getCreator() { public GHUser getCreator() throws IOException {
return creator; return root.intern(creator);
} }
public Date getDueOn() { public Date getDueOn() {
@@ -72,12 +72,19 @@ public class GHMilestone extends GHObject {
} }
/** /**
* Closes this issue. * Closes this milestone.
*/ */
public void close() throws IOException { public void close() throws IOException {
edit("state", "closed"); edit("state", "closed");
} }
/**
* Reopens this milestone.
*/
public void reopen() throws IOException {
edit("state", "open");
}
private void edit(String key, Object value) throws IOException { private void edit(String key, Object value) throws IOException {
new Requester(root)._with(key, value).method("PATCH").to(getApiRoute()); new Requester(root)._with(key, value).method("PATCH").to(getApiRoute());
} }

View File

@@ -6,7 +6,6 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
@@ -178,6 +177,39 @@ public class GHMyself extends GHUser {
return listRepositories(); 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 { // public void addEmails(Collection<String> emails) throws IOException {
//// new Requester(root,ApiVersion.V3).withCredential().to("/user/emails"); //// new Requester(root,ApiVersion.V3).withCredential().to("/user/emails");
// root.retrieveWithAuth3() // root.retrieveWithAuth3()

View File

@@ -2,10 +2,16 @@ package org.kohsuke.github;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import javax.annotation.CheckForNull;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL; import java.net.URL;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Map;
/** /**
* Most (all?) domain objects in GitHub seems to have these 4 properties. * Most (all?) domain objects in GitHub seems to have these 4 properties.
@@ -13,6 +19,11 @@ import java.util.Date;
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", @SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API") "NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public abstract class GHObject { public abstract class GHObject {
/**
* Capture response HTTP headers on the state object.
*/
protected Map<String, List<String>> responseHeaderFields;
protected String url; protected String url;
protected int id; protected int id;
protected String created_at; protected String created_at;
@@ -21,6 +32,21 @@ public abstract class GHObject {
/*package*/ GHObject() { /*package*/ GHObject() {
} }
/**
* Returns the HTTP response headers given along with the state of this object.
*
* <p>
* Some of the HTTP headers have nothing to do with the object, for example "Cache-Control"
* and others are different depending on how this object was retrieved.
*
* This method was added as a kind of hack to allow the caller to retrieve OAuth scopes and such.
* Use with caution. The method might be removed in the future.
*/
@CheckForNull @Deprecated
public Map<String, List<String>> getResponseHeaderFields() {
return responseHeaderFields;
}
/** /**
* When was this resource created? * When was this resource created?
*/ */
@@ -46,7 +72,7 @@ public abstract class GHObject {
* URL of this object for humans, which renders some HTML. * URL of this object for humans, which renders some HTML.
*/ */
@WithBridgeMethods(value=String.class, adapterMethod="urlToString") @WithBridgeMethods(value=String.class, adapterMethod="urlToString")
public abstract URL getHtmlUrl(); public abstract URL getHtmlUrl() throws IOException;
/** /**
* When was this resource last updated? * When was this resource last updated?
@@ -72,4 +98,39 @@ public abstract class GHObject {
private Object urlToString(URL url, Class type) { private Object urlToString(URL url, Class type) {
return url==null ? null : url.toString(); 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

@@ -93,6 +93,17 @@ public class GHOrganization extends GHPerson {
return null; 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. * Checks if this organization has the specified user as a member.
*/ */
@@ -209,7 +220,7 @@ public class GHOrganization extends GHPerson {
*/ */
public List<GHRepository> getRepositoriesWithOpenPullRequests() throws IOException { public List<GHRepository> getRepositoriesWithOpenPullRequests() throws IOException {
List<GHRepository> r = new ArrayList<GHRepository>(); List<GHRepository> r = new ArrayList<GHRepository>();
for (GHRepository repository : listRepositories()) { for (GHRepository repository : listRepositories(100)) {
repository.wrap(root); repository.wrap(root);
List<GHPullRequest> pullRequests = repository.getPullRequests(GHIssueState.OPEN); List<GHPullRequest> pullRequests = repository.getPullRequests(GHIssueState.OPEN);
if (pullRequests.size() > 0) { if (pullRequests.size() > 0) {

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

@@ -38,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. * Depending on the original API call where this object is created, it may not contain everything.
*/ */
protected synchronized void populate() throws IOException { 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); root.retrieve().to(url, this);
} }
@@ -52,7 +56,7 @@ public abstract class GHPerson extends GHObject {
*/ */
public synchronized Map<String,GHRepository> getRepositories() throws IOException { public synchronized Map<String,GHRepository> getRepositories() throws IOException {
Map<String,GHRepository> repositories = new TreeMap<String, GHRepository>(); Map<String,GHRepository> repositories = new TreeMap<String, GHRepository>();
for (GHRepository r : listRepositories()) { for (GHRepository r : listRepositories(100)) {
repositories.put(r.getName(),r); repositories.put(r.getName(),r);
} }
return Collections.unmodifiableMap(repositories); return Collections.unmodifiableMap(repositories);
@@ -89,10 +93,10 @@ public abstract class GHPerson extends GHObject {
} }
/** /**
* Loads repository list in a pagenated fashion. * Loads repository list in a paginated fashion.
* *
* <p> * <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. * 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 * Every {@link Iterator#next()} call results in I/O. Exceptions that occur during the processing is wrapped
@@ -204,7 +208,7 @@ public abstract class GHPerson extends GHObject {
public Date getUpdatedAt() throws IOException { public Date getUpdatedAt() throws IOException {
populate(); populate();
return super.getCreatedAt(); return super.getUpdatedAt();
} }
/** /**

View File

@@ -23,10 +23,16 @@
*/ */
package org.kohsuke.github; package org.kohsuke.github;
import javax.annotation.CheckForNull;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List;
import static org.kohsuke.github.Previews.*;
/** /**
* A pull request. * A pull request.
@@ -200,18 +206,20 @@ public class GHPullRequest extends GHIssue {
* Depending on the original API call where this object is created, it may not contain everything. * Depending on the original API call where this object is created, it may not contain everything.
*/ */
private void populate() throws IOException { private void populate() throws IOException {
if (merged_by!=null) return; // already populated if (mergeable_state!=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); root.retrieve().to(url, this).wrapUp(owner);
} }
/** /**
* Retrieves all the commits associated to this pull request. * Retrieves all the files associated to this pull request.
*/ */
public PagedIterable<GHPullRequestFileDetail> listFiles() { public PagedIterable<GHPullRequestFileDetail> listFiles() {
return new PagedIterable<GHPullRequestFileDetail>() { return new PagedIterable<GHPullRequestFileDetail>() {
public PagedIterator<GHPullRequestFileDetail> _iterator(int pageSize) { public PagedIterator<GHPullRequestFileDetail> _iterator(int pageSize) {
return new PagedIterator<GHPullRequestFileDetail>(root.retrieve().asIterator(String.format("%s/files", getApiURL()), return new PagedIterator<GHPullRequestFileDetail>(root.retrieve().asIterator(String.format("%s/files", getApiRoute()),
GHPullRequestFileDetail[].class, pageSize)) { GHPullRequestFileDetail[].class, pageSize)) {
@Override @Override
protected void wrapUp(GHPullRequestFileDetail[] page) { protected void wrapUp(GHPullRequestFileDetail[] page) {
@@ -221,6 +229,27 @@ public class GHPullRequest extends GHIssue {
}; };
} }
/**
* 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. * Obtains all the review comments associated with this pull request.
*/ */
@@ -245,7 +274,7 @@ public class GHPullRequest extends GHIssue {
return new PagedIterable<GHPullRequestCommitDetail>() { return new PagedIterable<GHPullRequestCommitDetail>() {
public PagedIterator<GHPullRequestCommitDetail> _iterator(int pageSize) { public PagedIterator<GHPullRequestCommitDetail> _iterator(int pageSize) {
return new PagedIterator<GHPullRequestCommitDetail>(root.retrieve().asIterator( return new PagedIterator<GHPullRequestCommitDetail>(root.retrieve().asIterator(
String.format("%s/commits", getApiURL()), String.format("%s/commits", getApiRoute()),
GHPullRequestCommitDetail[].class, pageSize)) { GHPullRequestCommitDetail[].class, pageSize)) {
@Override @Override
protected void wrapUp(GHPullRequestCommitDetail[] page) { protected void wrapUp(GHPullRequestCommitDetail[] page) {
@@ -257,6 +286,34 @@ public class GHPullRequest extends GHIssue {
}; };
} }
@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 { public GHPullRequestReviewComment createReviewComment(String body, String sha, String path, int position) throws IOException {
return new Requester(root).method("POST") return new Requester(root).method("POST")
.with("body", body) .with("body", body)
@@ -289,13 +346,57 @@ public class GHPullRequest extends GHIssue {
* SHA that pull request head must match to allow merge. * SHA that pull request head must match to allow merge.
*/ */
public void merge(String msg, String sha) throws IOException { public void merge(String msg, String sha) throws IOException {
new Requester(root).method("PUT").with("commit_message",msg).with("sha",sha).to(getApiRoute()+"/merge"); merge(msg, sha, null);
} }
/**
* Merge this pull request, using the specified merge method.
*
* The equivalent of the big green "Merge pull request" button.
*
* @param msg
* Commit message. If null, the default one will be used.
* @param method
* SHA that pull request head must match to allow merge.
*/
public void merge(String msg, String sha, MergeMethod method) throws IOException {
new Requester(root).method("PUT")
.with("commit_message",msg)
.with("sha",sha)
.with("merge_method",method)
.to(getApiRoute()+"/merge");
}
public enum MergeMethod{ MERGE, SQUASH, REBASE }
private void fetchIssue() throws IOException { private void fetchIssue() throws IOException {
if (!fetchedIssueDetails) { if (!fetchedIssueDetails) {
new Requester(root).to(getIssuesApiRoute(), this); new Requester(root).to(getIssuesApiRoute(), this);
fetchedIssueDetails = true; 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

@@ -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.*;
/**
* 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

@@ -26,6 +26,8 @@ package org.kohsuke.github;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import static org.kohsuke.github.Previews.*;
/** /**
* Review comment to the pull request * Review comment to the pull request
* *
@@ -33,7 +35,7 @@ import java.net.URL;
* @see GHPullRequest#listReviewComments() * @see GHPullRequest#listReviewComments()
* @see GHPullRequest#createReviewComment(String, String, String, int) * @see GHPullRequest#createReviewComment(String, String, String, int)
*/ */
public class GHPullRequestReviewComment extends GHObject { public class GHPullRequestReviewComment extends GHObject implements Reactable {
GHPullRequest owner; GHPullRequest owner;
private String body; private String body;
@@ -42,6 +44,14 @@ public class GHPullRequestReviewComment extends GHObject {
private int position; private int position;
private int originalPosition; 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) { /*package*/ GHPullRequestReviewComment wrapUp(GHPullRequest owner) {
this.owner = owner; this.owner = owner;
return this; return this;
@@ -103,4 +113,27 @@ public class GHPullRequestReviewComment extends GHObject {
public void delete() throws IOException { public void delete() throws IOException {
new Requester(owner.root).method("DELETE").to(getApiRoute()); 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

@@ -1,6 +1,7 @@
package org.kohsuke.github; package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Date; import java.util.Date;
/** /**

View File

@@ -0,0 +1,55 @@
package org.kohsuke.github;
import java.io.IOException;
import java.net.URL;
import static org.kohsuke.github.Previews.*;
/**
* 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,6 +1,7 @@
package org.kohsuke.github; package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;

View File

@@ -8,7 +8,7 @@ import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import static java.lang.String.format; import static java.lang.String.*;
/** /**
* Release in a github repository. * Release in a github repository.
@@ -45,10 +45,12 @@ public class GHRelease extends GHObject {
return draft; return draft;
} }
/**
* @deprecated
* Use {@link #update()}
*/
public GHRelease setDraft(boolean draft) throws IOException { public GHRelease setDraft(boolean draft) throws IOException {
edit("draft", draft); return update().draft(draft).update();
this.draft = draft;
return this;
} }
public URL getHtmlUrl() { public URL getHtmlUrl() {
@@ -121,9 +123,7 @@ public class GHRelease extends GHObject {
* Java 7 or greater. Options for fixing this for earlier JVMs can be found here * 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 * http://stackoverflow.com/questions/12361090/server-name-indication-sni-on-java but involve more complicated
* handling of the HTTP requests to github's API. * handling of the HTTP requests to github's API.
* */
* @throws IOException
*/
public GHAsset uploadAsset(File file, String contentType) throws IOException { public GHAsset uploadAsset(File file, String contentType) throws IOException {
Requester builder = new Requester(owner.root); Requester builder = new Requester(owner.root);
@@ -151,10 +151,10 @@ public class GHRelease extends GHObject {
} }
/** /**
* Edit this release. * Updates this release via a builder.
*/ */
private void edit(String key, Object value) throws IOException { public GHReleaseUpdater update() {
new Requester(root)._with(key, value).method("PATCH").to(owner.getApiTailUrl("releases/"+id)); return new GHReleaseUpdater(this);
} }
private String getApiTailUrl(String end) { private String getApiTailUrl(String end) {

View File

@@ -21,9 +21,7 @@ public class GHReleaseBuilder {
* @param body The release notes body. * @param body The release notes body.
*/ */
public GHReleaseBuilder body(String body) { public GHReleaseBuilder body(String body) {
if (body != null) { builder.with("body", body);
builder.with("body", body);
}
return this; return this;
} }
@@ -33,12 +31,9 @@ public class GHReleaseBuilder {
* *
* @param commitish Defaults to the repositorys default branch (usually "master"). Unused if the Git tag * @param commitish Defaults to the repositorys default branch (usually "master"). Unused if the Git tag
* already exists. * already exists.
* @return
*/ */
public GHReleaseBuilder commitish(String commitish) { public GHReleaseBuilder commitish(String commitish) {
if (commitish != null) { builder.with("target_commitish", commitish);
builder.with("target_commitish", commitish);
}
return this; return this;
} }
@@ -57,9 +52,7 @@ public class GHReleaseBuilder {
* @param name the name of the release * @param name the name of the release
*/ */
public GHReleaseBuilder name(String name) { public GHReleaseBuilder name(String name) {
if (name != null) { builder.with("name", name);
builder.with("name", name);
}
return this; return this;
} }

View File

@@ -0,0 +1,81 @@
package org.kohsuke.github;
import java.io.IOException;
/**
* Modifies {@link GHRelease}.
*
* @author Kohsuke Kawaguchi
* @see GHRelease#update()
*/
public class GHReleaseUpdater {
private final GHRelease base;
private final Requester builder;
GHReleaseUpdater(GHRelease base) {
this.base = base;
this.builder = new Requester(base.root);
}
public GHReleaseUpdater tag(String tag) {
builder.with("tag_name",tag);
return this;
}
/**
* @param body The release notes body.
*/
public GHReleaseUpdater body(String body) {
builder.with("body", body);
return this;
}
/**
* Specifies the commitish value that determines where the Git tag is created from. Can be any branch or
* commit SHA.
*
* @param commitish Defaults to the repositorys default branch (usually "master"). Unused if the Git tag
* already exists.
*/
public GHReleaseUpdater commitish(String commitish) {
builder.with("target_commitish", commitish);
return this;
}
/**
* Optional.
*
* @param draft {@code true} to create a draft (unpublished) release, {@code false} to create a published one.
* Default is {@code false}.
*/
public GHReleaseUpdater draft(boolean draft) {
builder.with("draft", draft);
return this;
}
/**
* @param name the name of the release
*/
public GHReleaseUpdater name(String name) {
builder.with("name", name);
return this;
}
/**
* Optional
*
* @param prerelease {@code true} to identify the release as a prerelease. {@code false} to identify the release
* as a full release. Default is {@code false}.
*/
public GHReleaseUpdater prerelease(boolean prerelease) {
builder.with("prerelease", prerelease);
return this;
}
public GHRelease update() throws IOException {
return builder
.method("PATCH")
.to(base.owner.getApiTailUrl("releases/"+base.id), GHRelease.class).wrap(base.owner);
}
}

View File

@@ -31,6 +31,7 @@ import org.apache.commons.lang.StringUtils;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.io.Reader; import java.io.Reader;
@@ -50,7 +51,8 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import static java.util.Arrays.asList; import static java.util.Arrays.*;
import static org.kohsuke.github.Previews.*;
/** /**
* A repository on GitHub. * A repository on GitHub.
@@ -58,25 +60,33 @@ import static java.util.Arrays.asList;
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
*/ */
@SuppressWarnings({"UnusedDeclaration"}) @SuppressWarnings({"UnusedDeclaration"})
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", @SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API") "NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public class GHRepository extends GHObject { public class GHRepository extends GHObject {
/*package almost final*/ GitHub root; /*package almost final*/ GitHub root;
private String description, homepage, name, full_name; private String description, homepage, name, full_name;
private String html_url; // this is the UI private String html_url; // this is the UI
/*
* The license information makes use of the preview API.
*
* See: https://developer.github.com/v3/licenses/
*/
private GHLicense license;
private String git_url, ssh_url, clone_url, svn_url, mirror_url; private String git_url, ssh_url, clone_url, svn_url, mirror_url;
private GHUser owner; // not fully populated. beware. private GHUser owner; // not fully populated. beware.
private boolean has_issues, has_wiki, fork, has_downloads; private boolean has_issues, has_wiki, fork, has_downloads, has_pages;
@JsonProperty("private") @JsonProperty("private")
private boolean _private; private boolean _private;
private int watchers,forks,open_issues,size,network_count,subscribers_count; private int forks_count, stargazers_count, watchers_count, size, open_issues_count, subscribers_count;
private String pushed_at; private String pushed_at;
private Map<Integer,GHMilestone> milestones = new HashMap<Integer, GHMilestone>(); private Map<Integer,GHMilestone> milestones = new HashMap<Integer, GHMilestone>();
private String default_branch,language; private String default_branch,language;
private Map<String,GHCommit> commits = new HashMap<String, GHCommit>(); private Map<String,GHCommit> commits = new HashMap<String, GHCommit>();
@SkipFromToString
private GHRepoPermission permissions; private GHRepoPermission permissions;
private GHRepository source, parent; private GHRepository source, parent;
@@ -223,7 +233,7 @@ public class GHRepository extends GHObject {
} }
public GHUser getOwner() throws IOException { public GHUser getOwner() throws IOException {
return root.getUser(owner.login); // because 'owner' isn't fully populated return root.isOffline() ? owner : root.getUser(getOwnerName()); // because 'owner' isn't fully populated
} }
public GHIssue getIssue(int id) throws IOException { public GHIssue getIssue(int id) throws IOException {
@@ -288,6 +298,14 @@ public class GHRepository extends GHObject {
public List<GHRelease> getReleases() throws IOException { public List<GHRelease> getReleases() throws IOException {
return listReleases().asList(); return listReleases().asList();
} }
public GHRelease getLatestRelease() throws IOException {
try {
return root.retrieve().to(getApiTailUrl("releases/latest"), GHRelease.class).wrap(this);
} catch (FileNotFoundException e) {
return null; // no latest release
}
}
public PagedIterable<GHRelease> listReleases() throws IOException { public PagedIterable<GHRelease> listReleases() throws IOException {
return new PagedIterable<GHRelease>() { return new PagedIterable<GHRelease>() {
@@ -330,7 +348,11 @@ public class GHRepository extends GHObject {
} }
public String getOwnerName() { public String getOwnerName() {
return owner.login; // consistency of the GitHub API is super... some serialized forms of GHRepository populate
// a full GHUser while others populate only the owner and email. This later form is super helpful
// in putting the login in owner.name not owner.login... thankfully we can easily identify this
// second set because owner.login will be null
return owner.login != null ? owner.login : owner.name;
} }
public boolean hasIssues() { public boolean hasIssues() {
@@ -350,7 +372,11 @@ public class GHRepository extends GHObject {
* This not only counts direct forks, but also forks of forks, and so on. * This not only counts direct forks, but also forks of forks, and so on.
*/ */
public int getForks() { public int getForks() {
return forks; return forks_count;
}
public int getStargazersCount() {
return stargazers_count;
} }
public boolean isPrivate() { public boolean isPrivate() {
@@ -361,16 +387,25 @@ public class GHRepository extends GHObject {
return has_downloads; return has_downloads;
} }
public boolean hasPages() {
return has_pages;
}
public int getWatchers() { public int getWatchers() {
return watchers; return watchers_count;
} }
public int getOpenIssueCount() { public int getOpenIssueCount() {
return open_issues; return open_issues_count;
} }
/**
* @deprecated
* This no longer exists in the official API documentation.
* Use {@link #getForks()}
*/
public int getNetworkCount() { public int getNetworkCount() {
return network_count; return forks_count;
} }
public int getSubscribersCount() { public int getSubscribersCount() {
@@ -407,8 +442,8 @@ public class GHRepository extends GHObject {
public int getSize() { public int getSize() {
return size; return size;
} }
/** /**
* Gets the collaborators on this repository. * Gets the collaborators on this repository.
* This set always appear to include the owner. * This set always appear to include the owner.
@@ -422,25 +457,24 @@ public class GHRepository extends GHObject {
* Lists up the collaborators on this repository. * Lists up the collaborators on this repository.
* *
* @return Users * @return Users
* @throws IOException
*/ */
public PagedIterable<GHUser> listCollaborators() throws IOException { public PagedIterable<GHUser> listCollaborators() throws IOException {
return new PagedIterable<GHUser>() { return listUsers("collaborators");
public PagedIterator<GHUser> _iterator(int pageSize) { }
return new PagedIterator<GHUser>(root.retrieve().asIterator(getApiTailUrl("collaborators"), GHUser[].class, pageSize)) { /**
* Lists all <a href="https://help.github.com/articles/assigning-issues-and-pull-requests-to-other-github-users/">the available assignees</a>
@Override * to which issues may be assigned.
protected void wrapUp(GHUser[] users) { */
for (GHUser user : users) { public PagedIterable<GHUser> listAssignees() throws IOException {
user.wrapUp(root); return listUsers("assignees");
} }
}
};
}
};
/**
* Checks if the given user is an assignee for this repository.
*/
public boolean hasAssignee(GHUser u) throws IOException {
return root.retrieve().asHttpStatusCode(getApiTailUrl("assignees/" + u.getLogin()))/100==2;
} }
/** /**
@@ -454,11 +488,32 @@ public class GHRepository extends GHObject {
return r; return r;
} }
/**
* Obtain permission for a given user in this repository.
* @param user a {@link GHUser#getLogin}
* @throws FileNotFoundException under some conditions (e.g., private repo you can see but are not an admin of); treat as unknown
* @throws HttpException with a 403 under other conditions (e.g., public repo you have no special rights to); treat as unknown
*/
public GHPermissionType getPermission(String user) throws IOException {
GHPermission perm = root.retrieve().to(getApiTailUrl("collaborators/" + user + "/permission"), GHPermission.class);
perm.wrapUp(root);
return perm.getPermissionType();
}
/**
* Obtain permission for a given user in this repository.
* @throws FileNotFoundException under some conditions (e.g., private repo you can see but are not an admin of); treat as unknown
* @throws HttpException with a 403 under other conditions (e.g., public repo you have no special rights to); treat as unknown
*/
public GHPermissionType getPermission(GHUser u) throws IOException {
return getPermission(u.getLogin());
}
/** /**
* If this repository belongs to an organization, return a set of teams. * If this repository belongs to an organization, return a set of teams.
*/ */
public Set<GHTeam> getTeams() throws IOException { public Set<GHTeam> getTeams() throws IOException {
return Collections.unmodifiableSet(new HashSet<GHTeam>(Arrays.asList(GHTeam.wrapUp(root.retrieve().to(getApiTailUrl("teams"), GHTeam[].class), root.getOrganization(owner.login))))); return Collections.unmodifiableSet(new HashSet<GHTeam>(Arrays.asList(GHTeam.wrapUp(root.retrieve().to(getApiTailUrl("teams"), GHTeam[].class), root.getOrganization(getOwnerName())))));
} }
public void addCollaborators(GHUser... users) throws IOException { public void addCollaborators(GHUser... users) throws IOException {
@@ -478,7 +533,6 @@ public class GHRepository extends GHObject {
} }
private void modifyCollaborators(Collection<GHUser> users, String method) throws IOException { private void modifyCollaborators(Collection<GHUser> users, String method) throws IOException {
verifyMine();
for (GHUser user : users) { for (GHUser user : users) {
new Requester(root).method(method).to(getApiTailUrl("collaborators/" + user.getLogin())); new Requester(root).method(method).to(getApiTailUrl("collaborators/" + user.getLogin()));
} }
@@ -542,7 +596,7 @@ public class GHRepository extends GHObject {
try { try {
new Requester(root).method("DELETE").to(getApiTailUrl("")); new Requester(root).method("DELETE").to(getApiTailUrl(""));
} catch (FileNotFoundException x) { } catch (FileNotFoundException x) {
throw (FileNotFoundException) new FileNotFoundException("Failed to delete " + owner.login + "/" + name + "; might not exist, or you might need the delete_repo scope in your token: http://stackoverflow.com/a/19327004/12916").initCause(x); throw (FileNotFoundException) new FileNotFoundException("Failed to delete " + getOwnerName() + "/" + name + "; might not exist, or you might need the delete_repo scope in your token: http://stackoverflow.com/a/19327004/12916").initCause(x);
} }
} }
@@ -586,7 +640,19 @@ public class GHRepository extends GHObject {
* Newly forked repository that belong to you. * Newly forked repository that belong to you.
*/ */
public GHRepository fork() throws IOException { public GHRepository fork() throws IOException {
return new Requester(root).method("POST").to(getApiTailUrl("forks"), GHRepository.class).wrap(root); new Requester(root).method("POST").to(getApiTailUrl("forks"), null);
// this API is asynchronous. we need to wait for a bit
for (int i=0; i<10; i++) {
GHRepository r = root.getMyself().getRepository(name);
if (r!=null) return r;
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw (IOException)new InterruptedIOException().initCause(e);
}
}
throw new IOException(this+" was forked but can't find the new repository");
} }
/** /**
@@ -721,7 +787,27 @@ public class GHRepository extends GHObject {
* @throws IOException on failure communicating with GitHub * @throws IOException on failure communicating with GitHub
*/ */
public GHRef[] getRefs() throws IOException { public GHRef[] getRefs() throws IOException {
return GHRef.wrap(root.retrieve().to(String.format("/repos/%s/%s/git/refs", owner.login, name), GHRef[].class), root); return GHRef.wrap(root.retrieve().to(String.format("/repos/%s/%s/git/refs", getOwnerName(), name), GHRef[].class), root);
}
/**
* Retrieves all refs for the github repository.
*
* @return paged iterable of all refs
* @throws IOException on failure communicating with GitHub, potentially due to an invalid ref type being requested
*/
public PagedIterable<GHRef> listRefs() throws IOException {
final String url = String.format("/repos/%s/%s/git/refs", getOwnerName(), name);
return new PagedIterable<GHRef>() {
public PagedIterator<GHRef> _iterator(int pageSize) {
return new PagedIterator<GHRef>(root.retrieve().asIterator(url, GHRef[].class, pageSize)) {
protected void wrapUp(GHRef[] page) {
// no-op
}
};
}
};
} }
/** /**
@@ -731,8 +817,29 @@ public class GHRepository extends GHObject {
* @throws IOException on failure communicating with GitHub, potentially due to an invalid ref type being requested * @throws IOException on failure communicating with GitHub, potentially due to an invalid ref type being requested
*/ */
public GHRef[] getRefs(String refType) throws IOException { public GHRef[] getRefs(String refType) throws IOException {
return GHRef.wrap(root.retrieve().to(String.format("/repos/%s/%s/git/refs/%s", owner.login, name, refType), GHRef[].class),root); return GHRef.wrap(root.retrieve().to(String.format("/repos/%s/%s/git/refs/%s", getOwnerName(), name, refType), GHRef[].class),root);
} }
/**
* Retrieves all refs of the given type for the current GitHub repository.
*
* @param refType the type of reg to search for e.g. <tt>tags</tt> or <tt>commits</tt>
* @return paged iterable of all refs of the specified type
* @throws IOException on failure communicating with GitHub, potentially due to an invalid ref type being requested
*/
public PagedIterable<GHRef> listRefs(String refType) throws IOException {
final String url = String.format("/repos/%s/%s/git/refs/%s", getOwnerName(), name, refType);
return new PagedIterable<GHRef>() {
public PagedIterator<GHRef> _iterator(int pageSize) {
return new PagedIterator<GHRef>(root.retrieve().asIterator(url, GHRef[].class, pageSize)) {
protected void wrapUp(GHRef[] page) {
// no-op
}
};
}
};
}
/** /**
* Retrive a ref of the given type for the current GitHub repository. * Retrive a ref of the given type for the current GitHub repository.
* *
@@ -744,8 +851,24 @@ public class GHRepository extends GHObject {
* invalid ref type being requested * invalid ref type being requested
*/ */
public GHRef getRef(String refName) throws IOException { public GHRef getRef(String refName) throws IOException {
return root.retrieve().to(String.format("/repos/%s/%s/git/refs/%s", owner.login, name, refName), GHRef.class).wrap(root); // hashes in branch names must be replaced with the url encoded equivalent or this call will fail
// FIXME: how about other URL unsafe characters, like space, @, : etc? do we need to be using URLEncoder.encode()?
// OTOH, '/' need no escaping
refName = refName.replaceAll("#", "%23");
return root.retrieve().to(String.format("/repos/%s/%s/git/refs/%s", getOwnerName(), name, refName), GHRef.class).wrap(root);
} }
/**
* Returns the <strong>annotated</strong> tag object. Only valid if the {@link GHRef#getObject()} has a
* {@link GHRef.GHObject#getType()} of {@code tag}.
*
* @param sha the sha of the tag object
* @return the annotated tag object
*/
public GHTagObject getTagObject(String sha) throws IOException {
return root.retrieve().to(getApiTailUrl("git/tags/" + sha), GHTagObject.class).wrap(this);
}
/** /**
* Retrive a tree of the given type for the current GitHub repository. * Retrive a tree of the given type for the current GitHub repository.
* *
@@ -756,8 +879,12 @@ public class GHRepository extends GHObject {
* invalid tree type being requested * invalid tree type being requested
*/ */
public GHTree getTree(String sha) throws IOException { public GHTree getTree(String sha) throws IOException {
String url = String.format("/repos/%s/%s/git/trees/%s", owner.login, name, sha); String url = String.format("/repos/%s/%s/git/trees/%s", getOwnerName(), name, sha);
return root.retrieve().to(url, GHTree.class).wrap(root); return root.retrieve().to(url, GHTree.class).wrap(this);
}
public GHTreeBuilder createTree() {
return new GHTreeBuilder(this);
} }
/** /**
@@ -771,8 +898,37 @@ public class GHRepository extends GHObject {
* invalid tree type being requested * invalid tree type being requested
*/ */
public GHTree getTreeRecursive(String sha, int recursive) throws IOException { public GHTree getTreeRecursive(String sha, int recursive) throws IOException {
String url = String.format("/repos/%s/%s/git/trees/%s?recursive=%d", owner.login, name, sha, recursive); String url = String.format("/repos/%s/%s/git/trees/%s?recursive=%d", getOwnerName(), name, sha, recursive);
return root.retrieve().to(url, GHTree.class).wrap(root); return root.retrieve().to(url, GHTree.class).wrap(this);
}
/**
* Obtains the metadata &amp; the content of a blob.
*
* <p>
* This method retrieves the whole content in memory, so beware when you are dealing with large BLOB.
*
* @see <a href="https://developer.github.com/v3/git/blobs/#get-a-blob">Get a blob</a>
* @see #readBlob(String)
*/
public GHBlob getBlob(String blobSha) throws IOException {
String target = getApiTailUrl("git/blobs/" + blobSha);
return root.retrieve().to(target, GHBlob.class);
}
public GHBlobBuilder createBlob() {
return new GHBlobBuilder(this);
}
/**
* Reads the content of a blob as a stream for better efficiency.
*
* @see <a href="https://developer.github.com/v3/git/blobs/#get-a-blob">Get a blob</a>
* @see #getBlob(String)
*/
public InputStream readBlob(String blobSha) throws IOException {
String target = getApiTailUrl("git/blobs/" + blobSha);
return root.retrieve().withHeader("Accept","application/vnd.github.VERSION.raw").asStream(target);
} }
/** /**
@@ -781,19 +937,23 @@ public class GHRepository extends GHObject {
public GHCommit getCommit(String sha1) throws IOException { public GHCommit getCommit(String sha1) throws IOException {
GHCommit c = commits.get(sha1); GHCommit c = commits.get(sha1);
if (c==null) { if (c==null) {
c = root.retrieve().to(String.format("/repos/%s/%s/commits/%s", owner.login, name, sha1), GHCommit.class).wrapUp(this); c = root.retrieve().to(String.format("/repos/%s/%s/commits/%s", getOwnerName(), name, sha1), GHCommit.class).wrapUp(this);
commits.put(sha1,c); commits.put(sha1,c);
} }
return c; return c;
} }
public GHCommitBuilder createCommit() {
return new GHCommitBuilder(this);
}
/** /**
* Lists all the commits. * Lists all the commits.
*/ */
public PagedIterable<GHCommit> listCommits() { public PagedIterable<GHCommit> listCommits() {
return new PagedIterable<GHCommit>() { return new PagedIterable<GHCommit>() {
public PagedIterator<GHCommit> _iterator(int pageSize) { public PagedIterator<GHCommit> _iterator(int pageSize) {
return new PagedIterator<GHCommit>(root.retrieve().asIterator(String.format("/repos/%s/%s/commits", owner.login, name), GHCommit[].class, pageSize)) { return new PagedIterator<GHCommit>(root.retrieve().asIterator(String.format("/repos/%s/%s/commits", getOwnerName(), name), GHCommit[].class, pageSize)) {
protected void wrapUp(GHCommit[] page) { protected void wrapUp(GHCommit[] page) {
for (GHCommit c : page) for (GHCommit c : page)
c.wrapUp(GHRepository.this); c.wrapUp(GHRepository.this);
@@ -816,7 +976,7 @@ public class GHRepository extends GHObject {
public PagedIterable<GHCommitComment> listCommitComments() { public PagedIterable<GHCommitComment> listCommitComments() {
return new PagedIterable<GHCommitComment>() { return new PagedIterable<GHCommitComment>() {
public PagedIterator<GHCommitComment> _iterator(int pageSize) { public PagedIterator<GHCommitComment> _iterator(int pageSize) {
return new PagedIterator<GHCommitComment>(root.retrieve().asIterator(String.format("/repos/%s/%s/comments", owner.login, name), GHCommitComment[].class, pageSize)) { return new PagedIterator<GHCommitComment>(root.retrieve().asIterator(String.format("/repos/%s/%s/comments", getOwnerName(), name), GHCommitComment[].class, pageSize)) {
@Override @Override
protected void wrapUp(GHCommitComment[] page) { protected void wrapUp(GHCommitComment[] page) {
for (GHCommitComment c : page) for (GHCommitComment c : page)
@@ -827,13 +987,53 @@ public class GHRepository extends GHObject {
}; };
} }
/**
* Gets the basic license details for the repository.
* <p>
* This is a preview item and subject to change.
*
* @throws IOException as usual but also if you don't use the preview connector
* @return null if there's no license.
*/
@Preview @Deprecated
public GHLicense getLicense() throws IOException{
GHContentWithLicense lic = getLicenseContent_();
return lic!=null ? lic.license : null;
}
/**
* Retrieves the contents of the repository's license file - makes an additional API call
* <p>
* This is a preview item and subject to change.
*
* @return details regarding the license contents, or null if there's no license.
* @throws IOException as usual but also if you don't use the preview connector
*/
@Preview @Deprecated
public GHContent getLicenseContent() throws IOException {
return getLicenseContent_();
}
@Preview @Deprecated
private GHContentWithLicense getLicenseContent_() throws IOException {
try {
return root.retrieve()
.withPreview(DRAX)
.to(getApiTailUrl("license"), GHContentWithLicense.class).wrap(this);
} catch (FileNotFoundException e) {
return null;
}
}
/**
/** /**
* Lists all the commit statues attached to the given commit, newer ones first. * Lists all the commit statues attached to the given commit, newer ones first.
*/ */
public PagedIterable<GHCommitStatus> listCommitStatuses(final String sha1) throws IOException { public PagedIterable<GHCommitStatus> listCommitStatuses(final String sha1) throws IOException {
return new PagedIterable<GHCommitStatus>() { return new PagedIterable<GHCommitStatus>() {
public PagedIterator<GHCommitStatus> _iterator(int pageSize) { public PagedIterator<GHCommitStatus> _iterator(int pageSize) {
return new PagedIterator<GHCommitStatus>(root.retrieve().asIterator(String.format("/repos/%s/%s/statuses/%s", owner.login, name, sha1), GHCommitStatus[].class, pageSize)) { return new PagedIterator<GHCommitStatus>(root.retrieve().asIterator(String.format("/repos/%s/%s/statuses/%s", getOwnerName(), name, sha1), GHCommitStatus[].class, pageSize)) {
@Override @Override
protected void wrapUp(GHCommitStatus[] page) { protected void wrapUp(GHCommitStatus[] page) {
for (GHCommitStatus c : page) for (GHCommitStatus c : page)
@@ -868,7 +1068,7 @@ public class GHRepository extends GHObject {
.with("target_url", targetUrl) .with("target_url", targetUrl)
.with("description", description) .with("description", description)
.with("context", context) .with("context", context)
.to(String.format("/repos/%s/%s/statuses/%s",owner.login,this.name,sha1),GHCommitStatus.class).wrapUp(root); .to(String.format("/repos/%s/%s/statuses/%s",getOwnerName(),this.name,sha1),GHCommitStatus.class).wrapUp(root);
} }
/** /**
@@ -884,7 +1084,7 @@ public class GHRepository extends GHObject {
public PagedIterable<GHEventInfo> listEvents() throws IOException { public PagedIterable<GHEventInfo> listEvents() throws IOException {
return new PagedIterable<GHEventInfo>() { return new PagedIterable<GHEventInfo>() {
public PagedIterator<GHEventInfo> _iterator(int pageSize) { public PagedIterator<GHEventInfo> _iterator(int pageSize) {
return new PagedIterator<GHEventInfo>(root.retrieve().asIterator(String.format("/repos/%s/%s/events", owner.login, name), GHEventInfo[].class, pageSize)) { return new PagedIterator<GHEventInfo>(root.retrieve().asIterator(String.format("/repos/%s/%s/events", getOwnerName(), name), GHEventInfo[].class, pageSize)) {
@Override @Override
protected void wrapUp(GHEventInfo[] page) { protected void wrapUp(GHEventInfo[] page) {
for (GHEventInfo c : page) for (GHEventInfo c : page)
@@ -935,12 +1135,36 @@ public class GHRepository extends GHObject {
} }
/** /**
* Lists all the users who have starred this repo. * Lists all the users who have starred this repo based on the old version of the API. For
* additional information, like date when the repository was starred, see {@link #listStargazers2()}
*/ */
public PagedIterable<GHUser> listStargazers() { public PagedIterable<GHUser> listStargazers() {
return listUsers("stargazers"); return listUsers("stargazers");
} }
/**
* Lists all the users who have starred this repo based on new version of the API, having extended
* information like the time when the repository was starred. For compatibility with the old API
* see {@link #listStargazers()}
*/
public PagedIterable<GHStargazer> listStargazers2() {
return new PagedIterable<GHStargazer>() {
@Override
public PagedIterator<GHStargazer> _iterator(int pageSize) {
Requester requester = root.retrieve();
requester.setHeader("Accept", "application/vnd.github.v3.star+json");
return new PagedIterator<GHStargazer>(requester.asIterator(getApiTailUrl("stargazers"), GHStargazer[].class, pageSize)) {
@Override
protected void wrapUp(GHStargazer[] page) {
for (GHStargazer c : page) {
c.wrapUp(GHRepository.this);
}
}
};
}
};
}
private PagedIterable<GHUser> listUsers(final String suffix) { private PagedIterable<GHUser> listUsers(final String suffix) {
return new PagedIterable<GHUser>() { return new PagedIterable<GHUser>() {
public PagedIterator<GHUser> _iterator(int pageSize) { public PagedIterator<GHUser> _iterator(int pageSize) {
@@ -986,11 +1210,6 @@ public class GHRepository extends GHObject {
// return root.retrieveWithAuth("/pulls/"+owner+'/'+name,JsonPullRequests.class).wrap(root); // return root.retrieveWithAuth("/pulls/"+owner+'/'+name,JsonPullRequests.class).wrap(root);
// } // }
private void verifyMine() throws IOException {
if (!root.login.equals(owner.login))
throw new IOException("Operation not applicable to a repository owned by someone else: "+owner.login);
}
/** /**
* Returns a set that represents the post-commit hook URLs. * Returns a set that represents the post-commit hook URLs.
* The returned set is live, and changes made to them are reflected to GitHub. * The returned set is live, and changes made to them are reflected to GitHub.
@@ -998,7 +1217,7 @@ public class GHRepository extends GHObject {
* @deprecated * @deprecated
* Use {@link #getHooks()} and {@link #createHook(String, Map, Collection, boolean)} * Use {@link #getHooks()} and {@link #createHook(String, Map, Collection, boolean)}
*/ */
@SuppressFBWarnings(value = "DMI_COLLECTION_OF_URLS", @SuppressFBWarnings(value = "DMI_COLLECTION_OF_URLS",
justification = "It causes a performance degradation, but we have already exposed it to the API") justification = "It causes a performance degradation, but we have already exposed it to the API")
public Set<URL> getPostCommitHooks() { public Set<URL> getPostCommitHooks() {
return postCommitHooks; return postCommitHooks;
@@ -1007,8 +1226,9 @@ public class GHRepository extends GHObject {
/** /**
* Live set view of the post-commit hook. * Live set view of the post-commit hook.
*/ */
@SuppressFBWarnings(value = "DMI_COLLECTION_OF_URLS", @SuppressFBWarnings(value = "DMI_COLLECTION_OF_URLS",
justification = "It causes a performance degradation, but we have already exposed it to the API") justification = "It causes a performance degradation, but we have already exposed it to the API")
@SkipFromToString
private final Set<URL> postCommitHooks = new AbstractSet<URL>() { private final Set<URL> postCommitHooks = new AbstractSet<URL>() {
private List<URL> getPostCommitHooks() { private List<URL> getPostCommitHooks() {
try { try {
@@ -1063,6 +1283,9 @@ public class GHRepository extends GHObject {
/*package*/ GHRepository wrap(GitHub root) { /*package*/ GHRepository wrap(GitHub root) {
this.root = root; this.root = root;
if (root.isOffline()) {
owner.wrapUp(root);
}
return this; return this;
} }
@@ -1071,13 +1294,17 @@ public class GHRepository extends GHObject {
*/ */
public Map<String,GHBranch> getBranches() throws IOException { public Map<String,GHBranch> getBranches() throws IOException {
Map<String,GHBranch> r = new TreeMap<String,GHBranch>(); Map<String,GHBranch> r = new TreeMap<String,GHBranch>();
for (GHBranch p : root.retrieve().to(getApiTailUrl("branches"), GHBranch[].class)) { for (GHBranch p : root.retrieve().withPreview(LOKI).to(getApiTailUrl("branches"), GHBranch[].class)) {
p.wrap(this); p.wrap(this);
r.put(p.getName(),p); r.put(p.getName(),p);
} }
return r; return r;
} }
public GHBranch getBranch(String name) throws IOException {
return root.retrieve().withPreview(LOKI).to(getApiTailUrl("branches/"+name),GHBranch.class).wrap(this);
}
/** /**
* @deprecated * @deprecated
* Use {@link #listMilestones(GHIssueState)} * Use {@link #listMilestones(GHIssueState)}
@@ -1297,7 +1524,7 @@ public class GHRepository extends GHObject {
public boolean equals(Object obj) { public boolean equals(Object obj) {
// We ignore contributions in the calculation // We ignore contributions in the calculation
return super.equals(obj); return super.equals(obj);
} }
} }
/** /**
@@ -1325,22 +1552,30 @@ public class GHRepository extends GHObject {
return new GHNotificationStream(root,getApiTailUrl("/notifications")); return new GHNotificationStream(root,getApiTailUrl("/notifications"));
} }
/**
* <a href="https://developer.github.com/v3/repos/traffic/#views">https://developer.github.com/v3/repos/traffic/#views</a>
*/
public GHRepositoryViewTraffic getViewTraffic() throws IOException{
return root.retrieve().to(getApiTailUrl("/traffic/views"), GHRepositoryViewTraffic.class);
}
@Override /**
public String toString() { * <a href="https://developer.github.com/v3/repos/traffic/#clones">https://developer.github.com/v3/repos/traffic/#clones</a>
return "Repository:"+owner.login+":"+name; */
public GHRepositoryCloneTraffic getCloneTraffic() throws IOException{
return root.retrieve().to(getApiTailUrl("/traffic/clones"), GHRepositoryCloneTraffic.class);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return toString().hashCode(); return ("Repository:"+getOwnerName()+":"+name).hashCode();
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj instanceof GHRepository) { if (obj instanceof GHRepository) {
GHRepository that = (GHRepository) obj; GHRepository that = (GHRepository) obj;
return this.owner.login.equals(that.owner.login) return this.getOwnerName().equals(that.getOwnerName())
&& this.name.equals(that.name); && this.name.equals(that.name);
} }
return false; return false;
@@ -1348,6 +1583,6 @@ public class GHRepository extends GHObject {
String getApiTailUrl(String tail) { String getApiTailUrl(String tail) {
if (tail.length()>0 && !tail.startsWith("/")) tail='/'+tail; if (tail.length()>0 && !tail.startsWith("/")) tail='/'+tail;
return "/repos/" + owner.login + "/" + name +tail; return "/repos/" + getOwnerName() + "/" + name +tail;
} }
} }

View File

@@ -0,0 +1,37 @@
package org.kohsuke.github;
import java.util.List;
/**
* Repository clone statistics.
*
* @see GHRepository#getCloneTraffic()
*/
public class GHRepositoryCloneTraffic extends GHRepositoryTraffic {
private List<DailyInfo> clones;
/*package*/ GHRepositoryCloneTraffic() {
}
/*package*/ GHRepositoryCloneTraffic(Integer count, Integer uniques, List<DailyInfo> clones) {
super(count, uniques);
this.clones = clones;
}
public List<DailyInfo> getClones() {
return clones;
}
public List<DailyInfo> getDailyInfo() {
return getClones();
}
public static class DailyInfo extends GHRepositoryTraffic.DailyInfo {
/*package*/ DailyInfo() {
}
/*package*/ DailyInfo(String timestamp, int count, int uniques) {
super(timestamp, count, uniques);
}
}
}

View File

@@ -1,7 +1,5 @@
package org.kohsuke.github; package org.kohsuke.github;
import java.util.Locale;
/** /**
* Search repositories. * Search repositories.
* *
@@ -57,6 +55,11 @@ public class GHRepositorySearchBuilder extends GHSearchBuilder<GHRepository> {
return q("stars:"+v); return q("stars:"+v);
} }
public GHRepositorySearchBuilder order(GHDirection v) {
req.with("order",v);
return this;
}
public GHRepositorySearchBuilder sort(Sort sort) { public GHRepositorySearchBuilder sort(Sort sort) {
req.with("sort",sort); req.with("sort",sort);
return this; return this;

View File

@@ -0,0 +1,54 @@
package org.kohsuke.github;
import java.util.Date;
import java.util.List;
public abstract class GHRepositoryTraffic implements TrafficInfo {
private int count;
private int uniques;
/*package*/ GHRepositoryTraffic() {
}
/*package*/ GHRepositoryTraffic(int count, int uniques) {
this.count = count;
this.uniques = uniques;
}
public int getCount() {
return count;
}
public int getUniques() {
return uniques;
}
public abstract List<? extends DailyInfo> getDailyInfo();
public static abstract class DailyInfo implements TrafficInfo {
private String timestamp;
private int count;
private int uniques;
public Date getTimestamp() {
return GitHub.parseDate(timestamp);
}
public int getCount() {
return count;
}
public int getUniques() {
return uniques;
}
/*package*/ DailyInfo() {
}
/*package*/ DailyInfo(String timestamp, Integer count, Integer uniques) {
this.timestamp = timestamp;
this.count = count;
this.uniques = uniques;
}
}
}

View File

@@ -0,0 +1,37 @@
package org.kohsuke.github;
import java.util.List;
/**
* Repository view statistics.
*
* @see GHRepository#getViewTraffic()
*/
public class GHRepositoryViewTraffic extends GHRepositoryTraffic {
private List<DailyInfo> views;
/*package*/ GHRepositoryViewTraffic() {
}
/*package*/ GHRepositoryViewTraffic(int count, int uniques, List<DailyInfo> views) {
super(count, uniques);
this.views = views;
}
public List<DailyInfo> getViews() {
return views;
}
public List<DailyInfo> getDailyInfo() {
return getViews();
}
public static class DailyInfo extends GHRepositoryTraffic.DailyInfo {
/*package*/ DailyInfo() {
}
/*package*/ DailyInfo(String timestamp, int count, int uniques) {
super(timestamp, count, uniques);
}
}
}

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

@@ -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#getTagObject(String)
*/
@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 * @author Kohsuke Kawaguchi
*/ */
public class GHTeam { public class GHTeam {
private String name,permission; private String name,permission,slug;
private int id; private int id;
private GHOrganization organization; // populated by GET /user/teams where Teams+Orgs are returned together private GHOrganization organization; // populated by GET /user/teams where Teams+Orgs are returned together
@@ -43,6 +43,10 @@ public class GHTeam {
return permission; return permission;
} }
public String getSlug() {
return slug;
}
public int getId() { public int getId() {
return id; return id;
} }
@@ -120,12 +124,25 @@ public class GHTeam {
} }
public void add(GHRepository r) throws IOException { 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 { public void remove(GHRepository r) throws IOException {
org.root.retrieve().method("DELETE").to(api("/repos/" + r.getOwnerName() + '/' + r.getName()), null); 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) { private String api(String tail) {
return "/teams/"+id+tail; return "/teams/"+id+tail;

View File

@@ -1,6 +1,7 @@
package org.kohsuke.github; package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;

View File

@@ -10,10 +10,12 @@ import java.util.List;
* https://developer.github.com/v3/git/trees/ * https://developer.github.com/v3/git/trees/
* *
* @author Daniel Teixeira - https://github.com/ddtxra * @author Daniel Teixeira - https://github.com/ddtxra
* @see GHCommit#getTree()
* @see GHRepository#getTree(String) * @see GHRepository#getTree(String)
* @see GHTreeEntry#asTree()
*/ */
public class GHTree { public class GHTree {
/* package almost final */GitHub root; /* package almost final */GHRepository repo;
private boolean truncated; private boolean truncated;
private String sha, url; private String sha, url;
@@ -28,12 +30,24 @@ public class GHTree {
/** /**
* Return an array of entries of the trees * Return an array of entries of the trees
* @return
*/ */
public List<GHTreeEntry> getTree() { public List<GHTreeEntry> getTree() {
return Collections.unmodifiableList(Arrays.asList(tree)); 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. * 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. * @return true true if the number of items in the tree array exceeded the GitHub maximum limit otherwise false.
@@ -50,8 +64,11 @@ public class GHTree {
return GitHub.parseURL(url); return GitHub.parseURL(url);
} }
/* package */GHTree wrap(GitHub root) { /* package */GHTree wrap(GHRepository repo) {
this.root = root; this.repo = repo;
for (GHTreeEntry e : tree) {
e.tree = this;
}
return this; return this;
} }

View File

@@ -0,0 +1,90 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Builder pattern for creating a new tree.
* Based on https://developer.github.com/v3/git/trees/#create-a-tree
*/
public class GHTreeBuilder {
private final GHRepository repo;
private final Requester req;
private final List<TreeEntry> treeEntries = new ArrayList<TreeEntry>();
@SuppressFBWarnings("URF_UNREAD_FIELD")
private static final class TreeEntry {
private final String path;
private final String mode;
private final String type;
private String sha;
private String content;
private TreeEntry(String path, String mode, String type) {
this.path = path;
this.mode = mode;
this.type = type;
}
}
GHTreeBuilder(GHRepository repo) {
this.repo = repo;
req = new Requester(repo.root);
}
/**
* @param baseTree the SHA of tree you want to update with new data
*/
public GHTreeBuilder baseTree(String baseTree) {
req.with("base_tree", baseTree);
return this;
}
/**
* Adds a new entry to the tree.
* Exactly one of the parameters {@code sha} and {@code content} must be non-null.
*/
public GHTreeBuilder entry(String path, String mode, String type, String sha, String content) {
TreeEntry entry = new TreeEntry(path, mode, type);
entry.sha = sha;
entry.content = content;
treeEntries.add(entry);
return this;
}
/**
* Specialized version of {@link #entry(String, String, String, String, String)} for adding an existing blob referred by its SHA.
*/
public GHTreeBuilder shaEntry(String path, String sha, boolean executable) {
TreeEntry entry = new TreeEntry(path, executable ? "100755" : "100644", "blob");
entry.sha = sha;
treeEntries.add(entry);
return this;
}
/**
* Specialized version of {@link #entry(String, String, String, String, String)} for adding a text file with the specified {@code content}.
*/
public GHTreeBuilder textEntry(String path, String content, boolean executable) {
TreeEntry entry = new TreeEntry(path, executable ? "100755" : "100644", "blob");
entry.content = content;
treeEntries.add(entry);
return this;
}
private String getApiTail() {
return String.format("/repos/%s/%s/git/trees", repo.getOwnerName(), repo.getName());
}
/**
* Creates a tree based on the parameters specified thus far.
*/
public GHTree create() throws IOException {
req._with("tree", treeEntries);
return req.method("POST").to(getApiTail(), GHTree.class).wrap(repo);
}
}

View File

@@ -1,5 +1,7 @@
package org.kohsuke.github; package org.kohsuke.github;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL; import java.net.URL;
/** /**
@@ -10,6 +12,8 @@ import java.net.URL;
* @see GHTree * @see GHTree
*/ */
public class GHTreeEntry { public class GHTreeEntry {
/* package almost final */GHTree tree;
private String path, mode, type, sha, url; private String path, mode, type, sha, url;
private long size; private long size;
@@ -44,7 +48,7 @@ public class GHTreeEntry {
/** /**
* Gets the type such as: * Gets the type such as:
* "blob" * "blob", "tree", etc.
* *
* @return The type * @return The type
*/ */
@@ -68,4 +72,37 @@ public class GHTreeEntry {
public URL getUrl() { public URL getUrl() {
return GitHub.parseURL(url); 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

@@ -196,11 +196,6 @@ public class GHUser extends GHPerson {
}; };
} }
@Override
public String toString() {
return "User:"+login;
}
@Override @Override
public int hashCode() { public int hashCode() {
return login.hashCode(); return login.hashCode();
@@ -219,4 +214,9 @@ public class GHUser extends GHPerson {
if (tail.length()>0 && !tail.startsWith("/")) tail='/'+tail; if (tail.length()>0 && !tail.startsWith("/")) tail='/'+tail;
return "/users/" + login + tail; return "/users/" + login + tail;
} }
/*package*/ GHUser wrapUp(GitHub root) {
super.wrapUp(root);
return this;
}
} }

View File

@@ -1,7 +1,5 @@
package org.kohsuke.github; package org.kohsuke.github;
import java.util.Locale;
/** /**
* Search users. * Search users.
* *
@@ -49,6 +47,11 @@ public class GHUserSearchBuilder extends GHSearchBuilder<GHUser> {
return q("followers:"+v); return q("followers:"+v);
} }
public GHUserSearchBuilder order(GHDirection v) {
req.with("order",v);
return this;
}
public GHUserSearchBuilder sort(Sort sort) { public GHUserSearchBuilder sort(Sort sort) {
req.with("sort",sort); req.with("sort",sort);
return this; return this;

View File

@@ -23,11 +23,16 @@
*/ */
package org.kohsuke.github; package org.kohsuke.github;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; import com.fasterxml.jackson.databind.DeserializationFeature;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; import com.fasterxml.jackson.databind.ObjectMapper;
import static java.util.logging.Level.FINE; import com.fasterxml.jackson.databind.introspect.VisibilityChecker.Std;
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import org.apache.commons.codec.Charsets;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
@@ -48,16 +53,15 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.codec.Charsets; import java.util.concurrent.ConcurrentMap;
import org.apache.commons.codec.binary.Base64;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.VisibilityChecker.Std;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import java.util.logging.Logger; import java.util.logging.Logger;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.*;
import static java.net.HttpURLConnection.*;
import static java.util.logging.Level.*;
import static org.kohsuke.github.Previews.*;
/** /**
* Root of the GitHub API. * Root of the GitHub API.
* *
@@ -77,15 +81,21 @@ public class GitHub {
*/ */
/*package*/ final String encodedAuthorization; /*package*/ final String encodedAuthorization;
private final Map<String,GHUser> users = new Hashtable<String, GHUser>(); private final ConcurrentMap<String,GHUser> users;
private final Map<String,GHOrganization> orgs = new Hashtable<String, GHOrganization>(); private final ConcurrentMap<String,GHOrganization> orgs;
// Cache of myself object.
private GHMyself myself;
private final String apiUrl; private final String apiUrl;
/*package*/ final RateLimitHandler rateLimitHandler; /*package*/ final RateLimitHandler rateLimitHandler;
/*package*/ final AbuseLimitHandler abuseLimitHandler;
private HttpConnector connector = HttpConnector.DEFAULT; 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. * Creates a client API root object.
* *
@@ -122,7 +132,7 @@ public class GitHub {
* @param connector * @param connector
* HttpConnector to use. Pass null to use default connector. * HttpConnector to use. Pass null to use default connector.
*/ */
/* package */ GitHub(String apiUrl, String login, String oauthAccessToken, String password, HttpConnector connector, RateLimitHandler rateLimitHandler) throws IOException { /* 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 if (apiUrl.endsWith("/")) apiUrl = apiUrl.substring(0, apiUrl.length()-1); // normalize
this.apiUrl = apiUrl; this.apiUrl = apiUrl;
if (null != connector) this.connector = connector; if (null != connector) this.connector = connector;
@@ -139,7 +149,10 @@ public class GitHub {
} }
} }
users = new ConcurrentHashMap<String, GHUser>();
orgs = new ConcurrentHashMap<String, GHOrganization>();
this.rateLimitHandler = rateLimitHandler; this.rateLimitHandler = rateLimitHandler;
this.abuseLimitHandler = abuseLimitHandler;
if (login==null && encodedAuthorization!=null) if (login==null && encodedAuthorization!=null)
login = getMyself().getLogin(); login = getMyself().getLogin();
@@ -211,6 +224,24 @@ public class GitHub {
return new GitHubBuilder().withEndpoint(apiUrl).build(); 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 * Is this an anonymous connection
* @return {@code true} if operations that require authentication will fail. * @return {@code true} if operations that require authentication will fail.
@@ -219,10 +250,22 @@ public class GitHub {
return login==null && encodedAuthorization==null; 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() { public HttpConnector getConnector() {
return connector; return connector;
} }
public String getApiUrl() {
return apiUrl;
}
/** /**
* Sets the custom connector used to make requests to GitHub. * Sets the custom connector used to make requests to GitHub.
*/ */
@@ -256,32 +299,78 @@ public class GitHub {
*/ */
public GHRateLimit getRateLimit() throws IOException { public GHRateLimit getRateLimit() throws IOException {
try { try {
return retrieve().to("/rate_limit", JsonRateLimit.class).rate; return rateLimit = retrieve().to("/rate_limit", JsonRateLimit.class).rate;
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
// GitHub Enterprise doesn't have the rate limit, so in that case // GitHub Enterprise doesn't have the rate limit, so in that case
// return some big number that's not too big. // return some big number that's not too big.
// see issue #78 // see issue #78
GHRateLimit r = new GHRateLimit(); GHRateLimit r = new GHRateLimit();
r.limit = r.remaining = 1000000; r.limit = r.remaining = 1000000;
long hours = 1000L * 60 * 60; long hour = 60L * 60L; // this is madness, storing the date as seconds in a Date object
r.reset = new Date(System.currentTimeMillis() + 1 * hours ); r.reset = new Date(System.currentTimeMillis() / 1000L + hour);
return r; 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);
}
}
}
/**
* 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. * Gets the {@link GHUser} that represents yourself.
*/ */
@WithBridgeMethods(GHUser.class) @WithBridgeMethods(GHUser.class)
public GHMyself getMyself() throws IOException { public GHMyself getMyself() throws IOException {
requireCredential(); requireCredential();
synchronized (this) {
if (this.myself != null) return myself;
GHMyself u = retrieve().to("/user", GHMyself.class);
GHMyself u = retrieve().to("/user", GHMyself.class); u.root = this;
this.myself = u;
u.root = this; return u;
users.put(u.getLogin(), u); }
return u;
} }
/** /**
@@ -297,7 +386,7 @@ public class GitHub {
return u; return u;
} }
/** /**
* clears all cached data in order for external changes (modifications and del * clears all cached data in order for external changes (modifications and del
*/ */
@@ -309,7 +398,7 @@ public class GitHub {
/** /**
* Interns the given {@link GHUser}. * Interns the given {@link GHUser}.
*/ */
protected GHUser getUser(GHUser orig) throws IOException { protected GHUser getUser(GHUser orig) {
GHUser u = users.get(orig.getLogin()); GHUser u = users.get(orig.getLogin());
if (u==null) { if (u==null) {
orig.root = this; orig.root = this;
@@ -337,6 +426,62 @@ public class GitHub {
String[] tokens = name.split("/"); String[] tokens = name.split("/");
return retrieve().to("/repos/" + tokens[0] + '/' + tokens[1], GHRepository.class).wrap(this); 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. * This method returns a shallowly populated organizations.
@@ -453,6 +598,42 @@ public class GitHub {
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. * Ensures that the credential is valid.
*/ */
@@ -467,6 +648,18 @@ public class GitHub {
} }
} }
/*package*/ GHUser intern(GHUser user) throws IOException {
if (user==null) return user;
// if we already have this user in our map, use it
GHUser u = users.get(user.getLogin());
if (u!=null) return u;
// if not, remember this new user
users.putIfAbsent(user.getLogin(),user);
return user;
}
private static class GHApiInfo { private static class GHApiInfo {
private String rate_limit_url; private String rate_limit_url;
@@ -528,13 +721,31 @@ public class GitHub {
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
X-Content-Type-Options: nosniff X-Content-Type-Options: nosniff
*/ */
return uc.getResponseCode() == HTTP_UNAUTHORIZED try {
&& uc.getHeaderField("X-GitHub-Media-Type") != null; 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) { } catch (IOException e) {
return false; return false;
} }
} }
/**
* Search commits.
*/
@Preview @Deprecated
public GHCommitSearchBuilder searchCommits() {
return new GHCommitSearchBuilder(this);
}
/** /**
* Search issues. * Search issues.
*/ */

View File

@@ -30,6 +30,7 @@ public class GitHubBuilder {
private HttpConnector connector; private HttpConnector connector;
private RateLimitHandler rateLimitHandler = RateLimitHandler.WAIT; private RateLimitHandler rateLimitHandler = RateLimitHandler.WAIT;
private AbuseLimitHandler abuseLimitHandler = AbuseLimitHandler.WAIT;
public GitHubBuilder() { public GitHubBuilder() {
} }
@@ -178,6 +179,10 @@ public class GitHubBuilder {
this.rateLimitHandler = handler; this.rateLimitHandler = handler;
return this; return this;
} }
public GitHubBuilder withAbuseLimitHandler(AbuseLimitHandler handler) {
this.abuseLimitHandler = handler;
return this;
}
/** /**
* Configures {@linkplain #withConnector(HttpConnector) connector} * Configures {@linkplain #withConnector(HttpConnector) connector}
@@ -193,6 +198,6 @@ public class GitHubBuilder {
} }
public GitHub build() throws IOException { public GitHub build() throws IOException {
return new GitHub(endpoint, user, oauthToken, password, connector, rateLimitHandler); return new GitHub(endpoint, user, oauthToken, password, connector, rateLimitHandler, abuseLimitHandler);
} }
} }

View File

@@ -1,6 +1,7 @@
package org.kohsuke.github; package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Date; import java.util.Date;
/** /**

View File

@@ -5,7 +5,6 @@ import org.kohsuke.github.extras.ImpatientHttpConnector;
import java.io.IOException; import java.io.IOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.util.concurrent.TimeUnit;
/** /**
* Pluggability for customizing HTTP request behaviors or using altogether different library. * Pluggability for customizing HTTP request behaviors or using altogether different library.
@@ -29,4 +28,13 @@ public interface HttpConnector {
return (HttpURLConnection) url.openConnection(); 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

@@ -1,11 +1,10 @@
package org.kohsuke.github; package org.kohsuke.github;
import javax.annotation.CheckForNull;
import java.io.IOException; import java.io.IOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import javax.annotation.CheckForNull;
/** /**
* {@link IOException} for http exceptions because {@link HttpURLConnection} throws un-discerned * {@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 * {@link IOException} and it can help to know the http response code to decide how to handle an

View File

@@ -1,6 +1,5 @@
package org.kohsuke.github; package org.kohsuke.github;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;

View File

@@ -6,7 +6,7 @@ import java.util.List;
import java.util.NoSuchElementException; 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()} * Aside from the normal iterator operation, this method exposes {@link #nextPage()}
* that allows the caller to retrieve items per page. * that allows the caller to retrieve items per page.

View File

@@ -2,7 +2,6 @@ package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.util.Iterator; import java.util.Iterator;
/** /**

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

@@ -9,6 +9,7 @@ import java.net.HttpURLConnection;
* *
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
* @see GitHubBuilder#withRateLimitHandler(RateLimitHandler) * @see GitHubBuilder#withRateLimitHandler(RateLimitHandler)
* @see AbuseLimitHandler
*/ */
public abstract class RateLimitHandler { public abstract class RateLimitHandler {
/** /**

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

@@ -26,7 +26,10 @@ package org.kohsuke.github;
import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonMappingException;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import javax.annotation.CheckForNull;
import javax.annotation.WillClose;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -38,27 +41,28 @@ import java.lang.reflect.Field;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.ProtocolException; import java.net.ProtocolException;
import java.net.SocketTimeoutException;
import java.net.URL; import java.net.URL;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.ListIterator;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import javax.annotation.WillClose; import static java.util.Arrays.*;
import static java.util.logging.Level.*;
import static java.util.Arrays.asList; import static org.apache.commons.lang.StringUtils.*;
import static java.util.logging.Level.FINE;
import static org.kohsuke.github.GitHub.*; import static org.kohsuke.github.GitHub.*;
/** /**
@@ -75,13 +79,14 @@ class Requester {
* Request method. * Request method.
*/ */
private String method = "POST"; private String method = "POST";
private String contentType = "application/x-www-form-urlencoded"; private String contentType = null;
private InputStream body; private InputStream body;
/** /**
* Current connection. * Current connection.
*/ */
private HttpURLConnection uc; private HttpURLConnection uc;
private boolean forceBody;
private static class Entry { private static class Entry {
String key; String key;
@@ -106,6 +111,15 @@ class Requester {
headers.put(name,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. * Makes a request with authentication credential.
*/ */
@@ -159,6 +173,11 @@ class Requester {
return this; return this;
} }
public Requester withNullable(String key, Object value) {
args.add(new Entry(key, value));
return this;
}
public Requester _with(String key, Object value) { public Requester _with(String key, Object value) {
if (value!=null) { if (value!=null) {
args.add(new Entry(key,value)); args.add(new Entry(key,value));
@@ -189,6 +208,16 @@ class Requester {
return this; 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 { public void to(String tailApiUrl) throws IOException {
to(tailApiUrl,null); to(tailApiUrl,null);
} }
@@ -222,7 +251,7 @@ class Requester {
@SuppressFBWarnings("SBSC_USE_STRINGBUFFER_CONCATENATION") @SuppressFBWarnings("SBSC_USE_STRINGBUFFER_CONCATENATION")
private <T> T _to(String tailApiUrl, Class<T> type, T instance) throws IOException { private <T> T _to(String tailApiUrl, Class<T> type, T instance) throws IOException {
if (METHODS_WITHOUT_BODY.contains(method) && !args.isEmpty()) { if (!isMethodWithBody() && !args.isEmpty()) {
boolean questionMarkFound = tailApiUrl.indexOf('?') != -1; boolean questionMarkFound = tailApiUrl.indexOf('?') != -1;
tailApiUrl += questionMarkFound ? '&' : '?'; tailApiUrl += questionMarkFound ? '&' : '?';
for (Iterator<Entry> it = args.listIterator(); it.hasNext();) { for (Iterator<Entry> it = args.listIterator(); it.hasNext();) {
@@ -249,7 +278,7 @@ class Requester {
if (nextLinkMatcher.find()) { if (nextLinkMatcher.find()) {
final String link = nextLinkMatcher.group(1); final String link = nextLinkMatcher.group(1);
T nextResult = _to(link, type, instance); T nextResult = _to(link, type, instance);
setResponseHeaders(nextResult);
final int resultLength = Array.getLength(result); final int resultLength = Array.getLength(result);
final int nextResultLength = Array.getLength(nextResult); final int nextResultLength = Array.getLength(nextResult);
T concatResult = (T) Array.newInstance(type.getComponentType(), resultLength + nextResultLength); T concatResult = (T) Array.newInstance(type.getComponentType(), resultLength + nextResultLength);
@@ -259,9 +288,11 @@ class Requester {
} }
} }
} }
return result; return setResponseHeaders(result);
} catch (IOException e) { } catch (IOException e) {
handleApiError(e); handleApiError(e);
} finally {
noteRateLimit(tailApiUrl);
} }
} }
} }
@@ -280,6 +311,8 @@ class Requester {
return uc.getResponseCode(); return uc.getResponseCode();
} catch (IOException e) { } catch (IOException e) {
handleApiError(e); handleApiError(e);
} finally {
noteRateLimit(tailApiUrl);
} }
} }
} }
@@ -289,11 +322,64 @@ class Requester {
setupConnection(root.getApiURL(tailApiUrl)); setupConnection(root.getApiURL(tailApiUrl));
buildRequest(); buildRequest();
try { try {
return wrapStream(uc.getInputStream()); return wrapStream(uc.getInputStream());
} catch (IOException e) { } catch (IOException e) {
handleApiError(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);
} }
} }
} }
@@ -309,18 +395,19 @@ class Requester {
private void buildRequest() throws IOException { private void buildRequest() throws IOException {
if (isMethodWithBody()) { if (isMethodWithBody()) {
uc.setDoOutput(true); uc.setDoOutput(true);
uc.setRequestProperty("Content-type", contentType);
if (body == null) { if (body == null) {
uc.setRequestProperty("Content-type", defaultString(contentType,"application/json"));
Map json = new HashMap(); Map json = new HashMap();
for (Entry e : args) { for (Entry e : args) {
json.put(e.key, e.value); json.put(e.key, e.value);
} }
MAPPER.writeValue(uc.getOutputStream(), json); MAPPER.writeValue(uc.getOutputStream(), json);
} else { } else {
uc.setRequestProperty("Content-type", defaultString(contentType,"application/x-www-form-urlencoded"));
try { try {
byte[] bytes = new byte[32768]; byte[] bytes = new byte[32768];
int read = 0; int read;
while ((read = body.read(bytes)) != -1) { while ((read = body.read(bytes)) != -1) {
uc.getOutputStream().write(bytes, 0, read); uc.getOutputStream().write(bytes, 0, read);
} }
@@ -332,11 +419,11 @@ class Requester {
} }
private boolean isMethodWithBody() { private boolean isMethodWithBody() {
return !METHODS_WITHOUT_BODY.contains(method); return forceBody || !METHODS_WITHOUT_BODY.contains(method);
} }
/** /**
* Loads pagenated resources. * Loads paginated resources.
* *
* Every iterator call reports a new batch. * Every iterator call reports a new batch.
*/ */
@@ -363,7 +450,7 @@ class Requester {
} }
try { try {
return new PagingIterator<T>(type, root.getApiURL(s.toString())); return new PagingIterator<T>(type, tailApiUrl, root.getApiURL(s.toString()));
} catch (IOException e) { } catch (IOException e) {
throw new Error(e); throw new Error(e);
} }
@@ -372,6 +459,7 @@ class Requester {
class PagingIterator<T> implements Iterator<T> { class PagingIterator<T> implements Iterator<T> {
private final Class<T> type; private final Class<T> type;
private final String tailApiUrl;
/** /**
* The next batch to be returned from {@link #next()}. * The next batch to be returned from {@link #next()}.
@@ -383,9 +471,10 @@ class Requester {
*/ */
private URL url; private URL url;
PagingIterator(Class<T> type, URL url) { PagingIterator(Class<T> type, String tailApiUrl, URL url) {
this.url = url;
this.type = type; this.type = type;
this.tailApiUrl = tailApiUrl;
this.url = url;
} }
public boolean hasNext() { public boolean hasNext() {
@@ -419,6 +508,8 @@ class Requester {
return; return;
} catch (IOException e) { } catch (IOException e) {
handleApiError(e); handleApiError(e);
} finally {
noteRateLimit(tailApiUrl);
} }
} }
} catch (IOException e) { } catch (IOException e) {
@@ -463,6 +554,11 @@ class Requester {
uc.setRequestProperty(e.getKey(), v); uc.setRequestProperty(e.getKey(), v);
} }
setRequestMethod(uc);
uc.setRequestProperty("Accept-Encoding", "gzip");
}
private void setRequestMethod(HttpURLConnection uc) throws IOException {
try { try {
uc.setRequestMethod(method); uc.setRequestMethod(method);
} catch (ProtocolException e) { } catch (ProtocolException e) {
@@ -474,11 +570,31 @@ class Requester {
} catch (Exception x) { } catch (Exception x) {
throw (IOException)new IOException("Failed to set the custom verb").initCause(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"); if (!uc.getRequestMethod().equals(method))
throw new IllegalStateException("Failed to set the request method to "+method);
} }
@CheckForNull
private <T> T parse(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; InputStreamReader r = null;
int responseCode = -1; int responseCode = -1;
String responseMessage = null; String responseMessage = null;
@@ -488,29 +604,53 @@ class Requester {
if (responseCode == 304) { if (responseCode == 304) {
return null; // special case handling for 304 unmodified, as the content will be "" 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"); r = new InputStreamReader(wrapStream(uc.getInputStream()), "UTF-8");
String data = IOUtils.toString(r); String data = IOUtils.toString(r);
if (type!=null) if (type!=null)
try { try {
return MAPPER.readValue(data,type); return setResponseHeaders(MAPPER.readValue(data, type));
} catch (JsonMappingException e) { } catch (JsonMappingException e) {
throw (IOException)new IOException("Failed to deserialize " +data).initCause(e); throw (IOException)new IOException("Failed to deserialize " +data).initCause(e);
} }
if (instance!=null) if (instance!=null) {
return MAPPER.readerForUpdating(instance).<T>readValue(data); return setResponseHeaders(MAPPER.readerForUpdating(instance).<T>readValue(data));
}
return null; return null;
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
// java.net.URLConnection handles 404 exception has FileNotFoundException, don't wrap exception in HttpException // java.net.URLConnection handles 404 exception has FileNotFoundException, don't wrap exception in HttpException
// to preserve backward compatibility // to preserve backward compatibility
throw e; throw e;
} catch (IOException e) { } catch (IOException e) {
if (e instanceof SocketTimeoutException && timeouts > 0) {
LOGGER.log(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); throw new HttpException(responseCode, responseMessage, uc.getURL(), e);
} finally { } finally {
IOUtils.closeQuietly(r); IOUtils.closeQuietly(r);
} }
} }
private <T> T setResponseHeaders(T readValue) {
if (readValue instanceof GHObject[]) {
for (GHObject ghObject : (GHObject[]) readValue) {
setResponseHeaders(ghObject);
}
} else if (readValue instanceof GHObject) {
setResponseHeaders((GHObject) readValue);
}
return readValue;
}
private void setResponseHeaders(GHObject readValue) {
readValue.responseHeaderFields = uc.getHeaderFields();
}
/** /**
* Handles the "Content-Encoding" header. * Handles the "Content-Encoding" header.
*/ */
@@ -537,6 +677,24 @@ class Requester {
" handling exception " + e, e); " handling exception " + e, e);
throw e; throw e;
} }
InputStream es = wrapStream(uc.getErrorStream());
if (es != null) {
try {
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 GHFileNotFoundException(error).withResponseHeaderFields(uc).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 GHIOException(error).withResponseHeaderFields(uc).initCause(e);
}
} finally {
IOUtils.closeQuietly(es);
}
}
if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) // 401 / Unauthorized == bad creds if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) // 401 / Unauthorized == bad creds
throw e; throw e;
@@ -545,19 +703,14 @@ class Requester {
return; return;
} }
InputStream es = wrapStream(uc.getErrorStream()); // Retry-After is not documented but apparently that field exists
try { if (responseCode == HttpURLConnection.HTTP_FORBIDDEN &&
if (es!=null) { uc.getHeaderField("Retry-After") != null) {
if (e instanceof FileNotFoundException) { this.root.abuseLimitHandler.onError(e,uc);
// pass through 404 Not Found to allow the caller to handle it intelligently return;
throw (IOException) new FileNotFoundException(IOUtils.toString(es, "UTF-8")).initCause(e);
} else
throw (IOException) new IOException(IOUtils.toString(es, "UTF-8")).initCause(e);
} else
throw e;
} finally {
IOUtils.closeQuietly(es);
} }
throw e;
} }
private static final List<String> METHODS_WITHOUT_BODY = asList("GET", "DELETE"); private static final List<String> METHODS_WITHOUT_BODY = asList("GET", "DELETE");

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,16 @@
package org.kohsuke.github;
/**
* @author Kohsuke Kawaguchi
*/
public interface TrafficInfo {
/**
* Total count of hits.
*/
int getCount();
/**
* Unique visitors.
*/
int getUniques();
}

View File

@@ -0,0 +1,32 @@
package org.kohsuke.github.extras;
import okhttp3.OkHttpClient;
import okhttp3.OkUrlFactory;
import org.kohsuke.github.HttpConnector;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* {@link HttpConnector} for {@link OkHttpClient}.
*
* Unlike {@link #DEFAULT}, OkHttp does response caching.
* Making a conditional request against GitHubAPI and receiving a 304
* response does not count against the rate limit.
* See http://developer.github.com/v3/#conditional-requests
*
* @author Roberto Tyley
* @author Kohsuke Kawaguchi
*/
public class OkHttp3Connector implements HttpConnector {
private final OkUrlFactory urlFactory;
public OkHttp3Connector(OkUrlFactory urlFactory) {
this.urlFactory = urlFactory;
}
public HttpURLConnection connect(URL url) throws IOException {
return urlFactory.open(url);
}
}

View File

@@ -1,19 +1,16 @@
import org.kohsuke.github.GHRepository; import org.kohsuke.github.GHRepository.Contributor;
import org.kohsuke.github.GHUser; import org.kohsuke.github.GHUser;
import org.kohsuke.github.GitHub; import org.kohsuke.github.GitHub;
import java.util.Collection;
/** /**
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
*/ */
public class Foo { public class Foo {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
Collection<GHRepository> lst = GitHub.connect().getUser("kohsuke").getRepositories().values(); GitHub gh = GitHub.connect();
for (GHRepository r : lst) { for (Contributor c : gh.getRepository("kohsuke/yo").listContributors()) {
System.out.println(r.getName()); System.out.println(c);
} }
System.out.println(lst.size());
} }
private static void testRateLimit() throws Exception { private static void testRateLimit() throws Exception {

View File

@@ -1,10 +1,15 @@
package org.kohsuke.github; package org.kohsuke.github;
import java.io.FileInputStream;
import java.util.Properties;
import org.apache.commons.io.IOUtils;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before; import org.junit.Before;
import org.kohsuke.randname.RandomNameGenerator; import org.kohsuke.randname.RandomNameGenerator;
import java.io.File; import java.io.File;
import java.io.IOException;
/** /**
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
@@ -17,13 +22,34 @@ public abstract class AbstractGitHubApiTestBase extends Assert {
public void setUp() throws Exception { public void setUp() throws Exception {
File f = new File(System.getProperty("user.home"), ".github.kohsuke2"); File f = new File(System.getProperty("user.home"), ".github.kohsuke2");
if (f.exists()) { if (f.exists()) {
Properties props = new Properties();
FileInputStream in = null;
try {
in = new FileInputStream(f);
props.load(in);
} finally {
IOUtils.closeQuietly(in);
}
// use the non-standard credential preferentially, so that developers of this library do not have // use the non-standard credential preferentially, so that developers of this library do not have
// to clutter their event stream. // to clutter their event stream.
gitHub = GitHubBuilder.fromPropertyFile(f.getPath()).withRateLimitHandler(RateLimitHandler.FAIL).build(); gitHub = GitHubBuilder.fromProperties(props).withRateLimitHandler(RateLimitHandler.FAIL).build();
} else { } else {
gitHub = GitHubBuilder.fromCredentials().withRateLimitHandler(RateLimitHandler.FAIL).build(); gitHub = GitHubBuilder.fromCredentials().withRateLimitHandler(RateLimitHandler.FAIL).build();
} }
} }
protected GHUser getUser() {
try {
return gitHub.getMyself();
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
protected void kohsuke() {
String login = getUser().getLogin();
Assume.assumeTrue(login.equals("kohsuke") || login.equals("kohsuke2"));
}
protected static final RandomNameGenerator rnd = new RandomNameGenerator(); protected static final RandomNameGenerator rnd = new RandomNameGenerator();
} }

View File

@@ -5,17 +5,19 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.junit.Assume;
import org.junit.Test; import org.junit.Test;
import org.kohsuke.github.GHCommit.File; import org.kohsuke.github.GHCommit.File;
import org.kohsuke.github.GHOrganization.Permission; import org.kohsuke.github.GHOrganization.Permission;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static org.hamcrest.CoreMatchers.*;
/** /**
* Unit test for simple App. * Unit test for simple App.
*/ */
@@ -39,7 +41,7 @@ public class AppTest extends AbstractGitHubApiTestBase {
} }
@Test @Test
public void testRepositoryWithAutoInitializationCRUD() throws IOException { public void testRepositoryWithAutoInitializationCRUD() throws Exception {
String name = "github-api-test-autoinit"; String name = "github-api-test-autoinit";
deleteRepository(name); deleteRepository(name);
GHRepository r = gitHub.createRepository(name) GHRepository r = gitHub.createRepository(name)
@@ -49,6 +51,7 @@ public class AppTest extends AbstractGitHubApiTestBase {
r.enableIssueTracker(false); r.enableIssueTracker(false);
r.enableDownloads(false); r.enableDownloads(false);
r.enableWiki(false); r.enableWiki(false);
Thread.sleep(3000);
assertNotNull(r.getReadme()); assertNotNull(r.getReadme());
getUser().getRepository(name).delete(); getUser().getRepository(name).delete();
} }
@@ -167,14 +170,6 @@ public class AppTest extends AbstractGitHubApiTestBase {
return repository; return repository;
} }
private GHUser getUser() {
try {
return gitHub.getMyself();
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
@Test @Test
public void testListIssues() throws IOException { public void testListIssues() throws IOException {
GHUser u = getUser(); GHUser u = getUser();
@@ -221,10 +216,12 @@ public class AppTest extends AbstractGitHubApiTestBase {
} }
@Test @Test
public void testMyTeamsContainsAllMyOrganizations() throws IOException { public void testMyOrganizationsContainMyTeams() throws IOException {
Map<String, Set<GHTeam>> teams = gitHub.getMyTeams(); Map<String, Set<GHTeam>> teams = gitHub.getMyTeams();
Map<String, GHOrganization> myOrganizations = gitHub.getMyOrganizations(); Map<String, GHOrganization> myOrganizations = gitHub.getMyOrganizations();
assertEquals(teams.keySet(), myOrganizations.keySet()); //GitHub no longer has default 'owners' team, so there may be organization memberships without a team
//https://help.github.com/articles/about-improved-organization-permissions/
assertTrue(myOrganizations.keySet().containsAll(teams.keySet()));
} }
@Test @Test
@@ -335,6 +332,13 @@ public class AppTest extends AbstractGitHubApiTestBase {
assertNotNull(e); assertNotNull(e);
} }
@Test
public void testOrgTeamBySlug() throws Exception {
kohsuke();
GHTeam e = gitHub.getOrganization("github-api-test-org").getTeamBySlug("core-developers");
assertNotNull(e);
}
@Test @Test
public void testCommit() throws Exception { public void testCommit() throws Exception {
GHCommit commit = gitHub.getUser("jenkinsci").getRepository("jenkins").getCommit("08c1c9970af4d609ae754fbe803e06186e3206f7"); GHCommit commit = gitHub.getUser("jenkinsci").getRepository("jenkins").getCommit("08c1c9970af4d609ae754fbe803e06186e3206f7");
@@ -348,6 +352,11 @@ public class AppTest extends AbstractGitHubApiTestBase {
assertEquals(48,f.getLinesChanged()); assertEquals(48,f.getLinesChanged());
assertEquals("modified",f.getStatus()); assertEquals("modified",f.getStatus());
assertEquals("changelog.html", f.getFileName()); assertEquals("changelog.html", f.getFileName());
// walk the tree
GHTree t = commit.getTree();
assertThat(IOUtils.toString(t.getEntry("todo.txt").readAsBlob()), containsString("executor rendering"));
assertNotNull(t.getEntry("war").asTree());
} }
@Test @Test
@@ -592,6 +601,8 @@ public class AppTest extends AbstractGitHubApiTestBase {
.prerelease(false) .prerelease(false)
.create(); .create();
Thread.sleep(3000);
try { try {
for (GHTag tag : r.listTags()) { for (GHTag tag : r.listTags()) {
@@ -671,6 +682,15 @@ public class AppTest extends AbstractGitHubApiTestBase {
assertFalse(all.isEmpty()); assertFalse(all.isEmpty());
} }
@Test
public void testCommitSearch() throws IOException {
PagedSearchIterable<GHCommit> r = gitHub.searchCommits().author("kohsuke").list();
assertTrue(r.getTotalCount() > 0);
GHCommit firstCommit = r.iterator().next();
assertTrue(firstCommit.getFiles().size() > 0);
}
@Test @Test
public void testIssueSearch() throws IOException { public void testIssueSearch() throws IOException {
PagedSearchIterable<GHIssue> r = gitHub.searchIssues().mentions("kohsuke").isOpen().list(); PagedSearchIterable<GHIssue> r = gitHub.searchIssues().mentions("kohsuke").isOpen().list();
@@ -845,8 +865,65 @@ public class AppTest extends AbstractGitHubApiTestBase {
gitHub.listNotifications().markAsRead(); gitHub.listNotifications().markAsRead();
} }
private void kohsuke() { /**
String login = getUser().getLogin(); * Just basic code coverage to make sure toString() doesn't blow up
Assume.assumeTrue(login.equals("kohsuke") || login.equals("kohsuke2")); */
@Test
public void checkToString() throws Exception {
GHUser u = gitHub.getUser("rails");
System.out.println(u);
GHRepository r = u.getRepository("rails");
System.out.println(r);
System.out.println(r.getIssue(1));
}
@Test
public void reactions() throws Exception {
GHIssue i = gitHub.getRepository("kohsuke/github-api").getIssue(311);
// retrieval
GHReaction r = i.listReactions().iterator().next();
assertThat(r.getUser().getLogin(), is("kohsuke"));
assertThat(r.getContent(),is(ReactionContent.HEART));
// CRUD
GHReaction a = i.createReaction(ReactionContent.HOORAY);
assertThat(a.getUser().getLogin(),is(gitHub.getMyself().getLogin()));
a.delete();
}
@Test
public void listOrgMemberships() throws Exception {
GHMyself me = gitHub.getMyself();
for (GHMembership m : me.listOrgMemberships()) {
assertThat(m.getUser(), is((GHUser)me));
assertNotNull(m.getState());
assertNotNull(m.getRole());
System.out.printf("%s %s %s\n",
m.getOrganization().getLogin(),
m.getState(),
m.getRole());
}
}
@Test
public void blob() throws Exception {
GHRepository r = gitHub.getRepository("kohsuke/github-api");
String sha1 = "a12243f2fc5b8c2ba47dd677d0b0c7583539584d";
assertBlobContent(r.readBlob(sha1));
GHBlob blob = r.getBlob(sha1);
assertBlobContent(blob.read());
assertThat(blob.getSha(),is("a12243f2fc5b8c2ba47dd677d0b0c7583539584d"));
assertThat(blob.getSize(),is(1104L));
}
private void assertBlobContent(InputStream is) throws Exception {
String content = new String(IOUtils.toByteArray(is),"UTF-8");
assertThat(content,containsString("Copyright (c) 2011- Kohsuke Kawaguchi and other contributors"));
assertThat(content,containsString("FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR"));
assertThat(content.length(),is(1104));
} }
} }

View File

@@ -1,7 +1,6 @@
package org.kohsuke.github; package org.kohsuke.github;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import org.junit.Test; import org.junit.Test;
import java.io.IOException; import java.io.IOException;

View File

@@ -0,0 +1,82 @@
package org.kohsuke.github;
import org.junit.Before;
import org.junit.Test;
import org.kohsuke.github.GHBranchProtection.EnforceAdmins;
import org.kohsuke.github.GHBranchProtection.RequiredReviews;
import org.kohsuke.github.GHBranchProtection.RequiredStatusChecks;
import java.io.FileNotFoundException;
public class GHBranchProtectionTest extends AbstractGitHubApiTestBase {
private static final String BRANCH = "bp-test";
private static final String BRANCH_REF = "heads/" + BRANCH;
private GHBranch branch;
private GHRepository repo;
@Before
@Override
public void setUp() throws Exception {
super.setUp();
repo = gitHub.getRepository("github-api-test-org/GHContentIntegrationTest").fork();
try {
repo.getRef(BRANCH_REF);
} catch (FileNotFoundException e) {
repo.createRef("refs/" + BRANCH_REF, repo.getBranch("master").getSHA1());
}
branch = repo.getBranch(BRANCH);
if (branch.isProtected()) {
branch.disableProtection();
}
branch = repo.getBranch(BRANCH);
assertFalse(branch.isProtected());
}
@Test
public void testEnableBranchProtections() throws Exception {
// team/user restrictions require an organization repo to test against
GHBranchProtection protection = branch.enableProtection()
.addRequiredChecks("test-status-check")
.requireBranchIsUpToDate()
.requireCodeOwnReviews()
.dismissStaleReviews()
.includeAdmins()
.enable();
RequiredStatusChecks statusChecks = protection.getRequiredStatusChecks();
assertNotNull(statusChecks);
assertTrue(statusChecks.isRequiresBranchUpToDate());
assertTrue(statusChecks.getContexts().contains("test-status-check"));
RequiredReviews requiredReviews = protection.getRequiredReviews();
assertNotNull(requiredReviews);
assertTrue(requiredReviews.isDismissStaleReviews());
assertTrue(requiredReviews.isRequireCodeOwnerReviews());
EnforceAdmins enforceAdmins = protection.getEnforceAdmins();
assertNotNull(enforceAdmins);
assertTrue(enforceAdmins.isEnabled());
}
@Test
public void testEnableProtectionOnly() throws Exception {
branch.enableProtection().enable();
assertTrue(repo.getBranch(BRANCH).isProtected());
}
@Test
public void testEnableRequireReviewsOnly() throws Exception {
GHBranchProtection protection = branch.enableProtection()
.requireReviews()
.enable();
assertNotNull(protection.getRequiredReviews());
}
}

View File

@@ -0,0 +1,251 @@
package org.kohsuke.github;
import org.junit.Rule;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
public class GHEventPayloadTest {
@Rule
public final PayloadRule payload = new PayloadRule(".json");
@Test
public void commit_comment() throws Exception {
GHEventPayload.CommitComment event =
GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.CommitComment.class);
assertThat(event.getAction(), is("created"));
assertThat(event.getComment().getSHA1(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b"));
assertThat(event.getComment().getUser().getLogin(), is("baxterthehacker"));
assertThat(event.getRepository().getName(), is("public-repo"));
assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker"));
assertThat(event.getSender().getLogin(), is("baxterthehacker"));
}
@Test
public void create() throws Exception {
GHEventPayload.Create event =
GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.Create.class);
assertThat(event.getRef(), is("0.0.1"));
assertThat(event.getRefType(), is("tag"));
assertThat(event.getMasterBranch(), is("master"));
assertThat(event.getDescription(), is(""));
assertThat(event.getRepository().getName(), is("public-repo"));
assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker"));
assertThat(event.getSender().getLogin(), is("baxterthehacker"));
}
@Test
public void delete() throws Exception {
GHEventPayload.Delete event =
GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.Delete.class);
assertThat(event.getRef(), is("simple-tag"));
assertThat(event.getRefType(), is("tag"));
assertThat(event.getRepository().getName(), is("public-repo"));
assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker"));
assertThat(event.getSender().getLogin(), is("baxterthehacker"));
}
@Test
public void deployment() throws Exception {
GHEventPayload.Deployment event =
GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.Deployment.class);
assertThat(event.getDeployment().getSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b"));
assertThat(event.getDeployment().getEnvironment(), is("production"));
assertThat(event.getDeployment().getCreator().getLogin(), is("baxterthehacker"));
assertThat(event.getRepository().getName(), is("public-repo"));
assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker"));
assertThat(event.getSender().getLogin(), is("baxterthehacker"));
}
@Test
public void deployment_status() throws Exception {
GHEventPayload.DeploymentStatus event =
GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.DeploymentStatus.class);
assertThat(event.getDeploymentStatus().getState(), is(GHDeploymentState.SUCCESS));
assertThat(event.getDeploymentStatus().getTargetUrl(), nullValue());
assertThat(event.getDeployment().getSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b"));
assertThat(event.getDeployment().getEnvironment(), is("production"));
assertThat(event.getDeployment().getCreator().getLogin(), is("baxterthehacker"));
assertThat(event.getRepository().getName(), is("public-repo"));
assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker"));
assertThat(event.getSender().getLogin(), is("baxterthehacker"));
}
@Test
public void fork() throws Exception {
GHEventPayload.Fork event =
GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.Fork.class);
assertThat(event.getForkee().getName(), is("public-repo"));
assertThat(event.getForkee().getOwner().getLogin(), is("baxterandthehackers"));
assertThat(event.getRepository().getName(), is("public-repo"));
assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker"));
assertThat(event.getSender().getLogin(), is("baxterandthehackers"));
}
// TODO uncomment when we have GHPage implemented
// @Test
// public void gollum() throws Exception {
// GHEventPayload.Gollum event =
// GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.Gollum.class);
// assertThat(event.getPages().size(), is(1));
// GHPage page = event.getPages().get(0);
// assertThat(page.getName(), is("Home"));
// assertThat(page.getTitle(), is("Home"));
// assertThat(page.getSummary(), nullValue());
// assertThat(page.getAction(), is("created"));
// assertThat(page.getSha(), is("91ea1bd42aa2ba166b86e8aefe049e9837214e67"));
// assertThat(page.getHtmlUrl(), is("https://github.com/baxterthehacker/public-repo/wiki/Home"));
// assertThat(event.getRepository().getName(), is("public-repo"));
// assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker"));
// assertThat(event.getSender().getLogin(), is("baxterthehacker"));
// }
@Test
public void issue_comment() throws Exception {
GHEventPayload.IssueComment event =
GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.IssueComment.class);
assertThat(event.getAction(), is("created"));
assertThat(event.getIssue().getNumber(), is(2));
assertThat(event.getIssue().getTitle(), is("Spelling error in the README file"));
assertThat(event.getIssue().getState(), is(GHIssueState.OPEN));
assertThat(event.getIssue().getLabels().size(), is(1));
assertThat(event.getIssue().getLabels().iterator().next().getName(), is("bug"));
assertThat(event.getComment().getUser().getLogin(), is("baxterthehacker"));
assertThat(event.getComment().getBody(), is("You are totally right! I'll get this fixed right away."));
assertThat(event.getRepository().getName(), is("public-repo"));
assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker"));
assertThat(event.getSender().getLogin(), is("baxterthehacker"));
}
// TODO implement support classes and write test
// @Test
// public void issues() throws Exception {}
// TODO implement support classes and write test
// @Test
// public void label() throws Exception {}
// TODO implement support classes and write test
// @Test
// public void member() throws Exception {}
// TODO implement support classes and write test
// @Test
// public void membership() throws Exception {}
// TODO implement support classes and write test
// @Test
// public void milestone() throws Exception {}
// TODO implement support classes and write test
// @Test
// public void page_build() throws Exception {}
@Test
@Payload("public")
public void public_() throws Exception {
GHEventPayload.Public event =
GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.Public.class);
assertThat(event.getRepository().getName(), is("public-repo"));
assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker"));
assertThat(event.getSender().getLogin(), is("baxterthehacker"));
}
@Test
public void pull_request() throws Exception {
GHEventPayload.PullRequest event =
GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.PullRequest.class);
assertThat(event.getAction(), is("opened"));
assertThat(event.getNumber(), is(1));
assertThat(event.getPullRequest().getNumber(), is(1));
assertThat(event.getPullRequest().getTitle(), is("Update the README with new information"));
assertThat(event.getPullRequest().getBody(), is("This is a pretty simple change that we need to pull into "
+ "master."));
assertThat(event.getPullRequest().getUser().getLogin(), is("baxterthehacker"));
assertThat(event.getPullRequest().getHead().getUser().getLogin(), is("baxterthehacker"));
assertThat(event.getPullRequest().getHead().getRef(), is("changes"));
assertThat(event.getPullRequest().getHead().getLabel(), is("baxterthehacker:changes"));
assertThat(event.getPullRequest().getHead().getSha(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c"));
assertThat(event.getPullRequest().getBase().getUser().getLogin(), is("baxterthehacker"));
assertThat(event.getPullRequest().getBase().getRef(), is("master"));
assertThat(event.getPullRequest().getBase().getLabel(), is("baxterthehacker:master"));
assertThat(event.getPullRequest().getBase().getSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b"));
assertThat(event.getPullRequest().isMerged(), is(false));
assertThat(event.getPullRequest().getMergeable(), nullValue());
assertThat(event.getPullRequest().getMergeableState(), is("unknown"));
assertThat(event.getPullRequest().getMergedBy(), nullValue());
assertThat(event.getPullRequest().getCommentsCount(), is(0));
assertThat(event.getPullRequest().getReviewComments(), is(0));
assertThat(event.getPullRequest().getAdditions(), is(1));
assertThat(event.getPullRequest().getDeletions(), is(1));
assertThat(event.getPullRequest().getChangedFiles(), is(1));
assertThat(event.getRepository().getName(), is("public-repo"));
assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker"));
assertThat(event.getSender().getLogin(), is("baxterthehacker"));
}
// TODO implement support classes and write test
// @Test
// public void pull_request_review() throws Exception {}
// TODO implement support classes and write test
// @Test
// public void pull_request_review_comment() throws Exception {}
@Test
public void push() throws Exception {
GHEventPayload.Push event =
GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.Push.class);
assertThat(event.getRef(), is("refs/heads/changes"));
assertThat(event.getBefore(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b"));
assertThat(event.getHead(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c"));
assertThat(event.isCreated(), is(false));
assertThat(event.isDeleted(), is(false));
assertThat(event.isForced(), is(false));
assertThat(event.getCommits().size(), is(1));
assertThat(event.getCommits().get(0).getSha(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c"));
assertThat(event.getCommits().get(0).getAuthor().getEmail(), is("baxterthehacker@users.noreply.github.com"));
assertThat(event.getCommits().get(0).getCommitter().getEmail(), is("baxterthehacker@users.noreply.github.com"));
assertThat(event.getCommits().get(0).getAdded().size(), is(0));
assertThat(event.getCommits().get(0).getRemoved().size(), is(0));
assertThat(event.getCommits().get(0).getModified().size(), is(1));
assertThat(event.getCommits().get(0).getModified().get(0), is("README.md"));
assertThat(event.getRepository().getName(), is("public-repo"));
assertThat(event.getRepository().getOwnerName(), is("baxterthehacker"));
assertThat(event.getRepository().getUrl().toExternalForm(), is("https://github.com/baxterthehacker/public-repo"));
assertThat(event.getPusher().getName(), is("baxterthehacker"));
assertThat(event.getPusher().getEmail(), is("baxterthehacker@users.noreply.github.com"));
assertThat(event.getSender().getLogin(), is("baxterthehacker"));
}
// TODO implement support classes and write test
// @Test
// public void release() throws Exception {}
@Test
public void repository() throws Exception {
GHEventPayload.Repository event =
GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.Repository.class);
assertThat(event.getAction(), is("created"));
assertThat(event.getRepository().getName(), is("new-repository"));
assertThat(event.getRepository().getOwner().getLogin(), is("baxterandthehackers"));
assertThat(event.getOrganization().getLogin(), is("baxterandthehackers"));
assertThat(event.getSender().getLogin(), is("baxterthehacker"));
}
// TODO implement support classes and write test
// @Test
// public void status() throws Exception {}
// TODO implement support classes and write test
// @Test
// public void team_add() throws Exception {}
// TODO implement support classes and write test
// @Test
// public void watch() throws Exception {}
}

View File

@@ -0,0 +1,78 @@
package org.kohsuke.github;
import org.apache.commons.lang.StringUtils;
import org.junit.Ignore;
import org.junit.Test;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.hasValue;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.junit.Assert.assertThat;
/**
* @author Kanstantsin Shautsou
*/
public class GHHookTest {
@Ignore
@Test
public void exposeResponceHeaders() throws Exception {
String user1Login = "KostyaSha-auto";
String user1Pass = "secret";
String clientId = "90140219451";
String clientSecret = "1451245425";
String orgRepo = "KostyaSha-org/test";
// some login based user that has access to application
final GitHub gitHub = GitHub.connectUsingPassword(user1Login, user1Pass);
gitHub.getMyself();
// we request read
final List<String> scopes = Arrays.asList("repo", "read:org", "user:email", "read:repo_hook");
// application creates token with scopes
final GHAuthorization auth = gitHub.createOrGetAuth(clientId, clientSecret, scopes, "", "");
String token = auth.getToken();
if (StringUtils.isEmpty(token)) {
gitHub.deleteAuth(auth.getId());
token = gitHub.createOrGetAuth(clientId, clientSecret, scopes, "", "").getToken();
}
/// now create connection using token
final GitHub gitHub2 = GitHub.connectUsingOAuth(token);
// some repo in organisation
final GHRepository repository = gitHub2.getRepository(orgRepo);
// doesn't fail because we have read access
final List<GHHook> hooks = repository.getHooks();
try {
// fails because application isn't approved in organisation and you can find it only after doing real call
final GHHook hook = repository.createHook(
"my-hook",
singletonMap("url", "http://localhost"),
singletonList(GHEvent.PUSH),
true
);
} catch (IOException ex) {
assertThat(ex, instanceOf(GHFileNotFoundException.class));
final GHFileNotFoundException ghFileNotFoundException = (GHFileNotFoundException) ex;
final Map<String, List<String>> responseHeaderFields = ghFileNotFoundException.getResponseHeaderFields();
assertThat(responseHeaderFields, hasKey("X-Accepted-OAuth-Scopes"));
assertThat(responseHeaderFields.get("X-Accepted-OAuth-Scopes"),
hasItem("admin:repo_hook, public_repo, repo, write:repo_hook")
);
}
}
}

View File

@@ -0,0 +1,193 @@
/*
* 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 org.apache.commons.io.IOUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.net.URL;
/**
* @author Duncan Dickinson
*/
public class GHLicenseTest extends Assert {
private GitHub gitHub;
@Before
public void setUp() throws Exception {
gitHub = new GitHubBuilder()
.fromCredentials()
.build();
}
/**
* Basic test to ensure that the list of licenses from {@link GitHub#listLicenses()} is returned
*
* @throws IOException
*/
@Test
public void listLicenses() throws IOException {
Iterable<GHLicense> licenses = gitHub.listLicenses();
assertTrue(licenses.iterator().hasNext());
}
/**
* Tests that {@link GitHub#listLicenses()} returns the MIT license
* in the expected manner.
*
* @throws IOException
*/
@Test
public void listLicensesCheckIndividualLicense() throws IOException {
PagedIterable<GHLicense> licenses = gitHub.listLicenses();
for (GHLicense lic : licenses) {
if (lic.getKey().equals("mit")) {
assertTrue(lic.getUrl().equals(new URL("https://api.github.com/licenses/mit")));
return;
}
}
fail("The MIT license was not found");
}
/**
* Checks that the request for an individual license using {@link GitHub#getLicense(String)}
* returns expected values (not all properties are checked)
*
* @throws IOException
*/
@Test
public void getLicense() throws IOException {
String key = "mit";
GHLicense license = gitHub.getLicense(key);
assertNotNull(license);
assertTrue("The name is correct", license.getName().equals("MIT License"));
assertTrue("The HTML URL is correct", license.getHtmlUrl().equals(new URL("http://choosealicense.com/licenses/mit/")));
}
/**
* Accesses the 'kohsuke/github-api' repo using {@link GitHub#getRepository(String)}
* and checks that the license is correct
*
* @throws IOException
*/
@Test
public void checkRepositoryLicense() throws IOException {
GHRepository repo = gitHub.getRepository("kohsuke/github-api");
GHLicense license = repo.getLicense();
assertNotNull("The license is populated", license);
assertTrue("The key is correct", license.getKey().equals("mit"));
assertTrue("The name is correct", license.getName().equals("MIT License"));
assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/mit")));
}
/**
* Accesses the 'atom/atom' repo using {@link GitHub#getRepository(String)}
* and checks that the license is correct
*
* @throws IOException
*/
@Test
public void checkRepositoryLicenseAtom() throws IOException {
GHRepository repo = gitHub.getRepository("atom/atom");
GHLicense license = repo.getLicense();
assertNotNull("The license is populated", license);
assertTrue("The key is correct", license.getKey().equals("mit"));
assertTrue("The name is correct", license.getName().equals("MIT License"));
assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/mit")));
}
/**
* Accesses the 'pomes/pomes' repo using {@link GitHub#getRepository(String)}
* and checks that the license is correct
*
* @throws IOException
*/
@Test
public void checkRepositoryLicensePomes() throws IOException {
GHRepository repo = gitHub.getRepository("pomes/pomes");
GHLicense license = repo.getLicense();
assertNotNull("The license is populated", license);
assertTrue("The key is correct", license.getKey().equals("apache-2.0"));
assertTrue("The name is correct", license.getName().equals("Apache License 2.0"));
assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/apache-2.0")));
}
/**
* Accesses the 'dedickinson/test-repo' repo using {@link GitHub#getRepository(String)}
* and checks that *no* license is returned as the repo doesn't have one
*
* @throws IOException
*/
@Test
public void checkRepositoryWithoutLicense() throws IOException {
GHRepository repo = gitHub.getRepository("dedickinson/test-repo");
GHLicense license = repo.getLicense();
assertNull("There is no license", license);
}
/**
* Accesses the 'kohsuke/github-api' repo using {@link GitHub#getRepository(String)}
* and then calls {@link GHRepository#getLicense()} and checks that certain
* properties are correct
*
* @throws IOException
*/
@Test
public void checkRepositoryFullLicense() throws IOException {
GHRepository repo = gitHub.getRepository("kohsuke/github-api");
GHLicense license = repo.getLicense();
assertNotNull("The license is populated", license);
assertTrue("The key is correct", license.getKey().equals("mit"));
assertTrue("The name is correct", license.getName().equals("MIT License"));
assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/mit")));
assertTrue("The HTML URL is correct", license.getHtmlUrl().equals(new URL("http://choosealicense.com/licenses/mit/")));
}
/**
* Accesses the 'pomes/pomes' repo using {@link GitHub#getRepository(String)}
* and then calls {@link GHRepository#getLicenseContent()} and checks that certain
* properties are correct
*
* @throws IOException
*/
@Test
public void checkRepositoryLicenseContent() throws IOException {
GHRepository repo = gitHub.getRepository("pomes/pomes");
GHContent content = repo.getLicenseContent();
assertNotNull("The license content is populated", content);
assertTrue("The type is 'file'", content.getType().equals("file"));
assertTrue("The license file is 'LICENSE'", content.getName().equals("LICENSE"));
if (content.getEncoding().equals("base64")) {
String licenseText = new String(IOUtils.toByteArray(content.read()));
assertTrue("The license appears to be an Apache License", licenseText.contains("Apache License"));
} else {
fail("Expected the license to be Base64 encoded but instead it was " + content.getEncoding());
}
}
}

View File

@@ -7,12 +7,14 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.google.common.collect.Iterables;
import org.junit.Test; import org.junit.Test;
import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -20,6 +22,19 @@ import static org.mockito.Mockito.when;
* Unit test for {@link GitHub}. * Unit test for {@link GitHub}.
*/ */
public class GitHubTest { public class GitHubTest {
@Test
public void testOffline() throws Exception {
GitHub hub = GitHub.offline();
assertEquals("https://api.github.invalid/test", hub.getApiURL("/test").toString());
assertTrue(hub.isAnonymous());
try {
hub.getRateLimit();
fail("Offline instance should always fail");
} catch (IOException e) {
assertEquals("Offline", e.getMessage());
}
}
@Test @Test
public void testGitHubServerWithHttp() throws Exception { public void testGitHubServerWithHttp() throws Exception {
GitHub hub = GitHub.connectToEnterprise("http://enterprise.kohsuke.org/api/v3", "bogus","bogus"); GitHub hub = GitHub.connectToEnterprise("http://enterprise.kohsuke.org/api/v3", "bogus","bogus");
@@ -131,4 +146,13 @@ public class GitHubTest {
assertTrue(ioe.getMessage().contains("private mode enabled")); assertTrue(ioe.getMessage().contains("private mode enabled"));
} }
} }
@Test
public void listUsers() throws IOException {
GitHub hub = GitHub.connect();
for (GHUser u : Iterables.limit(hub.listUsers(),10)) {
assert u.getName()!=null;
System.out.println(u.getName());
}
}
} }

View File

@@ -0,0 +1,12 @@
package org.kohsuke.github;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Payload {
String value();
}

View File

@@ -0,0 +1,87 @@
package org.kohsuke.github;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import org.apache.commons.io.IOUtils;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/**
* @author Stephen Connolly
*/
public class PayloadRule implements TestRule {
private final String type;
private Class<?> testClass;
private String resourceName;
public PayloadRule(String type) {
this.type = type;
}
public Statement apply(final Statement base, final Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
Payload payload = description.getAnnotation(Payload.class);
resourceName = payload == null ? description.getMethodName() : payload.value();
testClass = description.getTestClass();
try {
base.evaluate();
} finally {
resourceName = null;
}
}
};
}
public InputStream asInputStream() throws FileNotFoundException {
String name = resourceName.startsWith("/")
? resourceName + type
: testClass.getSimpleName() + "/" + resourceName + type;
InputStream stream = testClass.getResourceAsStream(name);
if (stream == null) {
throw new FileNotFoundException(String.format("Resource %s from class %s", name, testClass));
}
return stream;
}
public byte[] asBytes() throws IOException {
InputStream input = asInputStream();
try {
return IOUtils.toByteArray(input);
} finally {
IOUtils.closeQuietly(input);
}
}
public String asString(Charset encoding) throws IOException {
return new String(asBytes(), encoding.name());
}
public String asString(String encoding) throws IOException {
return new String(asBytes(), encoding);
}
public String asString() throws IOException {
return new String(asBytes(), Charset.defaultCharset().name());
}
public Reader asReader() throws FileNotFoundException {
return new InputStreamReader(asInputStream(), Charset.defaultCharset());
}
public Reader asReader(String encoding) throws IOException {
return new InputStreamReader(asInputStream(), encoding);
}
public Reader asReader(Charset encoding) throws FileNotFoundException {
return new InputStreamReader(asInputStream(), encoding);
}
}

View File

@@ -7,6 +7,9 @@ import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
/** /**
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
*/ */
@@ -26,6 +29,33 @@ public class PullRequestTest extends AbstractGitHubApiTestBase {
p.comment("Some comment"); p.comment("Some comment");
} }
@Test
public void testPullRequestReviews() throws Exception {
String name = rnd.next();
GHPullRequest p = getRepository().createPullRequest(name, "stable", "master", "## test");
GHPullRequestReview draftReview = p.createReview("Some draft review", null,
GHPullRequestReviewComment.draft("Some niggle", "changelog.html", 1)
);
assertThat(draftReview.getState(), is(GHPullRequestReviewState.PENDING));
assertThat(draftReview.getBody(), is("Some draft review"));
assertThat(draftReview.getCommitId(), notNullValue());
List<GHPullRequestReview> reviews = p.listReviews().asList();
assertThat(reviews.size(), is(1));
GHPullRequestReview review = reviews.get(0);
assertThat(review.getState(), is(GHPullRequestReviewState.PENDING));
assertThat(review.getBody(), is("Some draft review"));
assertThat(review.getCommitId(), notNullValue());
review.submit("Some review comment", GHPullRequestReviewState.COMMENTED);
List<GHPullRequestReviewComment> comments = review.listReviewComments().asList();
assertEquals(1, comments.size());
GHPullRequestReviewComment comment = comments.get(0);
assertEquals("Some niggle", comment.getBody());
review = p.createReview("Some new review", null,
GHPullRequestReviewComment.draft("Some niggle", "changelog.html", 1)
);
review.delete();
}
@Test @Test
public void testPullRequestReviewComments() throws Exception { public void testPullRequestReviewComments() throws Exception {
String name = rnd.next(); String name = rnd.next();
@@ -69,6 +99,19 @@ public class PullRequestTest extends AbstractGitHubApiTestBase {
fail(); fail();
} }
@Test
public void testSquashMerge() throws Exception {
String name = rnd.next();
GHRef masterRef = getRepository().getRef("heads/master");
GHRef branchRef = getRepository().createRef("refs/heads/" + name, masterRef.getObject().getSha());
getRepository().createContent(name, name, name, name);
Thread.sleep(1000);
GHPullRequest p = getRepository().createPullRequest(name, name, "master", "## test squash");
Thread.sleep(1000);
p.merge("squash merge", null, GHPullRequest.MergeMethod.SQUASH);
branchRef.delete();
}
@Test @Test
// Requires push access to the test repo to pass // Requires push access to the test repo to pass
public void setLabels() throws Exception { public void setLabels() throws Exception {

View File

@@ -3,6 +3,7 @@ package org.kohsuke.github;
import org.junit.Test; import org.junit.Test;
import org.kohsuke.github.GHRepository.Contributor; import org.kohsuke.github.GHRepository.Contributor;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
/** /**
@@ -40,6 +41,59 @@ public class RepositoryTest extends AbstractGitHubApiTestBase {
assertTrue(kohsuke); assertTrue(kohsuke);
} }
@Test
public void getPermission() throws Exception {
kohsuke();
GHRepository r = gitHub.getRepository("github-api-test-org/test-permission");
assertEquals(GHPermissionType.ADMIN, r.getPermission("kohsuke"));
assertEquals(GHPermissionType.READ, r.getPermission("dude"));
r = gitHub.getOrganization("apache").getRepository("groovy");
try {
r.getPermission("jglick");
fail();
} catch (HttpException x) {
x.printStackTrace(); // good
assertEquals(403, x.getResponseCode());
}
if (false) {
// can't easily test this; there's no private repository visible to the test user
r = gitHub.getOrganization("cloudbees").getRepository("private-repo-not-writable-by-me");
try {
r.getPermission("jglick");
fail();
} catch (FileNotFoundException x) {
x.printStackTrace(); // good
}
}
}
@Test
public void LatestRepositoryExist() {
try {
// add the repository that have latest release
GHRelease release = gitHub.getRepository("kamontat/CheckIDNumber").getLatestRelease();
assertEquals("v3.0", release.getTagName());
} catch (IOException e) {
e.printStackTrace();
fail();
}
}
@Test
public void LatestRepositoryNotExist() {
try {
// add the repository that `NOT` have latest release
GHRelease release = gitHub.getRepository("kamontat/Java8Example").getLatestRelease();
assertNull(release);
} catch (IOException e) {
e.printStackTrace();
fail();
}
}
private GHRepository getRepository() throws IOException { private GHRepository getRepository() throws IOException {
return gitHub.getOrganization("github-api-test-org").getRepository("jenkins"); return gitHub.getOrganization("github-api-test-org").getRepository("jenkins");
} }
@@ -50,4 +104,12 @@ public class RepositoryTest extends AbstractGitHubApiTestBase {
String mainLanguage = r.getLanguage(); String mainLanguage = r.getLanguage();
assertTrue(r.listLanguages().containsKey(mainLanguage)); assertTrue(r.listLanguages().containsKey(mainLanguage));
} }
@Test // Issue #261
public void listEmptyContributors() throws IOException {
GitHub gh = GitHub.connect();
for (Contributor c : gh.getRepository("github-api-test-org/empty").listContributors()) {
System.out.println(c);
}
}
} }

View File

@@ -0,0 +1,167 @@
package org.kohsuke.github;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.IOUtils;
import org.junit.Assert;
import org.junit.Test;
import org.kohsuke.github.GHRepositoryTraffic.DailyInfo;
import org.mockito.Mockito;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;
public class RepositoryTrafficTest {
final private String login = "kohsuke", repositoryName = "github-api";
@SuppressWarnings("unchecked")
private <T extends GHRepositoryTraffic> void checkResponse(T expected, T actual){
Assert.assertEquals(expected.getCount(), actual.getCount());
Assert.assertEquals(expected.getUniques(), actual.getUniques());
List<? extends DailyInfo> expectedList = expected.getDailyInfo();
List<? extends DailyInfo> actualList = actual.getDailyInfo();
Iterator<? extends DailyInfo> expectedIt;
Iterator<? extends DailyInfo> actualIt;
Assert.assertEquals(expectedList.size(), actualList.size());
expectedIt = expectedList.iterator();
actualIt = actualList.iterator();
while(expectedIt.hasNext() && actualIt.hasNext()) {
DailyInfo expectedDailyInfo = expectedIt.next();
DailyInfo actualDailyInfo = actualIt.next();
Assert.assertEquals(expectedDailyInfo.getCount(), actualDailyInfo.getCount());
Assert.assertEquals(expectedDailyInfo.getUniques(), actualDailyInfo.getUniques());
Assert.assertEquals(expectedDailyInfo.getTimestamp(), actualDailyInfo.getTimestamp());
}
}
private <T extends GHRepositoryTraffic> void testTraffic(T expectedResult) throws IOException{
SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
ObjectMapper mapper = new ObjectMapper().setDateFormat(dateFormat);
String mockedResponse = mapper.writeValueAsString(expectedResult);
GitHub gitHub = GitHub.connect(login, null);
GitHub gitHubSpy = Mockito.spy(gitHub);
GHRepository repo = gitHubSpy.getUser(login).getRepository(repositoryName);
// accessing traffic info requires push access to the repo
// since we don't have that, let the mocking begin...
HttpConnector connectorSpy = Mockito.spy(gitHubSpy.getConnector());
Mockito.doReturn(connectorSpy).when(gitHubSpy).getConnector();
// also known as the "uc" in the Requester class
HttpURLConnection mockHttpURLConnection = Mockito.mock(HttpURLConnection.class);
// needed for Requester.setRequestMethod
Mockito.doReturn("GET").when(mockHttpURLConnection).getRequestMethod();
// this covers calls on "uc" in Requester.setupConnection and Requester.buildRequest
URL trafficURL = new URL(
"https://api.github.com/repos/"+login+"/"+repositoryName+"/traffic/" +
((expectedResult instanceof GHRepositoryViewTraffic) ? "views" : "clones")
);
Mockito.doReturn(mockHttpURLConnection).when(connectorSpy).connect(Mockito.eq(trafficURL));
// make Requester.parse work
Mockito.doReturn(200).when(mockHttpURLConnection).getResponseCode();
Mockito.doReturn("OK").when(mockHttpURLConnection).getResponseMessage();
InputStream stubInputStream = IOUtils.toInputStream(mockedResponse, "UTF-8");
Mockito.doReturn(stubInputStream).when(mockHttpURLConnection).getInputStream();
if(expectedResult instanceof GHRepositoryViewTraffic){
GHRepositoryViewTraffic views = repo.getViewTraffic();
checkResponse(expectedResult, views);
}
else if(expectedResult instanceof GHRepositoryCloneTraffic) {
GHRepositoryCloneTraffic clones = repo.getCloneTraffic();
checkResponse(expectedResult, clones);
}
}
@Test
public void testGetViews() throws IOException{
GHRepositoryViewTraffic expectedResult = new GHRepositoryViewTraffic(
21523359,
65534,
Arrays.asList(
new GHRepositoryViewTraffic.DailyInfo("2016-10-10T00:00:00Z", 3, 2),
new GHRepositoryViewTraffic.DailyInfo("2016-10-11T00:00:00Z", 9, 4),
new GHRepositoryViewTraffic.DailyInfo("2016-10-12T00:00:00Z", 27, 8),
new GHRepositoryViewTraffic.DailyInfo("2016-10-13T00:00:00Z", 81, 16),
new GHRepositoryViewTraffic.DailyInfo("2016-10-14T00:00:00Z", 243, 32),
new GHRepositoryViewTraffic.DailyInfo("2016-10-15T00:00:00Z", 729, 64),
new GHRepositoryViewTraffic.DailyInfo("2016-10-16T00:00:00Z", 2187, 128),
new GHRepositoryViewTraffic.DailyInfo("2016-10-17T00:00:00Z", 6561, 256),
new GHRepositoryViewTraffic.DailyInfo("2016-10-18T00:00:00Z", 19683, 512),
new GHRepositoryViewTraffic.DailyInfo("2016-10-19T00:00:00Z", 59049, 1024),
new GHRepositoryViewTraffic.DailyInfo("2016-10-20T00:00:00Z", 177147, 2048),
new GHRepositoryViewTraffic.DailyInfo("2016-10-21T00:00:00Z", 531441, 4096),
new GHRepositoryViewTraffic.DailyInfo("2016-10-22T00:00:00Z", 1594323, 8192),
new GHRepositoryViewTraffic.DailyInfo("2016-10-23T00:00:00Z", 4782969, 16384),
new GHRepositoryViewTraffic.DailyInfo("2016-10-24T00:00:00Z", 14348907, 32768)
)
);
testTraffic(expectedResult);
}
@Test
public void testGetClones() throws IOException{
GHRepositoryCloneTraffic expectedResult = new GHRepositoryCloneTraffic(
1500,
455,
Arrays.asList(
new GHRepositoryCloneTraffic.DailyInfo("2016-10-10T00:00:00Z", 10,3),
new GHRepositoryCloneTraffic.DailyInfo("2016-10-11T00:00:00Z", 20,6),
new GHRepositoryCloneTraffic.DailyInfo("2016-10-12T00:00:00Z", 30,5),
new GHRepositoryCloneTraffic.DailyInfo("2016-10-13T00:00:00Z", 40,7),
new GHRepositoryCloneTraffic.DailyInfo("2016-10-14T00:00:00Z", 50,11),
new GHRepositoryCloneTraffic.DailyInfo("2016-10-15T00:00:00Z", 60,12),
new GHRepositoryCloneTraffic.DailyInfo("2016-10-16T00:00:00Z", 70,19),
new GHRepositoryCloneTraffic.DailyInfo("2016-10-17T00:00:00Z", 170,111),
new GHRepositoryCloneTraffic.DailyInfo("2016-10-18T00:00:00Z", 180,70),
new GHRepositoryCloneTraffic.DailyInfo("2016-10-19T00:00:00Z", 190,10),
new GHRepositoryCloneTraffic.DailyInfo("2016-10-20T00:00:00Z", 200,18),
new GHRepositoryCloneTraffic.DailyInfo("2016-10-21T00:00:00Z", 210,8),
new GHRepositoryCloneTraffic.DailyInfo("2016-10-22T00:00:00Z", 220,168),
new GHRepositoryCloneTraffic.DailyInfo("2016-10-23T00:00:00Z", 5,2),
new GHRepositoryCloneTraffic.DailyInfo("2016-10-24T00:00:00Z", 45,5)
)
);
testTraffic(expectedResult);
}
@Test
public void testGetTrafficStatsAccessFailureDueToInsufficientPermissions() throws IOException {
String errorMsg = "Exception should be thrown, since we don't have permission to access repo traffic info.";
GitHub gitHub = GitHub.connect(login, null);
GHRepository repo = gitHub.getUser(login).getRepository(repositoryName);
try {
repo.getViewTraffic();
Assert.fail(errorMsg);
}
catch (HttpException ex){
}
try {
repo.getCloneTraffic();
Assert.fail(errorMsg);
}
catch (HttpException ex){
}
}
}

View File

@@ -0,0 +1,140 @@
{
"action": "created",
"comment": {
"url": "https://api.github.com/repos/baxterthehacker/public-repo/comments/11056394",
"html_url": "https://github.com/baxterthehacker/public-repo/commit/9049f1265b7d61be4a8904a9a27120d2064dab3b#commitcomment-11056394",
"id": 11056394,
"user": {
"login": "baxterthehacker",
"id": 6752317,
"avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/baxterthehacker",
"html_url": "https://github.com/baxterthehacker",
"followers_url": "https://api.github.com/users/baxterthehacker/followers",
"following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}",
"gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}",
"starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions",
"organizations_url": "https://api.github.com/users/baxterthehacker/orgs",
"repos_url": "https://api.github.com/users/baxterthehacker/repos",
"events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}",
"received_events_url": "https://api.github.com/users/baxterthehacker/received_events",
"type": "User",
"site_admin": false
},
"position": null,
"line": null,
"path": null,
"commit_id": "9049f1265b7d61be4a8904a9a27120d2064dab3b",
"created_at": "2015-05-05T23:40:29Z",
"updated_at": "2015-05-05T23:40:29Z",
"body": "This is a really good change! :+1:"
},
"repository": {
"id": 35129377,
"name": "public-repo",
"full_name": "baxterthehacker/public-repo",
"owner": {
"login": "baxterthehacker",
"id": 6752317,
"avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/baxterthehacker",
"html_url": "https://github.com/baxterthehacker",
"followers_url": "https://api.github.com/users/baxterthehacker/followers",
"following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}",
"gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}",
"starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions",
"organizations_url": "https://api.github.com/users/baxterthehacker/orgs",
"repos_url": "https://api.github.com/users/baxterthehacker/repos",
"events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}",
"received_events_url": "https://api.github.com/users/baxterthehacker/received_events",
"type": "User",
"site_admin": false
},
"private": false,
"html_url": "https://github.com/baxterthehacker/public-repo",
"description": "",
"fork": false,
"url": "https://api.github.com/repos/baxterthehacker/public-repo",
"forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks",
"keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams",
"hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks",
"issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}",
"events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events",
"assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}",
"branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}",
"tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags",
"blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}",
"languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages",
"stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers",
"contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors",
"subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers",
"subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription",
"commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}",
"compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges",
"archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads",
"issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}",
"pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}",
"milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}",
"notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}",
"releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}",
"created_at": "2015-05-05T23:40:12Z",
"updated_at": "2015-05-05T23:40:12Z",
"pushed_at": "2015-05-05T23:40:27Z",
"git_url": "git://github.com/baxterthehacker/public-repo.git",
"ssh_url": "git@github.com:baxterthehacker/public-repo.git",
"clone_url": "https://github.com/baxterthehacker/public-repo.git",
"svn_url": "https://github.com/baxterthehacker/public-repo",
"homepage": null,
"size": 0,
"stargazers_count": 0,
"watchers_count": 0,
"language": null,
"has_issues": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": true,
"forks_count": 0,
"mirror_url": null,
"open_issues_count": 2,
"forks": 0,
"open_issues": 2,
"watchers": 0,
"default_branch": "master"
},
"sender": {
"login": "baxterthehacker",
"id": 6752317,
"avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/baxterthehacker",
"html_url": "https://github.com/baxterthehacker",
"followers_url": "https://api.github.com/users/baxterthehacker/followers",
"following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}",
"gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}",
"starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions",
"organizations_url": "https://api.github.com/users/baxterthehacker/orgs",
"repos_url": "https://api.github.com/users/baxterthehacker/repos",
"events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}",
"received_events_url": "https://api.github.com/users/baxterthehacker/received_events",
"type": "User",
"site_admin": false
}
}

View File

@@ -0,0 +1,113 @@
{
"ref": "0.0.1",
"ref_type": "tag",
"master_branch": "master",
"description": "",
"pusher_type": "user",
"repository": {
"id": 35129377,
"name": "public-repo",
"full_name": "baxterthehacker/public-repo",
"owner": {
"login": "baxterthehacker",
"id": 6752317,
"avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/baxterthehacker",
"html_url": "https://github.com/baxterthehacker",
"followers_url": "https://api.github.com/users/baxterthehacker/followers",
"following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}",
"gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}",
"starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions",
"organizations_url": "https://api.github.com/users/baxterthehacker/orgs",
"repos_url": "https://api.github.com/users/baxterthehacker/repos",
"events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}",
"received_events_url": "https://api.github.com/users/baxterthehacker/received_events",
"type": "User",
"site_admin": false
},
"private": false,
"html_url": "https://github.com/baxterthehacker/public-repo",
"description": "",
"fork": false,
"url": "https://api.github.com/repos/baxterthehacker/public-repo",
"forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks",
"keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams",
"hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks",
"issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}",
"events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events",
"assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}",
"branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}",
"tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags",
"blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}",
"languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages",
"stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers",
"contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors",
"subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers",
"subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription",
"commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}",
"compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges",
"archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads",
"issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}",
"pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}",
"milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}",
"notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}",
"releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}",
"created_at": "2015-05-05T23:40:12Z",
"updated_at": "2015-05-05T23:40:30Z",
"pushed_at": "2015-05-05T23:40:38Z",
"git_url": "git://github.com/baxterthehacker/public-repo.git",
"ssh_url": "git@github.com:baxterthehacker/public-repo.git",
"clone_url": "https://github.com/baxterthehacker/public-repo.git",
"svn_url": "https://github.com/baxterthehacker/public-repo",
"homepage": null,
"size": 0,
"stargazers_count": 0,
"watchers_count": 0,
"language": null,
"has_issues": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": true,
"forks_count": 0,
"mirror_url": null,
"open_issues_count": 2,
"forks": 0,
"open_issues": 2,
"watchers": 0,
"default_branch": "master"
},
"sender": {
"login": "baxterthehacker",
"id": 6752317,
"avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/baxterthehacker",
"html_url": "https://github.com/baxterthehacker",
"followers_url": "https://api.github.com/users/baxterthehacker/followers",
"following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}",
"gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}",
"starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions",
"organizations_url": "https://api.github.com/users/baxterthehacker/orgs",
"repos_url": "https://api.github.com/users/baxterthehacker/repos",
"events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}",
"received_events_url": "https://api.github.com/users/baxterthehacker/received_events",
"type": "User",
"site_admin": false
}
}

View File

@@ -0,0 +1,111 @@
{
"ref": "simple-tag",
"ref_type": "tag",
"pusher_type": "user",
"repository": {
"id": 35129377,
"name": "public-repo",
"full_name": "baxterthehacker/public-repo",
"owner": {
"login": "baxterthehacker",
"id": 6752317,
"avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/baxterthehacker",
"html_url": "https://github.com/baxterthehacker",
"followers_url": "https://api.github.com/users/baxterthehacker/followers",
"following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}",
"gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}",
"starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions",
"organizations_url": "https://api.github.com/users/baxterthehacker/orgs",
"repos_url": "https://api.github.com/users/baxterthehacker/repos",
"events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}",
"received_events_url": "https://api.github.com/users/baxterthehacker/received_events",
"type": "User",
"site_admin": false
},
"private": false,
"html_url": "https://github.com/baxterthehacker/public-repo",
"description": "",
"fork": false,
"url": "https://api.github.com/repos/baxterthehacker/public-repo",
"forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks",
"keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams",
"hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks",
"issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}",
"events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events",
"assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}",
"branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}",
"tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags",
"blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}",
"languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages",
"stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers",
"contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors",
"subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers",
"subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription",
"commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}",
"compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges",
"archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads",
"issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}",
"pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}",
"milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}",
"notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}",
"releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}",
"created_at": "2015-05-05T23:40:12Z",
"updated_at": "2015-05-05T23:40:30Z",
"pushed_at": "2015-05-05T23:40:40Z",
"git_url": "git://github.com/baxterthehacker/public-repo.git",
"ssh_url": "git@github.com:baxterthehacker/public-repo.git",
"clone_url": "https://github.com/baxterthehacker/public-repo.git",
"svn_url": "https://github.com/baxterthehacker/public-repo",
"homepage": null,
"size": 0,
"stargazers_count": 0,
"watchers_count": 0,
"language": null,
"has_issues": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": true,
"forks_count": 0,
"mirror_url": null,
"open_issues_count": 2,
"forks": 0,
"open_issues": 2,
"watchers": 0,
"default_branch": "master"
},
"sender": {
"login": "baxterthehacker",
"id": 6752317,
"avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/baxterthehacker",
"html_url": "https://github.com/baxterthehacker",
"followers_url": "https://api.github.com/users/baxterthehacker/followers",
"following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}",
"gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}",
"starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions",
"organizations_url": "https://api.github.com/users/baxterthehacker/orgs",
"repos_url": "https://api.github.com/users/baxterthehacker/repos",
"events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}",
"received_events_url": "https://api.github.com/users/baxterthehacker/received_events",
"type": "User",
"site_admin": false
}
}

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