Compare commits

..

222 Commits

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

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

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

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

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

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

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

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

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

Fixes issue #149
2015-02-15 08:57:36 -08:00
Kohsuke Kawaguchi
15163ffde0 Avoid multiple concurrent population 2015-02-15 08:56:57 -08:00
Kohsuke Kawaguchi
b898284821 Mentions thread-safety and state the goal.
Most of the objects are effectively immutable, so this should be an easy goal

Fixes issue #148.
2015-02-15 08:56:29 -08:00
Kohsuke Kawaguchi
3bb7eb2e03 Added API to list contributors 2015-02-15 08:50:55 -08:00
Kohsuke Kawaguchi
11fcb9d456 Report the repository the push happened to
Fixes issue #144.
2015-02-15 08:42:32 -08:00
Kohsuke Kawaguchi
29f826448a Added a convenience method.
See: issue #134
2015-02-15 08:35:31 -08:00
Kohsuke Kawaguchi
a8cf4a7120 Added watch API support.
Fixes issue #130
2015-02-15 08:31:57 -08:00
Kohsuke Kawaguchi
60dce94a47 renamed to created RepositoryTest 2015-02-15 08:25:49 -08:00
Kohsuke Kawaguchi
c965b9cc24 follow up fix to the GHRepository.getApiTailUrl() change 2015-02-15 08:20:28 -08:00
Kohsuke Kawaguchi
762a32eb6d Added repository watch listing 2015-02-15 07:45:11 -08:00
Kohsuke Kawaguchi
541dac1aee Use getApiTailUrl for consistency 2015-02-15 07:35:02 -08:00
Kohsuke Kawaguchi
e2e2329301 Noting issue #60 that this method can return null 2015-02-15 07:16:18 -08:00
Kohsuke Kawaguchi
9afad71b0f Newly created user object resets root to null.
Fixes issue #111.
Test case from KostyaSha
2015-02-15 07:13:33 -08:00
Kohsuke Kawaguchi
7bbe0f7e8a Allow the client to explicitly control proxy
Fixes issue #109.
2015-02-15 07:02:50 -08:00
Kohsuke Kawaguchi
d90adfa98e Implemented label CRUD operations on GHRepository
Fixes issue #105
2015-02-15 06:55:35 -08:00
Kohsuke Kawaguchi
1dbcc4b776 Fixed the getReadme() method.
It was calling the wrong endpoint.
Fixed issue #99.
2015-02-15 06:31:22 -08:00
Kohsuke Kawaguchi
18696fca2d [maven-release-plugin] prepare for next development iteration 2015-02-14 10:28:55 -08:00
85 changed files with 4362 additions and 1118 deletions

40
pom.xml
View File

@@ -3,11 +3,11 @@
<parent>
<groupId>org.kohsuke</groupId>
<artifactId>pom</artifactId>
<version>12</version>
<version>14</version>
</parent>
<artifactId>github-api</artifactId>
<version>1.61</version>
<version>1.73</version>
<name>GitHub API for Java</name>
<url>http://github-api.kohsuke.org/</url>
<description>GitHub API for Java</description>
@@ -16,7 +16,7 @@
<connection>scm:git:git@github.com/kohsuke/${project.artifactId}.git</connection>
<developerConnection>scm:git:ssh://git@github.com/kohsuke/${project.artifactId}.git</developerConnection>
<url>http://${project.artifactId}.kohsuke.org/</url>
<tag>github-api-1.61</tag>
<tag>github-api-1.73</tag>
</scm>
<distributionManagement>
@@ -28,6 +28,8 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<findbugs-maven-plugin.version>3.0.2</findbugs-maven-plugin.version>
<findbugs-maven-plugin.failOnError>true</findbugs-maven-plugin.failOnError>
</properties>
<build>
@@ -44,6 +46,24 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>${findbugs-maven-plugin.version}</version>
<configuration>
<xmlOutput>true</xmlOutput>
<failOnError>${findbugs-maven-plugin.failOnError}</failOnError>
</configuration>
<executions>
<execution>
<id>run-findbugs</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
@@ -109,6 +129,12 @@
<version>1.9.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>annotations</artifactId>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<repositories>
<repository>
@@ -139,4 +165,12 @@
<distribution>repo</distribution>
</license>
</licenses>
<mailingLists>
<mailingList>
<name>User List</name>
<post>github-api@googlegroups.com</post>
<archive>https://groups.google.com/forum/#!forum/github-api</archive>
</mailingList>
</mailingLists>
</project>

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.net.URL;
import java.util.Collection;
import java.util.Date;
@@ -10,78 +11,86 @@ import java.util.List;
*
* @author janinko
* @see GitHub#createToken(Collection, String, String)
* @see http://developer.github.com/v3/oauth/#create-a-new-authorization
* @see <a href="http://developer.github.com/v3/oauth/#create-a-new-authorization">API documentation</a>
*/
public class GHAuthorization extends GHObject {
public static final String USER = "user";
public static final String USER_EMAIL = "user:email";
public static final String USER_FOLLOW = "user:follow";
public static final String PUBLIC_REPO = "public_repo";
public static final String REPO = "repo";
public static final String REPO_STATUS = "repo:status";
public static final String DELETE_REPO = "delete_repo";
public static final String NOTIFICATIONS = "notifications";
public static final String GIST = "gist";
public static final String READ_HOOK = "read:repo_hook";
public static final String WRITE_HOOK = "write:repo_hook";
public static final String AMIN_HOOK = "admin:repo_hook";
public static final String READ_ORG = "read:org";
public static final String WRITE_ORG = "write:org";
public static final String ADMIN_ORG = "admin:org";
public static final String READ_KEY = "read:public_key";
public static final String WRITE_KEY = "write:public_key";
public static final String ADMIN_KEY = "admin:public_key";
public static final String USER = "user";
public static final String USER_EMAIL = "user:email";
public static final String USER_FOLLOW = "user:follow";
public static final String PUBLIC_REPO = "public_repo";
public static final String REPO = "repo";
public static final String REPO_STATUS = "repo:status";
public static final String DELETE_REPO = "delete_repo";
public static final String NOTIFICATIONS = "notifications";
public static final String GIST = "gist";
public static final String READ_HOOK = "read:repo_hook";
public static final String WRITE_HOOK = "write:repo_hook";
public static final String AMIN_HOOK = "admin:repo_hook";
public static final String READ_ORG = "read:org";
public static final String WRITE_ORG = "write:org";
public static final String ADMIN_ORG = "admin:org";
public static final String READ_KEY = "read:public_key";
public static final String WRITE_KEY = "write:public_key";
public static final String ADMIN_KEY = "admin:public_key";
private GitHub root;
private List<String> scopes;
private String token;
private App app;
private String note;
private String note_url;
private GitHub root;
private List<String> scopes;
private String token;
private App app;
private String note;
private String note_url;
public GitHub getRoot() {
return root;
}
public List<String> getScopes() {
return scopes;
}
public List<String> getScopes() {
return scopes;
}
public String getToken(){
return token;
}
public String getToken() {
return token;
}
public URL getAppUrl(){
public URL getAppUrl() {
return GitHub.parseURL(app.url);
}
}
public String getAppName() {
return app.name;
}
public URL getApiURL(){
public String getAppName() {
return app.name;
}
@SuppressFBWarnings(value = "NM_CONFUSING",
justification = "It's a part of the library API, cannot be changed")
public URL getApiURL() {
return GitHub.parseURL(url);
}
}
public String getNote() {
return note;
}
/**
* @deprecated This object has no HTML URL.
*/
@Override
public URL getHtmlUrl() {
return null;
}
public URL getNoteUrl(){
public String getNote() {
return note;
}
public URL getNoteUrl() {
return GitHub.parseURL(note_url);
}
}
/*package*/ GHAuthorization wrap(GitHub root) {
this.root = root;
return this;
}
/*package*/ GHAuthorization wrap(GitHub root) {
this.root = root;
return this;
}
private static class App{
private String url;
private String name;
}
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD"},
justification = "JSON API")
private static class App {
private String url;
private String name;
}
}

View File

@@ -1,10 +1,14 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* A branch in a repository.
*
* @author Yusuke Kokubo
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public class GHBranch {
private GitHub root;
private GHRepository owner;
@@ -13,7 +17,10 @@ public class GHBranch {
private Commit commit;
public static class Commit {
String sha,url;
String sha;
@SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now")
String url;
}
public GitHub getRoot() {
@@ -37,10 +44,11 @@ public class GHBranch {
public String getSHA1() {
return commit.sha;
}
@Override
public String toString() {
return "Branch:" + name + " in " + owner.getUrl();
final String url = owner != null ? owner.getUrl().toString() : "unknown";
return "Branch:" + name + " in " + url;
}
/*package*/ GHBranch wrap(GHRepository repo) {

View File

@@ -1,6 +1,7 @@
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;
@@ -16,6 +17,8 @@ import java.util.List;
* @see GHRepository#getCommit(String)
* @see GHCommitComment#getCommit()
*/
@SuppressFBWarnings(value = {"NP_UNWRITTEN_FIELD", "UWF_UNWRITTEN_FIELD"},
justification = "JSON API")
public class GHCommit {
private GHRepository owner;
@@ -24,6 +27,8 @@ public class GHCommit {
/**
* Short summary of this commit.
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD", "UWF_UNWRITTEN_FIELD"}, justification = "JSON API")
public static class ShortInfo {
private GHAuthor author;
private GHAuthor committer;
@@ -32,26 +37,26 @@ public class GHCommit {
private int comment_count;
@WithBridgeMethods(value=GHAuthor.class,castRequired=true)
public GitUser getAuthor() {
return author;
}
@WithBridgeMethods(value = GHAuthor.class, castRequired = true)
public GitUser getAuthor() {
return author;
}
@WithBridgeMethods(value=GHAuthor.class,castRequired=true)
public GitUser getCommitter() {
return committer;
}
@WithBridgeMethods(value = GHAuthor.class, castRequired = true)
public GitUser getCommitter() {
return committer;
}
/**
* Commit message.
*/
public String getMessage() {
return message;
}
public String getMessage() {
return message;
}
public int getCommentCount() {
return comment_count;
}
public int getCommentCount() {
return comment_count;
}
}
/**
@@ -67,10 +72,13 @@ public class GHCommit {
/**
* A file that was modified.
*/
@SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD",
justification = "It's being initilized by JSON deserialization")
public static class File {
String status;
int changes,additions,deletions;
String raw_url, blob_url, filename, sha, patch;
String raw_url, blob_url, sha, patch;
String filename, previous_filename;
/**
* Number of lines added + removed.
@@ -94,19 +102,28 @@ public class GHCommit {
}
/**
* "modified", "added", or "deleted"
* "modified", "added", or "removed"
*/
public String getStatus() {
return status;
}
/**
* Just the base name and the extension without any directory name.
* Full path in the repository.
*/
@SuppressFBWarnings(value = "NM_CONFUSING",
justification = "It's a part of the library's API and cannot be renamed")
public String getFileName() {
return filename;
}
/**
* Previous path, in case file has moved.
*/
public String getPreviousFilename() {
return previous_filename;
}
/**
* The actual change.
*/
@@ -139,28 +156,34 @@ public class GHCommit {
}
public static class Parent {
String url,sha;
@SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now")
String url;
String sha;
}
static class User {
// TODO: what if someone who doesn't have an account on GitHub makes a commit?
String url,avatar_url,login,gravatar_id;
@SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now")
String url,avatar_url,gravatar_id;
@SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now")
int id;
String login;
}
String url,sha;
String url,html_url,sha;
List<File> files;
Stats stats;
List<Parent> parents;
User author,committer;
public ShortInfo getCommitShortInfo() {
return commit;
}
/**
public ShortInfo getCommitShortInfo() throws IOException {
populate();
return commit;
}
/**
* The repository that contains the commit.
*/
public GHRepository getOwner() {
@@ -170,24 +193,34 @@ public class GHCommit {
/**
* Number of lines added + removed.
*/
public int getLinesChanged() {
public int getLinesChanged() throws IOException {
populate();
return stats.total;
}
/**
* Number of lines added.
*/
public int getLinesAdded() {
public int getLinesAdded() throws IOException {
populate();
return stats.additions;
}
/**
* Number of lines removed.
*/
public int getLinesDeleted() {
public int getLinesDeleted() throws IOException {
populate();
return stats.deletions;
}
/**
* URL of this commit like "https://github.com/kohsuke/sandbox-ant/commit/8ae38db0ea5837313ab5f39d43a6f73de3bd9000"
*/
public URL getHtmlUrl() {
return GitHub.parseURL(html_url);
}
/**
* [0-9a-f]{40} SHA1 checksum.
*/
@@ -201,7 +234,8 @@ public class GHCommit {
* @return
* Can be empty but never null.
*/
public List<File> getFiles() {
public List<File> getFiles() throws IOException {
populate();
return files!=null ? Collections.unmodifiableList(files) : Collections.<File>emptyList();
}
@@ -251,8 +285,8 @@ public class GHCommit {
*/
public PagedIterable<GHCommitComment> listComments() {
return new PagedIterable<GHCommitComment>() {
public PagedIterator<GHCommitComment> iterator() {
return new PagedIterator<GHCommitComment>(owner.root.retrieve().asIterator(String.format("/repos/%s/%s/commits/%s/comments", owner.getOwnerName(), owner.getName(), sha), GHCommitComment[].class)) {
public PagedIterator<GHCommitComment> _iterator(int pageSize) {
return new PagedIterator<GHCommitComment>(owner.root.retrieve().asIterator(String.format("/repos/%s/%s/commits/%s/comments", owner.getOwnerName(), owner.getName(), sha), GHCommitComment[].class, pageSize)) {
@Override
protected void wrapUp(GHCommitComment[] page) {
for (GHCommitComment c : page)
@@ -279,7 +313,7 @@ public class GHCommit {
}
public GHCommitComment createComment(String body) throws IOException {
return createComment(body,null,null,null);
return createComment(body, null, null, null);
}
/**
@@ -296,6 +330,14 @@ public class GHCommit {
return owner.getLastCommitStatus(sha);
}
/**
* Some of the fields are not always filled in when this object is retrieved as a part of another API call.
*/
void populate() throws IOException {
if (files==null && stats==null)
owner.root.retrieve().to(owner.getApiTailUrl("commits/" + sha), this);
}
GHCommit wrapUp(GHRepository owner) {
this.owner = owner;
return this;

View File

@@ -1,5 +1,6 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.net.URL;
import java.util.Date;
@@ -12,6 +13,8 @@ import java.util.Date;
* @see GHCommit#listComments()
* @see GHCommit#createComment(String, String, Integer, Integer)
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public class GHCommitComment extends GHObject {
private GHRepository owner;
@@ -22,8 +25,12 @@ public class GHCommitComment extends GHObject {
static class User {
// TODO: what if someone who doesn't have an account on GitHub makes a commit?
String url,avatar_url,login,gravatar_id;
@SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now")
String url,avatar_url,gravatar_id;
@SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now")
int id;
String login;
}
public GHRepository getOwner() {
@@ -83,7 +90,7 @@ public class GHCommitComment extends GHObject {
* Updates the body of the commit message.
*/
public void update(String body) throws IOException {
GHCommitComment r = new Requester(owner.root)
new Requester(owner.root)
.with("body", body)
.method("PATCH").to(getApiTail(), GHCommitComment.class);
this.body = body;

View File

@@ -23,6 +23,8 @@
*/
package org.kohsuke.github;
import java.io.IOException;
/**
* Identifies a commit in {@link GHPullRequest}.
*
@@ -69,6 +71,13 @@ public class GHCommitPointer {
return label;
}
/**
* Obtains the commit that this pointer is referring to.
*/
public GHCommit getCommit() throws IOException {
return getRepository().getCommit(getSha());
}
void wrapUp(GitHub root) {
if (user!=null) user.root = root;
if (repo!=null) repo.wrap(root);

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,11 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.io.InputStream;
import javax.xml.bind.DatatypeConverter;
@@ -14,7 +17,13 @@ import javax.xml.bind.DatatypeConverter;
*/
@SuppressWarnings({"UnusedDeclaration"})
public class GHContent {
private GHRepository owner;
/*
In normal use of this class, repository field is set via wrap(),
but in the code search API, there's a nested 'repository' field that gets populated from JSON.
*/
private GHRepository repository;
private GitHub root;
private String type;
private String encoding;
@@ -26,9 +35,10 @@ public class GHContent {
private String url; // this is the API url
private String git_url; // this is the Blob url
private String html_url; // this is the UI
private String download_url;
public GHRepository getOwner() {
return owner;
return repository;
}
public String getType() {
@@ -58,35 +68,35 @@ public class GHContent {
/**
* Retrieve the decoded content that is stored at this location.
*
* <p>
* Due to the nature of GitHub's API, you're not guaranteed that
* the content will already be populated, so this may trigger
* network activity, and can throw an IOException.
**/
*
* @deprecated
* Use {@link #read()}
*/
@SuppressFBWarnings("DM_DEFAULT_ENCODING")
public String getContent() throws IOException {
return new String(DatatypeConverter.parseBase64Binary(getEncodedContent()));
}
/**
* Retrieve the raw content that is stored at this location.
* Retrieve the base64-encoded content that is stored at this location.
*
* <p>
* Due to the nature of GitHub's API, you're not guaranteed that
* the content will already be populated, so this may trigger
* network activity, and can throw an IOException.
**/
*
* @deprecated
* Use {@link #read()}
*/
public String getEncodedContent() throws IOException {
if (content != null)
if (content!=null)
return content;
GHContent retrievedContent = owner.getFileContent(path);
this.size = retrievedContent.size;
this.sha = retrievedContent.sha;
this.content = retrievedContent.content;
this.url = retrievedContent.url;
this.git_url = retrievedContent.git_url;
this.html_url = retrievedContent.html_url;
return content;
else
return Base64.encodeBase64String(IOUtils.toByteArray(read()));
}
public String getUrl() {
@@ -101,6 +111,21 @@ public class GHContent {
return html_url;
}
/**
* Retrieves the actual content stored here.
*/
public InputStream read() throws IOException {
return new Requester(root).asStream(getDownloadUrl());
}
/**
* URL to retrieve the raw content of the file. Null if this is a directory.
*/
public String getDownloadUrl() throws IOException {
populate();
return download_url;
}
public boolean isFile() {
return "file".equals(type);
}
@@ -109,6 +134,16 @@ public class GHContent {
return "dir".equals(type);
}
/**
* Fully populate the data by retrieving missing data.
*
* Depending on the original API call where this object is created, it may not contain everything.
*/
protected synchronized void populate() throws IOException {
if (download_url!=null) return; // already populated
root.retrieve().to(url, this);
}
/**
* List immediate children of this directory.
*/
@@ -117,21 +152,23 @@ public class GHContent {
throw new IllegalStateException(path+" is not a directory");
return new PagedIterable<GHContent>() {
public PagedIterator<GHContent> iterator() {
return new PagedIterator<GHContent>(owner.root.retrieve().asIterator(url, GHContent[].class)) {
public PagedIterator<GHContent> _iterator(int pageSize) {
return new PagedIterator<GHContent>(root.retrieve().asIterator(url, GHContent[].class, pageSize)) {
@Override
protected void wrapUp(GHContent[] page) {
GHContent.wrap(page,owner);
GHContent.wrap(page, repository);
}
};
}
};
}
@SuppressFBWarnings("DM_DEFAULT_ENCODING")
public GHContentUpdateResponse update(String newContent, String commitMessage) throws IOException {
return update(newContent.getBytes(), commitMessage, null);
}
@SuppressFBWarnings("DM_DEFAULT_ENCODING")
public GHContentUpdateResponse update(String newContent, String commitMessage, String branch) throws IOException {
return update(newContent.getBytes(), commitMessage, branch);
}
@@ -143,7 +180,7 @@ public class GHContent {
public GHContentUpdateResponse update(byte[] newContentBytes, String commitMessage, String branch) throws IOException {
String encodedContent = DatatypeConverter.printBase64Binary(newContentBytes);
Requester requester = new Requester(owner.root)
Requester requester = new Requester(root)
.with("path", path)
.with("message", commitMessage)
.with("sha", sha)
@@ -156,8 +193,8 @@ public class GHContent {
GHContentUpdateResponse response = requester.to(getApiRoute(), GHContentUpdateResponse.class);
response.getContent().wrap(owner);
response.getCommit().wrapUp(owner);
response.getContent().wrap(repository);
response.getCommit().wrapUp(repository);
this.content = encodedContent;
return response;
@@ -168,7 +205,7 @@ public class GHContent {
}
public GHContentUpdateResponse delete(String commitMessage, String branch) throws IOException {
Requester requester = new Requester(owner.root)
Requester requester = new Requester(root)
.with("path", path)
.with("message", commitMessage)
.with("sha", sha)
@@ -180,18 +217,26 @@ public class GHContent {
GHContentUpdateResponse response = requester.to(getApiRoute(), GHContentUpdateResponse.class);
response.getCommit().wrapUp(owner);
response.getCommit().wrapUp(repository);
return response;
}
private String getApiRoute() {
return "/repos/" + owner.getOwnerName() + "/" + owner.getName() + "/contents/" + path;
return "/repos/" + repository.getOwnerName() + "/" + repository.getName() + "/contents/" + path;
}
GHContent wrap(GHRepository owner) {
this.owner = owner;
this.repository = owner;
this.root = owner.root;
return this;
}
GHContent wrap(GitHub root) {
this.root = root;
if (repository!=null)
repository.wrap(root);
return this;
}
public static GHContent[] wrap(GHContent[] contents, GHRepository repository) {
for (GHContent unwrappedContent : contents) {

View File

@@ -0,0 +1,74 @@
package org.kohsuke.github;
/**
* Search code for {@link GHContent}.
*
* @author Kohsuke Kawaguchi
* @see GitHub#searchContent()
*/
public class GHContentSearchBuilder extends GHSearchBuilder<GHContent> {
/*package*/ GHContentSearchBuilder(GitHub root) {
super(root,ContentSearchResult.class);
}
/**
* Search terms.
*/
public GHContentSearchBuilder q(String term) {
super.q(term);
return this;
}
public GHContentSearchBuilder in(String v) {
return q("in:"+v);
}
public GHContentSearchBuilder language(String v) {
return q("language:"+v);
}
public GHContentSearchBuilder fork(String v) {
return q("fork:"+v);
}
public GHContentSearchBuilder size(String v) {
return q("size:"+v);
}
public GHContentSearchBuilder path(String v) {
return q("path:"+v);
}
public GHContentSearchBuilder filename(String v) {
return q("filename:"+v);
}
public GHContentSearchBuilder extension(String v) {
return q("extension:"+v);
}
public GHContentSearchBuilder user(String v) {
return q("user:"+v);
}
public GHContentSearchBuilder repo(String v) {
return q("repo:"+v);
}
private static class ContentSearchResult extends SearchResult<GHContent> {
private GHContent[] items;
@Override
/*package*/ GHContent[] getItems(GitHub root) {
for (GHContent item : items)
item.wrap(root);
return items;
}
}
@Override
protected String getApiUrl() {
return "/search/code";
}
}

View File

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

View File

@@ -9,7 +9,7 @@ public class GHDeployKey {
protected String url, key, title;
protected boolean verified;
protected int id;
private GHRepository owner;
private GHRepository owner;
public int getId() {
return id;
@@ -31,10 +31,10 @@ public class GHDeployKey {
return verified;
}
public GHDeployKey wrap(GHRepository repo) {
this.owner = repo;
return this;
}
public GHDeployKey wrap(GHRepository repo) {
this.owner = repo;
return this;
}
public String toString() {
return new ToStringBuilder(this).append("title",title).append("id",id).append("key",key).toString();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,7 @@
package org.kohsuke.github;
import java.util.Locale;
/**
* Hook event type.
*
@@ -23,12 +25,28 @@ public enum GHEvent {
ISSUE_COMMENT,
ISSUES,
MEMBER,
PAGE_BUILD,
PUBLIC,
PULL_REQUEST,
PULL_REQUEST_REVIEW_COMMENT,
PUSH,
RELEASE,
REPOSITORY, // only valid for org hooks
STATUS,
TEAM_ADD,
WATCH
WATCH,
PING,
/**
* Special event type that means "every possible event"
*/
ALL;
/**
* Returns GitHub's internal representation of this event.
*/
String symbol() {
if (this==ALL) return "*";
return name().toLowerCase(Locale.ENGLISH);
}
}

View File

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

View File

@@ -1,5 +1,6 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.Reader;
import java.util.List;
@@ -25,6 +26,8 @@ public abstract class GHEventPayload {
*
* @see <a href="http://developer.github.com/v3/activity/events/types/#pullrequestevent">authoritative source</a>
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public static class PullRequest extends GHEventPayload {
private String action;
private int number;
@@ -67,12 +70,15 @@ public abstract class GHEventPayload {
*
* @see <a href="http://developer.github.com/v3/activity/events/types/#issuecommentevent">authoritative source</a>
*/
@SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" },
justification = "Constructed by JSON deserialization")
public static class IssueComment extends GHEventPayload {
private String action;
private GHIssueComment comment;
private GHIssue issue;
private GHRepository repository;
@SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization")
public String getAction() {
return action;
}
@@ -104,8 +110,12 @@ public abstract class GHEventPayload {
@Override
void wrapUp(GitHub root) {
super.wrapUp(root);
repository.wrap(root);
issue.wrap(repository);
if (repository != null) {
repository.wrap(root);
issue.wrap(repository);
} else {
issue.wrap(root);
}
comment.wrapUp(issue);
}
}
@@ -120,6 +130,7 @@ public abstract class GHEventPayload {
private String ref;
private int size;
private List<PushCommit> commits;
private GHRepository repository;
/**
* The SHA of the HEAD commit on the repository
@@ -158,6 +169,17 @@ public abstract class GHEventPayload {
return commits;
}
public GHRepository getRepository() {
return repository;
}
@Override
void wrapUp(GitHub root) {
super.wrapUp(root);
if (repository!=null)
repository.wrap(root);
}
/**
* Commit in a push
*/

View File

@@ -3,6 +3,7 @@ package org.kohsuke.github;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -15,6 +16,7 @@ import java.util.Map.Entry;
* @see GHUser#listGists()
* @see GitHub#getGist(String)
* @see GitHub#createGist()
* @see <a href="https://developer.github.com/v3/gists/">documentation</a>
*/
public class GHGist extends GHObject {
/*package almost final*/ GHUser owner;
@@ -59,8 +61,8 @@ public class GHGist extends GHObject {
return git_push_url;
}
public String getHtmlUrl() {
return html_url;
public URL getHtmlUrl() {
return GitHub.parseURL(html_url);
}
public boolean isPublic() {
@@ -138,8 +140,8 @@ public class GHGist extends GHObject {
public PagedIterable<GHGist> listForks() {
return new PagedIterable<GHGist>() {
public PagedIterator<GHGist> iterator() {
return new PagedIterator<GHGist>(root.retrieve().asIterator(getApiTailUrl("forks"), GHGist[].class)) {
public PagedIterator<GHGist> _iterator(int pageSize) {
return new PagedIterator<GHGist>(root.retrieve().asIterator(getApiTailUrl("forks"), GHGist[].class, pageSize)) {
@Override
protected void wrapUp(GHGist[] page) {
try {

View File

@@ -1,6 +1,8 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
@@ -10,30 +12,24 @@ import java.util.Map;
/**
* @author Kohsuke Kawaguchi
*/
public class GHHook extends GHObject {
/**
* Repository that the hook belongs to.
*/
/*package*/ transient GHRepository repository;
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public abstract class GHHook extends GHObject {
String name;
List<String> events;
boolean active;
Map<String,String> config;
/*package*/ GHHook wrap(GHRepository owner) {
this.repository = owner;
return this;
}
public String getName() {
return name;
}
public EnumSet<GHEvent> getEvents() {
EnumSet<GHEvent> s = EnumSet.noneOf(GHEvent.class);
for (String e : events)
s.add(Enum.valueOf(GHEvent.class,e.toUpperCase(Locale.ENGLISH)));
for (String e : events) {
if (e.equals("*")) s.add(GHEvent.ALL);
else s.add(Enum.valueOf(GHEvent.class, e.toUpperCase(Locale.ENGLISH)));
}
return s;
}
@@ -49,6 +45,18 @@ public class GHHook extends GHObject {
* Deletes this hook.
*/
public void delete() throws IOException {
new Requester(repository.root).method("DELETE").to(String.format("/repos/%s/%s/hooks/%d", repository.getOwnerName(), repository.getName(), id));
new Requester(getRoot()).method("DELETE").to(getApiRoute());
}
/**
* @deprecated This object has no HTML URL.
*/
@Override
public URL getHtmlUrl() {
return null;
}
abstract GitHub getRoot();
abstract String getApiRoute();
}

View File

@@ -0,0 +1,131 @@
package org.kohsuke.github;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* Utility class for creating and retrieving webhooks; removes duplication between GHOrganization and GHRepository
* functionality
*/
class GHHooks {
static abstract class Context {
private final GitHub root;
private Context(GitHub root) {
this.root = root;
}
public List<GHHook> getHooks() throws IOException {
GHHook [] hookArray = root.retrieve().to(collection(),collectionClass()); // jdk/eclipse bug requires this to be on separate line
List<GHHook> list = new ArrayList<GHHook>(Arrays.asList(hookArray));
for (GHHook h : list)
wrap(h);
return list;
}
public GHHook getHook(int id) throws IOException {
GHHook hook = root.retrieve().to(collection() + "/" + id, clazz());
return wrap(hook);
}
public GHHook createHook(String name, Map<String, String> config, Collection<GHEvent> events, boolean active) throws IOException {
List<String> ea = null;
if (events!=null) {
ea = new ArrayList<String>();
for (GHEvent e : events)
ea.add(e.symbol());
}
GHHook hook = new Requester(root)
.with("name", name)
.with("active", active)
._with("config", config)
._with("events", ea)
.to(collection(), clazz());
return wrap(hook);
}
abstract String collection();
abstract Class<? extends GHHook[]> collectionClass();
abstract Class<? extends GHHook> clazz();
abstract GHHook wrap(GHHook hook);
}
private static class RepoContext extends Context {
private final GHRepository repository;
private final GHUser owner;
private RepoContext(GHRepository repository, GHUser owner) {
super(repository.root);
this.repository = repository;
this.owner = owner;
}
@Override
String collection() {
return String.format("/repos/%s/%s/hooks", owner.getLogin(), repository.getName());
}
@Override
Class<? extends GHHook[]> collectionClass() {
return GHRepoHook[].class;
}
@Override
Class<? extends GHHook> clazz() {
return GHRepoHook.class;
}
@Override
GHHook wrap(GHHook hook) {
return ((GHRepoHook)hook).wrap(repository);
}
}
private static class OrgContext extends Context {
private final GHOrganization organization;
private OrgContext(GHOrganization organization) {
super(organization.root);
this.organization = organization;
}
@Override
String collection() {
return String.format("/orgs/%s/hooks", organization.getLogin());
}
@Override
Class<? extends GHHook[]> collectionClass() {
return GHOrgHook[].class;
}
@Override
Class<? extends GHHook> clazz() {
return GHOrgHook.class;
}
@Override
GHHook wrap(GHHook hook) {
return ((GHOrgHook)hook).wrap(organization);
}
}
static Context repoContext(GHRepository repository, GHUser owner) {
return new RepoContext(repository, owner);
}
static Context orgContext(GHOrganization organization) {
return new OrgContext(organization);
}
}

View File

@@ -24,6 +24,9 @@
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.Collection;
@@ -44,14 +47,15 @@ import java.util.Locale;
public class GHIssue extends GHObject {
GitHub root;
GHRepository owner;
// API v3
// API v3
protected GHUser assignee;
protected String state;
protected int number;
protected String closed_at;
protected int comments;
protected String body;
// for backward compatibility with < 1.63, this collection needs to hold instances of Label, not GHLabel
protected List<Label> labels;
protected GHUser user;
protected String title, html_url;
@@ -59,22 +63,10 @@ public class GHIssue extends GHObject {
protected GHMilestone milestone;
protected GHUser closed_by;
public static class Label {
private String url;
private String name;
private String color;
public String getUrl() {
return url;
}
public String getName() {
return name;
}
public String getColor() {
return color;
}
/**
* @deprecated use {@link GHLabel}
*/
public static class Label extends GHLabel {
}
/*package*/ GHIssue wrap(GHRepository owner) {
@@ -85,9 +77,9 @@ public class GHIssue extends GHObject {
/*package*/ GHIssue wrap(GitHub root) {
this.root = root;
if(assignee != null) assignee.wrapUp(root);
if(user != null) user.wrapUp(root);
if(closed_by != null) closed_by.wrapUp(root);
if(assignee != null) assignee.wrapUp(root);
if(user != null) user.wrapUp(root);
if(closed_by != null) closed_by.wrapUp(root);
return this;
}
@@ -134,26 +126,31 @@ public class GHIssue extends GHObject {
return Enum.valueOf(GHIssueState.class, state.toUpperCase(Locale.ENGLISH));
}
public Collection<Label> getLabels() throws IOException {
public Collection<GHLabel> getLabels() throws IOException {
if(labels == null){
return Collections.EMPTY_LIST;
return Collections.emptyList();
}
return Collections.unmodifiableList(labels);
return Collections.<GHLabel>unmodifiableList(labels);
}
public Date getClosedAt() {
return GitHub.parseDate(closed_at);
}
public URL getApiURL(){
public URL getApiURL(){
return GitHub.parseURL(url);
}
}
/**
* Updates the issue by adding a comment.
*
* @return
* Newly posted comment.
*/
public void comment(String message) throws IOException {
new Requester(root).with("body",message).to(getIssuesApiRoute() + "/comments");
@WithBridgeMethods(void.class)
public GHIssueComment comment(String message) throws IOException {
GHIssueComment r = new Requester(root).with("body",message).to(getIssuesApiRoute() + "/comments", GHIssueComment.class);
return r.wrapUp(this);
}
private void edit(String key, Object value) throws IOException {
@@ -187,7 +184,7 @@ public class GHIssue extends GHObject {
}
public void assignTo(GHUser user) throws IOException {
editIssue("assignee",user.getLogin());
editIssue("assignee", user.getLogin());
}
public void setLabels(String... labels) throws IOException {
@@ -196,20 +193,20 @@ public class GHIssue extends GHObject {
/**
* Obtains all the comments associated with this issue.
*
* @see #listComments()
*
* @see #listComments()
*/
public List<GHIssueComment> getComments() throws IOException {
return listComments().asList();
}
/**
* Obtains all the comments associated with this issue.
*/
public List<GHIssueComment> getComments() throws IOException {
return listComments().asList();
}
/**
* Obtains all the comments associated with this issue.
*/
public PagedIterable<GHIssueComment> listComments() throws IOException {
return new PagedIterable<GHIssueComment>() {
public PagedIterator<GHIssueComment> iterator() {
return new PagedIterator<GHIssueComment>(root.retrieve().asIterator(getIssuesApiRoute() + "/comments", GHIssueComment[].class)) {
public PagedIterator<GHIssueComment> _iterator(int pageSize) {
return new PagedIterator<GHIssueComment>(root.retrieve().asIterator(getIssuesApiRoute() + "/comments", GHIssueComment[].class, pageSize)) {
protected void wrapUp(GHIssueComment[] page) {
for (GHIssueComment c : page)
c.wrapUp(GHIssue.this);
@@ -227,50 +224,67 @@ public class GHIssue extends GHObject {
return "/repos/"+owner.getOwnerName()+"/"+owner.getName()+"/issues/"+number;
}
public GHUser getAssignee() {
return assignee;
}
public GHUser getAssignee() {
return assignee;
}
/**
* User who submitted the issue.
*/
public GHUser getUser() {
public GHUser getUser() {
return user;
}
public GHUser getClosedBy() {
if(!"closed".equals(state)) return null;
if(closed_by != null) return closed_by;
//TODO closed_by = owner.getIssue(number).getClosed_by();
return closed_by;
}
public int getCommentsCount(){
return comments;
}
}
public PullRequest getPullRequest() {
return pull_request;
}
/**
* Reports who has closed the issue.
*
* <p>
* Note that GitHub doesn't always seem to report this information
* even for an issue that's already closed. See
* https://github.com/kohsuke/github-api/issues/60.
*/
public GHUser getClosedBy() {
if(!"closed".equals(state)) return null;
if(closed_by != null) return closed_by;
//TODO closed_by = owner.getIssue(number).getClosed_by();
return closed_by;
}
public int getCommentsCount(){
return comments;
}
public GHMilestone getMilestone() {
return milestone;
}
/**
* Returns non-null if this issue is a shadow of a pull request.
*/
public PullRequest getPullRequest() {
return pull_request;
}
public static class PullRequest{
private String diff_url, patch_url, html_url;
public URL getDiffUrl() {
return GitHub.parseURL(diff_url);
}
public URL getPatchUrl() {
return GitHub.parseURL(patch_url);
}
public URL getUrl() {
return GitHub.parseURL(html_url);
}
}
public boolean isPullRequest() {
return pull_request!=null;
}
public GHMilestone getMilestone() {
return milestone;
}
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD"},
justification = "JSON API")
public static class PullRequest{
private String diff_url, patch_url, html_url;
public URL getDiffUrl() {
return GitHub.parseURL(diff_url);
}
public URL getPatchUrl() {
return GitHub.parseURL(patch_url);
}
public URL getUrl() {
return GitHub.parseURL(html_url);
}
}
}

View File

@@ -25,7 +25,6 @@ package org.kohsuke.github;
import java.io.IOException;
import java.net.URL;
import java.util.Date;
/**
* Comment to the issue
@@ -71,4 +70,31 @@ public class GHIssueComment extends GHObject {
public GHUser getUser() throws IOException {
return owner.root.getUser(user.getLogin());
}
/**
* @deprecated This object has no HTML URL.
*/
@Override
public URL getHtmlUrl() {
return null;
}
/**
* Updates the body of the issue comment.
*/
public void update(String body) throws IOException {
new Requester(owner.root).with("body", body).method("PATCH").to(getApiRoute(), GHIssueComment.class);
this.body = body;
}
/**
* Deletes this issue comment.
*/
public void delete() throws IOException {
new Requester(owner.root).method("DELETE").to(getApiRoute());
}
private String getApiRoute() {
return "/repos/"+owner.getRepository().getOwnerName()+"/"+owner.getRepository().getName()+"/issues/comments/" + id;
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,37 @@
package org.kohsuke.github;
import java.io.IOException;
/**
* @author Kohsuke Kawaguchi
* @see GHIssue#getLabels()
* @see GHRepository#listLabels()
*/
public class GHLabel {
private String url, name, color;
private GHRepository repo;
public String getUrl() {
return url;
}
public String getName() {
return name;
}
/**
* Color code without leading '#', such as 'f29513'
*/
public String getColor() {
return color;
}
/*package*/ GHLabel wrapUp(GHRepository repo) {
this.repo = repo;
return this;
}
public void delete() throws IOException {
repo.root.retrieve().method("DELETE").to(url);
}
}

View File

@@ -1,6 +1,7 @@
package org.kohsuke.github;
import java.io.IOException;
import java.net.URL;
import java.util.Date;
import java.util.Locale;
@@ -11,52 +12,64 @@ import java.util.Locale;
*/
public class GHMilestone extends GHObject {
GitHub root;
GHRepository owner;
GHRepository owner;
GHUser creator;
private String state, due_on, title, description;
private int closed_issues, open_issues, number;
GHUser creator;
private String state, due_on, title, description, html_url;
private int closed_issues, open_issues, number;
protected String closed_at;
public GitHub getRoot() {
return root;
}
public GHRepository getOwner() {
return owner;
}
public GHUser getCreator() {
return creator;
}
public Date getDueOn() {
if (due_on == null) return null;
return GitHub.parseDate(due_on);
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public int getClosedIssues() {
return closed_issues;
}
public int getOpenIssues() {
return open_issues;
}
public int getNumber() {
return number;
}
public GHMilestoneState getState() {
return Enum.valueOf(GHMilestoneState.class, state.toUpperCase(Locale.ENGLISH));
}
public GitHub getRoot() {
return root;
}
public GHRepository getOwner() {
return owner;
}
public GHUser getCreator() {
return creator;
}
public Date getDueOn() {
if (due_on == null) return null;
return GitHub.parseDate(due_on);
}
/**
* When was this milestone closed?
*/
public Date getClosedAt() throws IOException {
return GitHub.parseDate(closed_at);
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public int getClosedIssues() {
return closed_issues;
}
public int getOpenIssues() {
return open_issues;
}
public int getNumber() {
return number;
}
public URL getHtmlUrl() {
return GitHub.parseURL(html_url);
}
public GHMilestoneState getState() {
return Enum.valueOf(GHMilestoneState.class, state.toUpperCase(Locale.ENGLISH));
}
/**
* Closes this issue.
@@ -73,9 +86,9 @@ public class GHMilestone extends GHObject {
return "/repos/"+owner.getOwnerName()+"/"+owner.getName()+"/milestones/"+number;
}
public GHMilestone wrap(GHRepository repo) {
this.owner = repo;
this.root = repo.root;
return this;
}
public GHMilestone wrap(GHRepository repo) {
this.owner = repo;
this.root = repo.root;
return this;
}
}

View File

@@ -5,8 +5,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
@@ -17,6 +17,33 @@ import java.util.TreeMap;
* @author Kohsuke Kawaguchi
*/
public class GHMyself extends GHUser {
/**
* Type of repositories returned during listing.
*/
public enum RepositoryListFilter {
/**
* All public and private repositories that current user has access or collaborates to
*/
ALL,
/**
* Public and private repositories owned by current user
*/
OWNER,
/**
* Public repositories that current user has access or collaborates to
*/
PUBLIC,
/**
* Private repositories that current user has access or collaborates to
*/
PRIVATE,
/**
* Public and private repositories that current user is a member
*/
MEMBER;
}
/**
* @deprecated
* Use {@link #getEmails2()}
@@ -109,16 +136,30 @@ public class GHMyself extends GHUser {
}
/**
* Lists up all the repositories this user owns (public and private) using the specified page size.
* List repositories that are accessible to the authenticated user (public and private) using the specified page size.
*
* This includes repositories owned by the authenticated user, repositories that belong to other users
* where the authenticated user is a collaborator, and other organizations' repositories that the authenticated
* user has access to through an organization membership.
*
* @param pageSize size for each page of items returned by GitHub. Maximum page size is 100.
*
* Unlike {@link #getRepositories()}, this does not wait until all the repositories are returned.
*/
public PagedIterable<GHRepository> listRepositories(final int pageSize) {
return listRepositories(pageSize, RepositoryListFilter.ALL);
}
/**
* List repositories of a certain type that are accessible by current authenticated user using the specified page size.
*
* @param pageSize size for each page of items returned by GitHub. Maximum page size is 100.
* @param repoType type of repository returned in the listing
*/
public PagedIterable<GHRepository> listRepositories(final int pageSize, final RepositoryListFilter repoType) {
return new PagedIterable<GHRepository>() {
public PagedIterator<GHRepository> iterator() {
return new PagedIterator<GHRepository>(root.retrieve().asIterator("/user/repos?per_page=" + pageSize, GHRepository[].class)) {
public PagedIterator<GHRepository> _iterator(int pageSize) {
return new PagedIterator<GHRepository>(root.retrieve().with("type",repoType).asIterator("/user/repos", GHRepository[].class, pageSize)) {
@Override
protected void wrapUp(GHRepository[] page) {
for (GHRepository c : page)
@@ -126,7 +167,7 @@ public class GHMyself extends GHUser {
}
};
}
};
}.withPageSize(pageSize);
}
/**

View File

@@ -0,0 +1,208 @@
package org.kohsuke.github;
import java.io.IOException;
import java.util.Date;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* Listens to GitHub notification stream.
*
* <p>
* This class supports two modes of retrieving notifications that can
* be controlled via {@link #nonBlocking(boolean)}.
*
* <p>
* In the blocking mode, which is the default, iterator will be infinite.
* The call to {@link Iterator#next()} will block until a new notification
* arrives. This is useful for application that runs perpetually and reacts
* to notifications.
*
* <p>
* In the non-blocking mode, the iterator will only report the set of
* notifications initially retrieved from GitHub, then quit. This is useful
* for a batch application to process the current set of notifications.
*
* @author Kohsuke Kawaguchi
* @see GitHub#listNotifications()
* @see GHRepository#listNotifications()
*/
public class GHNotificationStream implements Iterable<GHThread> {
private final GitHub root;
private Boolean all, participating;
private String since;
private String apiUrl;
private boolean nonBlocking = false;
/*package*/ GHNotificationStream(GitHub root, String apiUrl) {
this.root = root;
this.apiUrl = apiUrl;
}
/**
* Should the stream include notifications that are already read?
*/
public GHNotificationStream read(boolean v) {
all = v;
return this;
}
/**
* Should the stream be restricted to notifications in which the user
* is directly participating or mentioned?
*/
public GHNotificationStream participating(boolean v) {
participating = v;
return this;
}
public GHNotificationStream since(long timestamp) {
return since(new Date(timestamp));
}
public GHNotificationStream since(Date dt) {
since = GitHub.printDate(dt);
return this;
}
/**
* If set to true, {@link #iterator()} will stop iterating instead of blocking and
* waiting for the updates to arrive.
*/
public GHNotificationStream nonBlocking(boolean v) {
this.nonBlocking = v;
return this;
}
/**
* Returns an infinite blocking {@link Iterator} that returns
* {@link GHThread} as notifications arrive.
*/
public Iterator<GHThread> iterator() {
// capture the configuration setting here
final Requester req = new Requester(root).method("GET")
.with("all", all).with("participating", participating).with("since", since);
return new Iterator<GHThread>() {
/**
* Stuff we've fetched but haven't returned to the caller.
* Newer ones first.
*/
private GHThread[] threads = EMPTY_ARRAY;
/**
* Next element in {@link #threads} to return. This counts down.
*/
private int idx=-1;
/**
* threads whose updated_at is older than this should be ignored.
*/
private long lastUpdated = -1;
/**
* Next request should have "If-Modified-Since" header with this value.
*/
private String lastModified;
/**
* When is the next polling allowed?
*/
private long nextCheckTime = -1;
private GHThread next;
public GHThread next() {
if (next==null) {
next = fetch();
if (next==null)
throw new NoSuchElementException();
}
GHThread r = next;
next = null;
return r;
}
public boolean hasNext() {
if (next==null)
next = fetch();
return next!=null;
}
GHThread fetch() {
try {
while (true) {// loop until we get new threads to return
// if we have fetched un-returned threads, use them first
while (idx>=0) {
GHThread n = threads[idx--];
long nt = n.getUpdatedAt().getTime();
if (nt >= lastUpdated) {
lastUpdated = nt;
return n.wrap(root);
}
}
if (nonBlocking && nextCheckTime>=0)
return null; // nothing more to report, and we aren't blocking
// observe the polling interval before making the call
while (true) {
long now = System.currentTimeMillis();
if (nextCheckTime < now) break;
long waitTime = Math.min(Math.max(nextCheckTime - now, 1000), 60 * 1000);
Thread.sleep(waitTime);
}
req.setHeader("If-Modified-Since", lastModified);
threads = req.to(apiUrl, GHThread[].class);
if (threads==null) {
threads = EMPTY_ARRAY; // if unmodified, we get empty array
} else {
// we get a new batch, but we want to ignore the ones that we've seen
lastUpdated++;
}
idx = threads.length-1;
nextCheckTime = calcNextCheckTime();
lastModified = req.getResponseHeader("Last-Modified");
}
} catch (IOException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private long calcNextCheckTime() {
String v = req.getResponseHeader("X-Poll-Interval");
if (v==null) v="60";
long seconds = Integer.parseInt(v);
return System.currentTimeMillis() + seconds*1000;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public void markAsRead() throws IOException {
markAsRead(-1);
}
/**
* Marks all the notifications as read.
*/
public void markAsRead(long timestamp) throws IOException {
final Requester req = new Requester(root).method("PUT");
if (timestamp>=0)
req.with("last_read_at", GitHub.printDate(new Date(timestamp)));
req.asHttpStatusCode(apiUrl);
}
private static final GHThread[] EMPTY_ARRAY = new GHThread[0];
}

View File

@@ -1,6 +1,7 @@
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;
@@ -9,6 +10,8 @@ import java.util.Date;
/**
* Most (all?) domain objects in GitHub seems to have these 4 properties.
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public abstract class GHObject {
protected String url;
protected int id;
@@ -26,6 +29,7 @@ public abstract class GHObject {
return GitHub.parseDate(created_at);
}
@SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification = "Bridge method of getCreatedAt")
private Object createdAtStr(Date id, Class type) {
return created_at;
}
@@ -38,6 +42,12 @@ public abstract class GHObject {
return GitHub.parseURL(url);
}
/**
* URL of this object for humans, which renders some HTML.
*/
@WithBridgeMethods(value=String.class, adapterMethod="urlToString")
public abstract URL getHtmlUrl();
/**
* When was this resource last updated?
*/
@@ -53,10 +63,12 @@ public abstract class GHObject {
return id;
}
@SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification = "Bridge method of getId")
private Object intToString(int id, Class type) {
return String.valueOf(id);
}
@SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification = "Bridge method of getHtmlUrl")
private Object urlToString(URL url, Class type) {
return url==null ? null : url.toString();
}

View File

@@ -0,0 +1,27 @@
/*
* © Copyright 2015 - SourceClear Inc
*/
package org.kohsuke.github;
class GHOrgHook extends GHHook {
/**
* Organization that the hook belongs to.
*/
/*package*/ transient GHOrganization organization;
/*package*/ GHOrgHook wrap(GHOrganization owner) {
this.organization = owner;
return this;
}
@Override
GitHub getRoot() {
return organization.root;
}
@Override
String getApiRoute() {
return String.format("/orgs/%s/hooks/%d", organization.getLogin(), id);
}
}

View File

@@ -1,9 +1,11 @@
package org.kohsuke.github;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@@ -21,21 +23,35 @@ public class GHOrganization extends GHPerson {
*
* @return
* Newly created repository.
* @deprecated
* Use {@link #createRepository(String)} that uses a builder pattern to let you control every aspect.
*/
public GHRepository createRepository(String name, String description, String homepage, String team, boolean isPublic) throws IOException {
GHTeam t = getTeams().get(team);
if (t==null)
throw new IllegalArgumentException("No such team: "+team);
return createRepository(name,description,homepage,t,isPublic);
return createRepository(name, description, homepage, t, isPublic);
}
/**
* @deprecated
* Use {@link #createRepository(String)} that uses a builder pattern to let you control every aspect.
*/
public GHRepository createRepository(String name, String description, String homepage, GHTeam team, boolean isPublic) throws IOException {
if (team==null)
throw new IllegalArgumentException("Invalid team");
// such API doesn't exist, so fall back to HTML scraping
return new Requester(root)
.with("name", name).with("description", description).with("homepage", homepage)
.with("public", isPublic).with("team_id",team.getId()).to("/orgs/"+login+"/repos", GHRepository.class).wrap(root);
return createRepository(name).description(description).homepage(homepage).private_(!isPublic).team(team).create();
}
/**
* Starts a builder that creates a new repository.
*
* <p>
* You use the returned builder to set various properties, then call {@link GHCreateRepositoryBuilder#create()}
* to finally createa repository.
*/
public GHCreateRepositoryBuilder createRepository(String name) throws IOException {
return new GHCreateRepositoryBuilder(root,"/orgs/"+login+"/repos",name);
}
/**
@@ -54,8 +70,8 @@ public class GHOrganization extends GHPerson {
*/
public PagedIterable<GHTeam> listTeams() throws IOException {
return new PagedIterable<GHTeam>() {
public PagedIterator<GHTeam> iterator() {
return new PagedIterator<GHTeam>(root.retrieve().asIterator(String.format("/orgs/%s/teams", login), GHTeam[].class)) {
public PagedIterator<GHTeam> _iterator(int pageSize) {
return new PagedIterator<GHTeam>(root.retrieve().asIterator(String.format("/orgs/%s/teams", login), GHTeam[].class, pageSize)) {
@Override
protected void wrapUp(GHTeam[] page) {
for (GHTeam c : page)
@@ -147,9 +163,9 @@ public class GHOrganization extends GHPerson {
private PagedIterable<GHUser> listMembers(final String suffix, final String filter) throws IOException {
return new PagedIterable<GHUser>() {
public PagedIterator<GHUser> iterator() {
public PagedIterator<GHUser> _iterator(int pageSize) {
String filterParams = (filter == null) ? "" : ("?filter=" + filter);
return new PagedIterator<GHUser>(root.retrieve().asIterator(String.format("/orgs/%s/%s%s", login, suffix, filterParams), GHUser[].class)) {
return new PagedIterator<GHUser>(root.retrieve().asIterator(String.format("/orgs/%s/%s%s", login, suffix, filterParams), GHUser[].class, pageSize)) {
@Override
protected void wrapUp(GHUser[] users) {
GHUser.wrap(users, root);
@@ -172,7 +188,7 @@ public class GHOrganization extends GHPerson {
* Creates a new team and assigns the repositories.
*/
public GHTeam createTeam(String name, Permission p, Collection<GHRepository> repositories) throws IOException {
Requester post = new Requester(root).with("name", name).with("permission", p.name().toLowerCase());
Requester post = new Requester(root).with("name", name).with("permission", p);
List<String> repo_names = new ArrayList<String>();
for (GHRepository r : repositories) {
repo_names.add(r.getName());
@@ -182,7 +198,7 @@ public class GHOrganization extends GHPerson {
}
public GHTeam createTeam(String name, Permission p, GHRepository... repositories) throws IOException {
return createTeam(name,p, Arrays.asList(repositories));
return createTeam(name, p, Arrays.asList(repositories));
}
/**
@@ -219,8 +235,8 @@ public class GHOrganization extends GHPerson {
*/
public PagedIterable<GHEventInfo> listEvents() throws IOException {
return new PagedIterable<GHEventInfo>() {
public PagedIterator<GHEventInfo> iterator() {
return new PagedIterator<GHEventInfo>(root.retrieve().asIterator(String.format("/orgs/%s/events", login), GHEventInfo[].class)) {
public PagedIterator<GHEventInfo> _iterator(int pageSize) {
return new PagedIterator<GHEventInfo>(root.retrieve().asIterator(String.format("/orgs/%s/events", login), GHEventInfo[].class, pageSize)) {
@Override
protected void wrapUp(GHEventInfo[] page) {
for (GHEventInfo c : page)
@@ -241,8 +257,8 @@ public class GHOrganization extends GHPerson {
@Override
public PagedIterable<GHRepository> listRepositories(final int pageSize) {
return new PagedIterable<GHRepository>() {
public PagedIterator<GHRepository> iterator() {
return new PagedIterator<GHRepository>(root.retrieve().asIterator("/orgs/" + login + "/repos?per_page=" + pageSize, GHRepository[].class)) {
public PagedIterator<GHRepository> _iterator(int pageSize) {
return new PagedIterator<GHRepository>(root.retrieve().asIterator("/orgs/" + login + "/repos?per_page=" + pageSize, GHRepository[].class, pageSize)) {
@Override
protected void wrapUp(GHRepository[] page) {
for (GHRepository c : page)
@@ -252,4 +268,39 @@ public class GHOrganization extends GHPerson {
}
};
}
/**
* Retrieves the currently configured hooks.
*/
public List<GHHook> getHooks() throws IOException {
return GHHooks.orgContext(this).getHooks();
}
public GHHook getHook(int id) throws IOException {
return GHHooks.orgContext(this).getHook(id);
}
/**
*
* See https://api.github.com/hooks for possible names and their configuration scheme.
* TODO: produce type-safe binding
*
* @param name
* Type of the hook to be created. See https://api.github.com/hooks for possible names.
* @param config
* The configuration hash.
* @param events
* Can be null. Types of events to hook into.
*/
public GHHook createHook(String name, Map<String,String> config, Collection<GHEvent> events, boolean active) throws IOException {
return GHHooks.orgContext(this).createHook(name, config, events, active);
}
public GHHook createWebHook(URL url, Collection<GHEvent> events) throws IOException {
return createHook("web", Collections.singletonMap("url", url.toExternalForm()),events,true);
}
public GHHook createWebHook(URL url) throws IOException {
return createWebHook(url, null);
}
}

View File

@@ -2,6 +2,7 @@ package org.kohsuke.github;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
@@ -36,7 +37,7 @@ public abstract class GHPerson extends GHObject {
*
* Depending on the original API call where this object is created, it may not contain everything.
*/
protected void populate() throws IOException {
protected synchronized void populate() throws IOException {
if (created_at!=null) return; // already populated
root.retrieve().to(url, this);
@@ -75,8 +76,8 @@ public abstract class GHPerson extends GHObject {
*/
public PagedIterable<GHRepository> listRepositories(final int pageSize) {
return new PagedIterable<GHRepository>() {
public PagedIterator<GHRepository> iterator() {
return new PagedIterator<GHRepository>(root.retrieve().asIterator("/users/" + login + "/repos?per_page=" + pageSize, GHRepository[].class)) {
public PagedIterator<GHRepository> _iterator(int pageSize) {
return new PagedIterator<GHRepository>(root.retrieve().asIterator("/users/" + login + "/repos?per_page=" + pageSize, GHRepository[].class, pageSize)) {
@Override
protected void wrapUp(GHRepository[] page) {
for (GHRepository c : page)
@@ -103,7 +104,7 @@ public abstract class GHPerson extends GHObject {
public synchronized Iterable<List<GHRepository>> iterateRepositories(final int pageSize) {
return new Iterable<List<GHRepository>>() {
public Iterator<List<GHRepository>> iterator() {
final Iterator<GHRepository[]> pager = root.retrieve().asIterator("/users/" + login + "/repos?per_page="+pageSize,GHRepository[].class);
final Iterator<GHRepository[]> pager = root.retrieve().asIterator("/users/" + login + "/repos?per_page="+pageSize,GHRepository[].class, pageSize);
return new Iterator<List<GHRepository>>() {
public boolean hasNext() {
@@ -214,8 +215,9 @@ public abstract class GHPerson extends GHObject {
return blog;
}
public String getHtmlUrl() {
return html_url;
@Override
public URL getHtmlUrl() {
return GitHub.parseURL(html_url);
}
/**

View File

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

View File

@@ -27,7 +27,6 @@ import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.Date;
import java.util.Locale;
/**
* A pull request.
@@ -37,20 +36,21 @@ import java.util.Locale;
*/
@SuppressWarnings({"UnusedDeclaration"})
public class GHPullRequest extends GHIssue {
private String patch_url, diff_url, issue_url;
private GHCommitPointer base;
private String merged_at;
private GHCommitPointer head;
private String patch_url, diff_url, issue_url;
private GHCommitPointer base;
private String merged_at;
private GHCommitPointer head;
// details that are only available when obtained from ID
private GHUser merged_by;
private int review_comments, additions;
private boolean merged;
private Boolean mergeable;
private int deletions;
private String mergeable_state;
private int changed_files;
private int review_comments, additions;
private boolean merged;
private Boolean mergeable;
private int deletions;
private String mergeable_state;
private int changed_files;
private String merge_commit_sha;
/**
* GitHub doesn't return some properties of {@link GHIssue} when requesting the GET on the 'pulls' API
@@ -60,15 +60,15 @@ public class GHPullRequest extends GHIssue {
private transient boolean fetchedIssueDetails;
GHPullRequest wrapUp(GHRepository owner) {
this.wrap(owner);
GHPullRequest wrapUp(GHRepository owner) {
this.wrap(owner);
return wrapUp(owner.root);
}
GHPullRequest wrapUp(GitHub root) {
if (owner!=null) owner.wrap(root);
if (base!=null) base.wrapUp(root);
if (head!=null) head.wrapUp(root);
if (owner != null) owner.wrap(root);
if (base != null) base.wrapUp(root);
if (head != null) head.wrapUp(root);
if (merged_by != null) merged_by.wrapUp(root);
return this;
}
@@ -85,8 +85,8 @@ public class GHPullRequest extends GHIssue {
public URL getPatchUrl() {
return GitHub.parseURL(patch_url);
}
/**
/**
* The URL of the patch file.
* like https://github.com/jenkinsci/jenkins/pull/100.patch
*/
@@ -108,8 +108,8 @@ public class GHPullRequest extends GHIssue {
public GHCommitPointer getHead() {
return head;
}
@Deprecated
@Deprecated
public Date getIssueUpdatedAt() throws IOException {
return super.getUpdatedAt();
}
@@ -126,65 +126,73 @@ public class GHPullRequest extends GHIssue {
return GitHub.parseDate(merged_at);
}
@Override
public Collection<Label> getLabels() throws IOException {
@Override
public Collection<GHLabel> getLabels() throws IOException {
fetchIssue();
return super.getLabels();
}
return super.getLabels();
}
@Override
public GHUser getClosedBy() {
return null;
}
public GHUser getClosedBy() {
return null;
}
@Override
public PullRequest getPullRequest() {
return null;
}
@Override
public PullRequest getPullRequest() {
return null;
}
//
// details that are only available via get with ID
//
//
// details that are only available via get with ID
//
//
public GHUser getMergedBy() throws IOException {
populate();
return merged_by;
}
return merged_by;
}
public int getReviewComments() throws IOException {
public int getReviewComments() throws IOException {
populate();
return review_comments;
}
return review_comments;
}
public int getAdditions() throws IOException {
public int getAdditions() throws IOException {
populate();
return additions;
}
return additions;
}
public boolean isMerged() throws IOException {
populate();
return merged;
}
return merged;
}
public Boolean getMergeable() throws IOException {
public Boolean getMergeable() throws IOException {
populate();
return mergeable;
}
return mergeable;
}
public int getDeletions() throws IOException {
public int getDeletions() throws IOException {
populate();
return deletions;
}
return deletions;
}
public String getMergeableState() throws IOException {
public String getMergeableState() throws IOException {
populate();
return mergeable_state;
}
return mergeable_state;
}
public int getChangedFiles() throws IOException {
public int getChangedFiles() throws IOException {
populate();
return changed_files;
}
return changed_files;
}
/**
* See <a href="https://developer.github.com/changes/2013-04-25-deprecating-merge-commit-sha">GitHub blog post</a>
*/
public String getMergeCommitSha() throws IOException {
populate();
return merge_commit_sha;
}
/**
* Fully populate the data by retrieving missing data.
@@ -194,7 +202,40 @@ public class GHPullRequest extends GHIssue {
private void populate() throws IOException {
if (merged_by!=null) return; // already populated
root.retrieve().to(url, this);
root.retrieve().to(url, this).wrapUp(owner);
}
/**
* Retrieves all the commits associated to this pull request.
*/
public PagedIterable<GHPullRequestFileDetail> listFiles() {
return new PagedIterable<GHPullRequestFileDetail>() {
public PagedIterator<GHPullRequestFileDetail> _iterator(int pageSize) {
return new PagedIterator<GHPullRequestFileDetail>(root.retrieve().asIterator(String.format("%s/files", getApiURL()),
GHPullRequestFileDetail[].class, pageSize)) {
@Override
protected void wrapUp(GHPullRequestFileDetail[] page) {
}
};
}
};
}
/**
* Obtains all the review comments associated with this pull request.
*/
public PagedIterable<GHPullRequestReviewComment> listReviewComments() throws IOException {
return new PagedIterable<GHPullRequestReviewComment>() {
public PagedIterator<GHPullRequestReviewComment> _iterator(int pageSize) {
return new PagedIterator<GHPullRequestReviewComment>(root.retrieve().asIterator(getApiRoute() + "/comments",
GHPullRequestReviewComment[].class, pageSize)) {
protected void wrapUp(GHPullRequestReviewComment[] page) {
for (GHPullRequestReviewComment c : page)
c.wrapUp(GHPullRequest.this);
}
};
}
};
}
/**
@@ -202,18 +243,29 @@ public class GHPullRequest extends GHIssue {
*/
public PagedIterable<GHPullRequestCommitDetail> listCommits() {
return new PagedIterable<GHPullRequestCommitDetail>() {
public PagedIterator<GHPullRequestCommitDetail> iterator() {
public PagedIterator<GHPullRequestCommitDetail> _iterator(int pageSize) {
return new PagedIterator<GHPullRequestCommitDetail>(root.retrieve().asIterator(
String.format("%s/commits", getApiURL()),
GHPullRequestCommitDetail[].class)) {
GHPullRequestCommitDetail[].class, pageSize)) {
@Override
protected void wrapUp(GHPullRequestCommitDetail[] page) {
for (GHPullRequestCommitDetail c : page)
c.wrapUp(GHPullRequest.this);
}
};
}
};
}
public GHPullRequestReviewComment createReviewComment(String body, String sha, String path, int position) throws IOException {
return new Requester(root).method("POST")
.with("body", body)
.with("commit_id", sha)
.with("path", path)
.with("position", position)
.to(getApiRoute() + "/comments", GHPullRequestReviewComment.class).wrapUp(this);
}
/**
* Merge this pull request.
*
@@ -223,7 +275,21 @@ public class GHPullRequest extends GHIssue {
* Commit message. If null, the default one will be used.
*/
public void merge(String msg) throws IOException {
new Requester(root).method("PUT").with("commit_message",msg).to(getApiRoute()+"/merge");
merge(msg,null);
}
/**
* Merge this pull request.
*
* The equivalent of the big green "Merge pull request" button.
*
* @param msg
* Commit message. If null, the default one will be used.
* @param sha
* SHA that pull request head must match to allow merge.
*/
public void merge(String msg, String sha) throws IOException {
new Requester(root).method("PUT").with("commit_message",msg).with("sha",sha).to(getApiRoute()+"/merge");
}
private void fetchIssue() throws IOException {

View File

@@ -24,106 +24,126 @@
package org.kohsuke.github;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.net.URL;
import java.util.Arrays;
/**
* Commit detail inside a {@link GHPullRequest}.
*
* @author Luca Milanesio
* @see GHPullRequest#listCommits()
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD", "URF_UNREAD_FIELD"}, justification = "JSON API")
public class GHPullRequestCommitDetail {
private GHPullRequest owner;
/*package*/ void wrapUp(GHPullRequest owner) {
this.owner = owner;
}
/**
* @deprecated Use {@link GitUser}
*/
public static class Authorship extends GitUser {}
public static class Authorship extends GitUser {
}
public static class Tree {
String sha;
String url;
public String getSha() {
return sha;
}
public URL getUrl() {
return GitHub.parseURL(url);
}
}
public static class Commit {
Authorship author;
Authorship committer;
String message;
Tree tree;
String url;
int comment_count;
@WithBridgeMethods(value = Authorship.class, castRequired = true)
public GitUser getAuthor() {
return author;
}
@WithBridgeMethods(value = Authorship.class, castRequired = true)
public GitUser getCommitter() {
return committer;
}
public String getMessage() {
return message;
}
public URL getUrl() {
return GitHub.parseURL(url);
}
public int getComment_count() {
return comment_count;
}
public Tree getTree() {
return tree;
}
}
public static class CommitPointer {
String sha;
String url;
String html_url;
public URL getUrl() {
return GitHub.parseURL(url);
}
public URL getHtml_url() {
return GitHub.parseURL(html_url);
}
public String getSha() {
return sha;
}
}
String sha;
String url;
public String getSha() {
return sha;
}
public URL getUrl() {
return GitHub.parseURL(url);
}
}
public static class Commit {
Authorship author;
Authorship committer;
String message;
Tree tree;
String url;
int comment_count;
@WithBridgeMethods(value=Authorship.class,castRequired=true)
public GitUser getAuthor() {
return author;
}
@WithBridgeMethods(value=Authorship.class,castRequired=true)
public GitUser getCommitter() {
return committer;
}
public String getMessage() {
return message;
}
public URL getUrl() {
return GitHub.parseURL(url);
}
public int getComment_count() {
return comment_count;
}
}
public static class CommitPointer {
String sha;
Commit commit;
String url;
String html_url;
public URL getUrl() {
return GitHub.parseURL(url);
}
public URL getHtml_url() {
return GitHub.parseURL(html_url);
}
String comments_url;
CommitPointer[] parents;
public String getSha() {
return sha;
return sha;
}
}
String sha;
Commit commit;
String url;
String html_url;
String comments_url;
CommitPointer[] parents;
public String getSha() {
return sha;
}
public Commit getCommit() {
return commit;
}
public URL getApiUrl() {
return GitHub.parseURL(url);
}
public URL getUrl() {
return GitHub.parseURL(html_url);
}
public URL getCommentsUrl() {
return GitHub.parseURL(comments_url);
}
public CommitPointer[] getParents() {
return parents;
}
public Commit getCommit() {
return commit;
}
public URL getApiUrl() {
return GitHub.parseURL(url);
}
public URL getUrl() {
return GitHub.parseURL(html_url);
}
public URL getCommentsUrl() {
return GitHub.parseURL(comments_url);
}
public CommitPointer[] getParents() {
return Arrays.copyOf(parents, parents.length);
}
}

View File

@@ -0,0 +1,86 @@
/*
* The MIT License
*
* Copyright (c) 2015, Julien Henry
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.kohsuke.github;
import java.net.URL;
/**
* File detail inside a {@link GHPullRequest}.
*
* @author Julien Henry
* @see GHPullRequest#listFiles()
*/
public class GHPullRequestFileDetail {
String sha;
String filename;
String status;
int additions;
int deletions;
int changes;
String blob_url;
String raw_url;
String contents_url;
String patch;
public String getSha() {
return sha;
}
public String getFilename() {
return filename;
}
public String getStatus() {
return status;
}
public int getAdditions() {
return additions;
}
public int getDeletions() {
return deletions;
}
public int getChanges() {
return changes;
}
public URL getBlobUrl() {
return GitHub.parseURL(blob_url);
}
public URL getRawUrl() {
return GitHub.parseURL(raw_url);
}
public URL getContentsUrl() {
return GitHub.parseURL(contents_url);
}
public String getPatch() {
return patch;
}
}

View File

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

View File

@@ -0,0 +1,106 @@
/*
* The MIT License
*
* Copyright (c) 2010, Kohsuke Kawaguchi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.kohsuke.github;
import java.io.IOException;
import java.net.URL;
/**
* Review comment to the pull request
*
* @author Julien Henry
* @see GHPullRequest#listReviewComments()
* @see GHPullRequest#createReviewComment(String, String, String, int)
*/
public class GHPullRequestReviewComment extends GHObject {
GHPullRequest owner;
private String body;
private GHUser user;
private String path;
private int position;
private int originalPosition;
/*package*/ GHPullRequestReviewComment wrapUp(GHPullRequest owner) {
this.owner = owner;
return this;
}
/**
* Gets the pull request to which this review comment is associated.
*/
public GHPullRequest getParent() {
return owner;
}
/**
* The comment itself.
*/
public String getBody() {
return body;
}
/**
* Gets the user who posted this comment.
*/
public GHUser getUser() throws IOException {
return owner.root.getUser(user.getLogin());
}
public String getPath() {
return path;
}
public int getPosition() {
return position;
}
public int getOriginalPosition() {
return originalPosition;
}
@Override
public URL getHtmlUrl() {
return null;
}
protected String getApiRoute() {
return "/repos/"+owner.getRepository().getFullName()+"/pulls/comments/"+id;
}
/**
* Updates the comment.
*/
public void update(String body) throws IOException {
new Requester(owner.root).method("PATCH").with("body", body).to(getApiRoute(),this);
this.body = body;
}
/**
* Deletes this review comment.
*/
public void delete() throws IOException {
new Requester(owner.root).method("DELETE").to(getApiRoute());
}
}

View File

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

View File

@@ -1,5 +1,8 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Date;
/**
* Rate limit.
* @author Kohsuke Kawaguchi
@@ -10,12 +13,30 @@ public class GHRateLimit {
*/
public int remaining;
/**
* Alotted API call per hour.
* Allotted API call per hour.
*/
public int limit;
/**
* The time at which the current rate limit window resets in UTC epoch seconds.
*/
public Date reset;
/**
* Non-epoch date
*/
@SuppressFBWarnings(value = "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR",
justification = "The value comes from JSON deserialization")
public Date getResetDate() {
return new Date(reset.getTime() * 1000);
}
@Override
public String toString() {
return remaining+"/"+limit;
return "GHRateLimit{" +
"remaining=" + remaining +
", limit=" + limit +
", resetDate=" + getResetDate() +
'}';
}
}

View File

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

View File

@@ -3,6 +3,7 @@ package org.kohsuke.github;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@@ -44,8 +45,14 @@ public class GHRelease extends GHObject {
return draft;
}
public String getHtmlUrl() {
return html_url;
public GHRelease setDraft(boolean draft) throws IOException {
edit("draft", draft);
this.draft = draft;
return this;
}
public URL getHtmlUrl() {
return GitHub.parseURL(html_url);
}
public String getName() {
@@ -69,7 +76,7 @@ public class GHRelease extends GHObject {
}
public Date getPublished_at() {
return published_at;
return new Date(published_at.getTime());
}
public GitHub getRoot() {
@@ -120,7 +127,7 @@ public class GHRelease extends GHObject {
public GHAsset uploadAsset(File file, String contentType) throws IOException {
Requester builder = new Requester(owner.root);
String url = format("https://uploads.github.com%sreleases/%d/assets?name=%s",
String url = format("https://uploads.github.com%s/releases/%d/assets?name=%s",
owner.getApiTailUrl(""), getId(), file.getName());
return builder.contentType(contentType)
.with(new FileInputStream(file))
@@ -143,6 +150,13 @@ public class GHRelease extends GHObject {
new Requester(root).method("DELETE").to(owner.getApiTailUrl("releases/"+id));
}
/**
* Edit this release.
*/
private void edit(String key, Object value) throws IOException {
new Requester(root)._with(key, value).method("PATCH").to(owner.getApiTailUrl("releases/"+id));
}
private String getApiTailUrl(String end) {
return owner.getApiTailUrl(format("releases/%s/%s",id,end));
}

View File

@@ -0,0 +1,23 @@
package org.kohsuke.github;
class GHRepoHook extends GHHook {
/**
* Repository that the hook belongs to.
*/
/*package*/ transient GHRepository repository;
/*package*/ GHRepoHook wrap(GHRepository owner) {
this.repository = owner;
return this;
}
@Override
GitHub getRoot() {
return repository.root;
}
@Override
String getApiRoute() {
return String.format("/repos/%s/%s/hooks/%d", repository.getOwnerName(), repository.getName(), id);
}
}

View File

@@ -25,12 +25,16 @@ package org.kohsuke.github;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.lang.StringUtils;
import javax.xml.bind.DatatypeConverter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.*;
@@ -38,16 +42,18 @@ import static java.util.Arrays.asList;
/**
* A repository on GitHub.
*
*
* @author Kohsuke Kawaguchi
*/
@SuppressWarnings({"UnusedDeclaration"})
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public class GHRepository extends GHObject {
/*package almost final*/ GitHub root;
private String description, homepage, name, full_name;
private String html_url; // this is the UI
private String git_url, ssh_url, clone_url, svn_url;
private String git_url, ssh_url, clone_url, svn_url, mirror_url;
private GHUser owner; // not fully populated. beware.
private boolean has_issues, has_wiki, fork, has_downloads;
@JsonProperty("private")
@@ -55,20 +61,22 @@ public class GHRepository extends GHObject {
private int watchers,forks,open_issues,size,network_count,subscribers_count;
private String pushed_at;
private Map<Integer,GHMilestone> milestones = new HashMap<Integer, GHMilestone>();
private String default_branch,language;
private Map<String,GHCommit> commits = new HashMap<String, GHCommit>();
private GHRepoPermission permissions;
private GHRepository source, parent;
public GHDeploymentBuilder createDeployment(String ref) {
return new GHDeploymentBuilder(this,ref);
}
public PagedIterable<GHDeploymentStatus> getDeploymentStatuses(final int id) {
return new PagedIterable<GHDeploymentStatus>() {
public PagedIterator<GHDeploymentStatus> iterator() {
return new PagedIterator<GHDeploymentStatus>(root.retrieve().asIterator(getApiTailUrl("deployments")+"/"+id+"/statuses", GHDeploymentStatus[].class)) {
public PagedIterator<GHDeploymentStatus> _iterator(int pageSize) {
return new PagedIterator<GHDeploymentStatus>(root.retrieve().asIterator(getApiTailUrl("deployments")+"/"+id+"/statuses", GHDeploymentStatus[].class, pageSize)) {
@Override
protected void wrapUp(GHDeploymentStatus[] page) {
for (GHDeploymentStatus c : page)
@@ -83,8 +91,8 @@ public class GHRepository extends GHObject {
List<String> params = Arrays.asList(getParam("sha", sha), getParam("ref", ref), getParam("task", task), getParam("environment", environment));
final String deploymentsUrl = getApiTailUrl("deployments") + "?"+ join(params,"&");
return new PagedIterable<GHDeployment>() {
public PagedIterator<GHDeployment> iterator() {
return new PagedIterator<GHDeployment>(root.retrieve().asIterator(deploymentsUrl, GHDeployment[].class)) {
public PagedIterator<GHDeployment> _iterator(int pageSize) {
return new PagedIterator<GHDeployment>(root.retrieve().asIterator(deploymentsUrl, GHDeployment[].class, pageSize)) {
@Override
protected void wrapUp(GHDeployment[] page) {
for (GHDeployment c : page)
@@ -150,6 +158,14 @@ public class GHRepository extends GHObject {
return svn_url;
}
/**
* Gets the Mirror URL to access this repository: https://github.com/apache/tomee
* mirrored from git://git.apache.org/tomee.git
*/
public String getMirrorUrl() {
return mirror_url;
}
/**
* Gets the SSH URL to access this repository, such as git@github.com:rails/rails.git
*/
@@ -157,6 +173,10 @@ public class GHRepository extends GHObject {
return ssh_url;
}
public URL getHtmlUrl() {
return GitHub.parseURL(html_url);
}
/**
* Short repository name without the owner. For example 'jenkins' in case of http://github.com/jenkinsci/jenkins
*/
@@ -195,7 +215,7 @@ public class GHRepository extends GHObject {
}
public GHIssue getIssue(int id) throws IOException {
return root.retrieve().to("/repos/" + owner.login + "/" + name + "/issues/" + id, GHIssue.class).wrap(this);
return root.retrieve().to(getApiTailUrl("issues/" + id), GHIssue.class).wrap(this);
}
public GHIssueBuilder createIssue(String title) {
@@ -208,10 +228,10 @@ public class GHRepository extends GHObject {
public List<GHIssue> getIssues(GHIssueState state, GHMilestone milestone) throws IOException {
return Arrays.asList(GHIssue.wrap(root.retrieve()
.to(String.format("/repos/%s/%s/issues?state=%s&milestone=%s", owner.login, name,
state.toString().toLowerCase(), milestone == null ? "none" : "" + milestone.getNumber()),
GHIssue[].class
), this));
.with("state", state)
.with("milestone", milestone == null ? "none" : "" + milestone.getNumber())
.to(getApiTailUrl("issues"),
GHIssue[].class), this));
}
/**
@@ -219,8 +239,8 @@ public class GHRepository extends GHObject {
*/
public PagedIterable<GHIssue> listIssues(final GHIssueState state) {
return new PagedIterable<GHIssue>() {
public PagedIterator<GHIssue> iterator() {
return new PagedIterator<GHIssue>(root.retrieve().asIterator(getApiTailUrl("issues?state="+state.toString().toLowerCase(Locale.ENGLISH)), GHIssue[].class)) {
public PagedIterator<GHIssue> _iterator(int pageSize) {
return new PagedIterator<GHIssue>(root.retrieve().with("state",state).asIterator(getApiTailUrl("issues"), GHIssue[].class, pageSize)) {
@Override
protected void wrapUp(GHIssue[] page) {
for (GHIssue c : page)
@@ -259,8 +279,8 @@ public class GHRepository extends GHObject {
public PagedIterable<GHRelease> listReleases() throws IOException {
return new PagedIterable<GHRelease>() {
public PagedIterator<GHRelease> iterator() {
return new PagedIterator<GHRelease>(root.retrieve().asIterator(getApiTailUrl("releases"), GHRelease[].class)) {
public PagedIterator<GHRelease> _iterator(int pageSize) {
return new PagedIterator<GHRelease>(root.retrieve().asIterator(getApiTailUrl("releases"), GHRelease[].class, pageSize)) {
@Override
protected void wrapUp(GHRelease[] page) {
for (GHRelease c : page)
@@ -273,8 +293,8 @@ public class GHRepository extends GHObject {
public PagedIterable<GHTag> listTags() throws IOException {
return new PagedIterable<GHTag>() {
public PagedIterator<GHTag> iterator() {
return new PagedIterator<GHTag>(root.retrieve().asIterator(getApiTailUrl("tags"), GHTag[].class)) {
public PagedIterator<GHTag> _iterator(int pageSize) {
return new PagedIterator<GHTag>(root.retrieve().asIterator(getApiTailUrl("tags"), GHTag[].class, pageSize)) {
@Override
protected void wrapUp(GHTag[] page) {
for (GHTag c : page)
@@ -285,7 +305,19 @@ public class GHRepository extends GHObject {
};
}
protected String getOwnerName() {
/**
* List languages for the specified repository.
* The value on the right of a language is the number of bytes of code written in that language.
* {
"C": 78769,
"Python": 7769
}
*/
public Map<String,Long> listLanguages() throws IOException {
return root.retrieve().to(getApiTailUrl("languages"), HashMap.class);
}
public String getOwnerName() {
return owner.login;
}
@@ -301,6 +333,10 @@ public class GHRepository extends GHObject {
return fork;
}
/**
* Returns the number of all forks of this repository.
* This not only counts direct forks, but also forks of forks, and so on.
*/
public int getForks() {
return forks;
}
@@ -339,11 +375,19 @@ public class GHRepository extends GHObject {
}
/**
* Returns the primary branch you'll configure in the "Admin > Options" config page.
* Returns the primary branch you'll configure in the "Admin &gt; Options" config page.
*
* @return
* This field is null until the user explicitly configures the master branch.
*/
public String getDefaultBranch() {
return default_branch;
}
/**
* @deprecated
* Renamed to {@link #getDefaultBranch()}
*/
public String getMasterBranch() {
return default_branch;
}
@@ -351,7 +395,8 @@ public class GHRepository extends GHObject {
public int getSize() {
return size;
}
/**
* Gets the collaborators on this repository.
* This set always appear to include the owner.
@@ -369,9 +414,9 @@ public class GHRepository extends GHObject {
*/
public PagedIterable<GHUser> listCollaborators() throws IOException {
return new PagedIterable<GHUser>() {
public PagedIterator<GHUser> iterator() {
public PagedIterator<GHUser> _iterator(int pageSize) {
return new PagedIterator<GHUser>(root.retrieve().asIterator("/repos/" + owner.login + "/" + name + "/collaborators", GHUser[].class)) {
return new PagedIterator<GHUser>(root.retrieve().asIterator(getApiTailUrl("collaborators"), GHUser[].class, pageSize)) {
@Override
protected void wrapUp(GHUser[] users) {
@@ -392,7 +437,7 @@ public class GHRepository extends GHObject {
*/
public Set<String> getCollaboratorNames() throws IOException {
Set<String> r = new HashSet<String>();
for (GHUser u : GHUser.wrap(root.retrieve().to("/repos/" + owner.login + "/" + name + "/collaborators", GHUser[].class),root))
for (GHUser u : GHUser.wrap(root.retrieve().to(getApiTailUrl("collaborators"), GHUser[].class),root))
r.add(u.login);
return r;
}
@@ -401,7 +446,7 @@ public class GHRepository extends GHObject {
* If this repository belongs to an organization, return a set of teams.
*/
public Set<GHTeam> getTeams() throws IOException {
return Collections.unmodifiableSet(new HashSet<GHTeam>(Arrays.asList(GHTeam.wrapUp(root.retrieve().to("/repos/" + owner.login + "/" + name + "/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(owner.login)))));
}
public void addCollaborators(GHUser... users) throws IOException {
@@ -423,22 +468,22 @@ public class GHRepository extends GHObject {
private void modifyCollaborators(Collection<GHUser> users, String method) throws IOException {
verifyMine();
for (GHUser user : users) {
new Requester(root).method(method).to("/repos/" + owner.login + "/" + name + "/collaborators/" + user.getLogin());
new Requester(root).method(method).to(getApiTailUrl("collaborators/" + user.getLogin()));
}
}
public void setEmailServiceHook(String address) throws IOException {
Map<String, String> config = new HashMap<String, String>();
config.put("address", address);
new Requester(root).method("POST").with("name", "email").with("config", config).with("active", "true")
.to(String.format("/repos/%s/%s/hooks", owner.login, name));
new Requester(root).method("POST").with("name", "email").with("config", config).with("active", true)
.to(getApiTailUrl("hooks"));
}
private void edit(String key, String value) throws IOException {
Requester requester = new Requester(root);
if (!key.equals("name"))
requester.with("name", name); // even when we don't change the name, we need to send it in
requester.with(key, value).method("PATCH").to("/repos/" + owner.login + "/" + name);
requester.with(key, value).method("PATCH").to(getApiTailUrl(""));
}
/**
@@ -474,17 +519,54 @@ public class GHRepository extends GHObject {
edit("homepage",value);
}
public void setDefaultBranch(String value) throws IOException {
edit("default_branch", value);
}
/**
* Deletes this repository.
*/
public void delete() throws IOException {
try {
new Requester(root).method("DELETE").to("/repos/" + owner.login + "/" + name);
new Requester(root).method("DELETE").to(getApiTailUrl(""));
} 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);
}
}
/**
* Sort orders for listing forks
*/
public enum ForkSort { NEWEST, OLDEST, STARGAZERS }
/**
* Lists all the direct forks of this repository, sorted by
* github api default, currently {@link ForkSort#NEWEST ForkSort.NEWEST}.
*/
public PagedIterable<GHRepository> listForks() {
return listForks(null);
}
/**
* Lists all the direct forks of this repository, sorted by the given sort order.
* @param sort the sort order. If null, defaults to github api default,
* currently {@link ForkSort#NEWEST ForkSort.NEWEST}.
*/
public PagedIterable<GHRepository> listForks(final ForkSort sort) {
return new PagedIterable<GHRepository>() {
public PagedIterator<GHRepository> _iterator(int pageSize) {
return new PagedIterator<GHRepository>(root.retrieve().with("sort",sort).asIterator(getApiTailUrl("forks"), GHRepository[].class, pageSize)) {
@Override
protected void wrapUp(GHRepository[] page) {
for (GHRepository c : page) {
c.wrap(root);
}
}
};
}
};
}
/**
* Forks this repository as your repository.
*
@@ -492,7 +574,7 @@ public class GHRepository extends GHObject {
* Newly forked repository that belong to you.
*/
public GHRepository fork() throws IOException {
return new Requester(root).method("POST").to("/repos/" + owner.login + "/" + name + "/forks", GHRepository.class).wrap(root);
return new Requester(root).method("POST").to(getApiTailUrl("forks"), GHRepository.class).wrap(root);
}
/**
@@ -502,7 +584,7 @@ public class GHRepository extends GHObject {
* Newly forked repository that belong to you.
*/
public GHRepository forkTo(GHOrganization org) throws IOException {
new Requester(root).to(String.format("/repos/%s/%s/forks?org=%s", owner.login, name, org.getLogin()));
new Requester(root).to(getApiTailUrl("forks?org="+org.getLogin()));
// this API is asynchronous. we need to wait for a bit
for (int i=0; i<10; i++) {
@@ -521,7 +603,7 @@ public class GHRepository extends GHObject {
* Retrieves a specified pull request.
*/
public GHPullRequest getPullRequest(int i) throws IOException {
return root.retrieve().to("/repos/" + owner.login + '/' + name + "/pulls/" + i, GHPullRequest.class).wrapUp(this);
return root.retrieve().to(getApiTailUrl("pulls/" + i), GHPullRequest.class).wrapUp(this);
}
/**
@@ -530,24 +612,24 @@ public class GHRepository extends GHObject {
* @see #listPullRequests(GHIssueState)
*/
public List<GHPullRequest> getPullRequests(GHIssueState state) throws IOException {
return listPullRequests(state).asList();
return queryPullRequests().state(state).list().asList();
}
/**
* Retrieves all the pull requests of a particular state.
*
* @deprecated
* Use {@link #queryPullRequests()}
*/
public PagedIterable<GHPullRequest> listPullRequests(final GHIssueState state) {
return new PagedIterable<GHPullRequest>() {
public PagedIterator<GHPullRequest> iterator() {
return new PagedIterator<GHPullRequest>(root.retrieve().asIterator(String.format("/repos/%s/%s/pulls?state=%s", owner.login, name, state.name().toLowerCase(Locale.ENGLISH)), GHPullRequest[].class)) {
@Override
protected void wrapUp(GHPullRequest[] page) {
for (GHPullRequest pr : page)
pr.wrapUp(GHRepository.this);
}
};
}
};
public PagedIterable<GHPullRequest> listPullRequests(GHIssueState state) {
return queryPullRequests().state(state).list();
}
/**
* Retrieves pull requests.
*/
public GHPullRequestQueryBuilder queryPullRequests() {
return new GHPullRequestQueryBuilder(this);
}
/**
@@ -577,15 +659,11 @@ public class GHRepository extends GHObject {
* Retrieves the currently configured hooks.
*/
public List<GHHook> getHooks() throws IOException {
List<GHHook> list = new ArrayList<GHHook>(Arrays.asList(
root.retrieve().to(String.format("/repos/%s/%s/hooks", owner.login, name), GHHook[].class)));
for (GHHook h : list)
h.wrap(this);
return list;
return GHHooks.repoContext(this, owner).getHooks();
}
public GHHook getHook(int id) throws IOException {
return root.retrieve().to(String.format("/repos/%s/%s/hooks/%d", owner.login, name, id), GHHook.class).wrap(this);
return GHHooks.repoContext(this, owner).getHook(id);
}
/**
@@ -597,7 +675,7 @@ public class GHRepository extends GHObject {
* @throws IOException on failure communicating with GitHub
*/
public GHCompare getCompare(String id1, String id2) throws IOException {
GHCompare compare = root.retrieve().to(String.format("/repos/%s/%s/compare/%s...%s", owner.login, name, id1, id2), GHCompare.class);
GHCompare compare = root.retrieve().to(getApiTailUrl(String.format("compare/%s...%s", id1, id2)), GHCompare.class);
return compare.wrap(this);
}
@@ -606,7 +684,23 @@ public class GHRepository extends GHObject {
}
public GHCompare getCompare(GHBranch id1, GHBranch id2) throws IOException {
return getCompare(id1.getName(),id2.getName());
GHRepository owner1 = id1.getOwner();
GHRepository owner2 = id2.getOwner();
// If the owner of the branches is different, we have a cross-fork compare.
if (owner1!=null && owner2!=null) {
String ownerName1 = owner1.getOwnerName();
String ownerName2 = owner2.getOwnerName();
if (!StringUtils.equals(ownerName1, ownerName2)) {
String qualifiedName1 = String.format("%s:%s", ownerName1, id1.getName());
String qualifiedName2 = String.format("%s:%s", ownerName2, id2.getName());
return getCompare(qualifiedName1, qualifiedName2);
}
}
return getCompare(id1.getName(), id2.getName());
}
/**
@@ -615,7 +709,7 @@ public class GHRepository extends GHObject {
* @throws IOException on failure communicating with GitHub
*/
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", owner.login, name), GHRef[].class), root);
}
/**
@@ -628,18 +722,47 @@ public class GHRepository extends GHObject {
return GHRef.wrap(root.retrieve().to(String.format("/repos/%s/%s/git/refs/%s", owner.login, name, refType), GHRef[].class),root);
}
/**
* Retrive a ref of the given type for the current GitHub repository.
*
* @param refName
* eg: heads/branch
* @return refs matching the request type
* @throws IOException
* on failure communicating with GitHub, potentially due to an
* invalid ref type being requested
*/
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);
}
* Retrive a ref of the given type for the current GitHub repository.
*
* @param refName
* eg: heads/branch
* @return refs matching the request type
* @throws IOException
* on failure communicating with GitHub, potentially due to an
* invalid ref type being requested
*/
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);
}
/**
* Retrive a tree of the given type for the current GitHub repository.
*
* @param sha - sha number or branch name ex: "master"
* @return refs matching the request type
* @throws IOException
* on failure communicating with GitHub, potentially due to an
* invalid tree type being requested
*/
public GHTree getTree(String sha) throws IOException {
String url = String.format("/repos/%s/%s/git/trees/%s", owner.login, name, sha);
return root.retrieve().to(url, GHTree.class).wrap(root);
}
/**
* Retrieves the tree for the current GitHub repository, recursively as described in here:
* https://developer.github.com/v3/git/trees/#get-a-tree-recursively
*
* @param sha - sha number or branch name ex: "master"
* @param recursive use 1
* @throws IOException
* on failure communicating with GitHub, potentially due to an
* invalid tree type being requested
*/
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);
return root.retrieve().to(url, GHTree.class).wrap(root);
}
/**
* Gets a commit object in this repository.
*/
@@ -657,8 +780,8 @@ public class GHRepository extends GHObject {
*/
public PagedIterable<GHCommit> listCommits() {
return new PagedIterable<GHCommit>() {
public PagedIterator<GHCommit> iterator() {
return new PagedIterator<GHCommit>(root.retrieve().asIterator(String.format("/repos/%s/%s/commits", owner.login, name), GHCommit[].class)) {
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)) {
protected void wrapUp(GHCommit[] page) {
for (GHCommit c : page)
c.wrapUp(GHRepository.this);
@@ -680,8 +803,8 @@ public class GHRepository extends GHObject {
*/
public PagedIterable<GHCommitComment> listCommitComments() {
return new PagedIterable<GHCommitComment>() {
public PagedIterator<GHCommitComment> iterator() {
return new PagedIterator<GHCommitComment>(root.retrieve().asIterator(String.format("/repos/%s/%s/comments", owner.login, name), GHCommitComment[].class)) {
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)) {
@Override
protected void wrapUp(GHCommitComment[] page) {
for (GHCommitComment c : page)
@@ -697,8 +820,8 @@ public class GHRepository extends GHObject {
*/
public PagedIterable<GHCommitStatus> listCommitStatuses(final String sha1) throws IOException {
return new PagedIterable<GHCommitStatus>() {
public PagedIterator<GHCommitStatus> iterator() {
return new PagedIterator<GHCommitStatus>(root.retrieve().asIterator(String.format("/repos/%s/%s/statuses/%s", owner.login, name, sha1), GHCommitStatus[].class)) {
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)) {
@Override
protected void wrapUp(GHCommitStatus[] page) {
for (GHCommitStatus c : page)
@@ -725,22 +848,22 @@ public class GHRepository extends GHObject {
* @param description
* Optional short description.
* @param context
* Optinal commit status context.
* Optinal commit status context.
*/
public GHCommitStatus createCommitStatus(String sha1, GHCommitState state, String targetUrl, String description, String context) throws IOException {
return new Requester(root)
.with("state", state.name().toLowerCase(Locale.ENGLISH))
.with("state", state)
.with("target_url", targetUrl)
.with("description", description)
.with("context", context)
.to(String.format("/repos/%s/%s/statuses/%s",owner.login,this.name,sha1),GHCommitStatus.class).wrapUp(root);
}
/**
* @see {@link #createCommitStatus(String, GHCommitState,String,String,String) createCommitStatus}
* @see #createCommitStatus(String, GHCommitState,String,String,String)
*/
public GHCommitStatus createCommitStatus(String sha1, GHCommitState state, String targetUrl, String description) throws IOException {
return createCommitStatus(sha1, state, targetUrl, description,null);
return createCommitStatus(sha1, state, targetUrl, description, null);
}
/**
@@ -748,8 +871,8 @@ public class GHRepository extends GHObject {
*/
public PagedIterable<GHEventInfo> listEvents() throws IOException {
return new PagedIterable<GHEventInfo>() {
public PagedIterator<GHEventInfo> iterator() {
return new PagedIterator<GHEventInfo>(root.retrieve().asIterator(String.format("/repos/%s/%s/events", owner.login, name), GHEventInfo[].class)) {
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)) {
@Override
protected void wrapUp(GHEventInfo[] page) {
for (GHEventInfo c : page)
@@ -761,10 +884,69 @@ public class GHRepository extends GHObject {
}
/**
*
* Lists labels in this repository.
*
* https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository
*/
public PagedIterable<GHLabel> listLabels() throws IOException {
return new PagedIterable<GHLabel>() {
public PagedIterator<GHLabel> _iterator(int pageSize) {
return new PagedIterator<GHLabel>(root.retrieve().asIterator(getApiTailUrl("labels"), GHLabel[].class, pageSize)) {
@Override
protected void wrapUp(GHLabel[] page) {
for (GHLabel c : page)
c.wrapUp(GHRepository.this);
}
};
}
};
}
public GHLabel getLabel(String name) throws IOException {
return root.retrieve().to(getApiTailUrl("labels/"+name), GHLabel.class).wrapUp(this);
}
public GHLabel createLabel(String name, String color) throws IOException {
return root.retrieve().method("POST")
.with("name",name)
.with("color", color)
.to(getApiTailUrl("labels"), GHLabel.class).wrapUp(this);
}
/**
* Lists all the subscribers (aka watchers.)
*
* https://developer.github.com/v3/activity/watching/
*/
public PagedIterable<GHUser> listSubscribers() {
return listUsers("subscribers");
}
/**
* Lists all the users who have starred this repo.
*/
public PagedIterable<GHUser> listStargazers() {
return listUsers("stargazers");
}
private PagedIterable<GHUser> listUsers(final String suffix) {
return new PagedIterable<GHUser>() {
public PagedIterator<GHUser> _iterator(int pageSize) {
return new PagedIterator<GHUser>(root.retrieve().asIterator(getApiTailUrl(suffix), GHUser[].class, pageSize)) {
protected void wrapUp(GHUser[] page) {
for (GHUser c : page)
c.wrapUp(root);
}
};
}
};
}
/**
*
* See https://api.github.com/hooks for possible names and their configuration scheme.
* TODO: produce type-safe binding
*
*
* @param name
* Type of the hook to be created. See https://api.github.com/hooks for possible names.
* @param config
@@ -773,21 +955,9 @@ public class GHRepository extends GHObject {
* Can be null. Types of events to hook into.
*/
public GHHook createHook(String name, Map<String,String> config, Collection<GHEvent> events, boolean active) throws IOException {
List<String> ea = null;
if (events!=null) {
ea = new ArrayList<String>();
for (GHEvent e : events)
ea.add(e.name().toLowerCase(Locale.ENGLISH));
}
return new Requester(root)
.with("name", name)
.with("active", active)
._with("config", config)
._with("events",ea)
.to(String.format("/repos/%s/%s/hooks",owner.login,this.name),GHHook.class).wrap(this);
return GHHooks.repoContext(this, owner).createHook(name, config, events, active);
}
public GHHook createWebHook(URL url, Collection<GHEvent> events) throws IOException {
return createHook("web",Collections.singletonMap("url",url.toExternalForm()),events,true);
}
@@ -812,10 +982,12 @@ public class GHRepository extends GHObject {
/**
* Returns a set that represents the post-commit hook URLs.
* The returned set is live, and changes made to them are reflected to GitHub.
*
* @deprecated
*
* @deprecated
* Use {@link #getHooks()} and {@link #createHook(String, Map, Collection, boolean)}
*/
@SuppressFBWarnings(value = "DMI_COLLECTION_OF_URLS",
justification = "It causes a performance degradation, but we have already exposed it to the API")
public Set<URL> getPostCommitHooks() {
return postCommitHooks;
}
@@ -823,6 +995,8 @@ public class GHRepository extends GHObject {
/**
* Live set view of the post-commit hook.
*/
@SuppressFBWarnings(value = "DMI_COLLECTION_OF_URLS",
justification = "It causes a performance degradation, but we have already exposed it to the API")
private final Set<URL> postCommitHooks = new AbstractSet<URL>() {
private List<URL> getPostCommitHooks() {
try {
@@ -898,10 +1072,10 @@ public class GHRepository extends GHObject {
*/
public Map<Integer, GHMilestone> getMilestones() throws IOException {
Map<Integer,GHMilestone> milestones = new TreeMap<Integer, GHMilestone>();
for (GHMilestone m : listMilestones(GHIssueState.OPEN)) {
milestones.put(m.getNumber(), m);
}
return milestones;
for (GHMilestone m : listMilestones(GHIssueState.OPEN)) {
milestones.put(m.getNumber(), m);
}
return milestones;
}
/**
@@ -909,8 +1083,8 @@ public class GHRepository extends GHObject {
*/
public PagedIterable<GHMilestone> listMilestones(final GHIssueState state) {
return new PagedIterable<GHMilestone>() {
public PagedIterator<GHMilestone> iterator() {
return new PagedIterator<GHMilestone>(root.retrieve().asIterator(getApiTailUrl("milestones?state="+state.toString().toLowerCase(Locale.ENGLISH)), GHMilestone[].class)) {
public PagedIterator<GHMilestone> _iterator(int pageSize) {
return new PagedIterator<GHMilestone>(root.retrieve().with("state",state).asIterator(getApiTailUrl("milestones"), GHMilestone[].class, pageSize)) {
@Override
protected void wrapUp(GHMilestone[] page) {
for (GHMilestone c : page)
@@ -921,16 +1095,16 @@ public class GHRepository extends GHObject {
};
}
public GHMilestone getMilestone(int number) throws IOException {
GHMilestone m = milestones.get(number);
if (m == null) {
public GHMilestone getMilestone(int number) throws IOException {
GHMilestone m = milestones.get(number);
if (m == null) {
m = root.retrieve().to(getApiTailUrl("milestones/" + number), GHMilestone.class);
m.owner = this;
m.root = root;
milestones.put(m.getNumber(), m);
}
return m;
}
m.owner = this;
m.root = root;
milestones.put(m.getNumber(), m);
}
return m;
}
public GHContent getFileContent(String path) throws IOException {
return getFileContent(path, null);
@@ -938,12 +1112,9 @@ public class GHRepository extends GHObject {
public GHContent getFileContent(String path, String ref) throws IOException {
Requester requester = root.retrieve();
String target = String.format("/repos/%s/%s/contents/%s", owner.login, name, path);
String target = getApiTailUrl("contents/" + path);
if (ref != null)
target = target + "?ref=" + ref;
return requester.to(target, GHContent.class).wrap(this);
return requester.with("ref",ref).to(target, GHContent.class).wrap(this);
}
public List<GHContent> getDirectoryContent(String path) throws IOException {
@@ -952,28 +1123,38 @@ public class GHRepository extends GHObject {
public List<GHContent> getDirectoryContent(String path, String ref) throws IOException {
Requester requester = root.retrieve();
String target = String.format("/repos/%s/%s/contents/%s", owner.login, name, path);
while (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
String target = getApiTailUrl("contents/" + path);
if (ref != null)
target = target + "?ref=" + ref;
GHContent[] files = requester.to(target, GHContent[].class);
GHContent[] files = requester.with("ref",ref).to(target, GHContent[].class);
GHContent.wrap(files, this);
return Arrays.asList(files);
}
public GHContent getReadme() throws Exception {
return getFileContent("readme");
/**
* https://developer.github.com/v3/repos/contents/#get-the-readme
*/
public GHContent getReadme() throws IOException {
Requester requester = root.retrieve();
return requester.to(getApiTailUrl("readme"), GHContent.class).wrap(this);
}
public GHContentUpdateResponse createContent(String content, String commitMessage, String path) throws IOException {
return createContent(content.getBytes(), commitMessage, path, null);
return createContent(content, commitMessage, path, null);
}
public GHContentUpdateResponse createContent(String content, String commitMessage, String path, String branch) throws IOException {
return createContent(content.getBytes(), commitMessage, path, branch);
final byte[] payload;
try {
payload = content.getBytes("UTF-8");
} catch (UnsupportedEncodingException ex) {
throw new IOException("UTF-8 encoding is not supported", ex);
}
return createContent(payload, commitMessage, path, branch);
}
public GHContentUpdateResponse createContent(byte[] contentBytes, String commitMessage, String path) throws IOException {
@@ -999,26 +1180,139 @@ public class GHRepository extends GHObject {
return response;
}
public GHMilestone createMilestone(String title, String description) throws IOException {
public GHMilestone createMilestone(String title, String description) throws IOException {
return new Requester(root)
.with("title", title).with("description", description).method("POST").to(getApiTailUrl("milestones"), GHMilestone.class).wrap(this);
}
public GHDeployKey addDeployKey(String title,String key) throws IOException {
return new Requester(root)
}
public GHDeployKey addDeployKey(String title,String key) throws IOException {
return new Requester(root)
.with("title", title).with("key", key).method("POST").to(getApiTailUrl("keys"), GHDeployKey.class).wrap(this);
}
public List<GHDeployKey> getDeployKeys() throws IOException{
List<GHDeployKey> list = new ArrayList<GHDeployKey>(Arrays.asList(
root.retrieve().to(String.format("/repos/%s/%s/keys", owner.login, name), GHDeployKey[].class)));
for (GHDeployKey h : list)
h.wrap(this);
return list;
}
}
public List<GHDeployKey> getDeployKeys() throws IOException{
List<GHDeployKey> list = new ArrayList<GHDeployKey>(Arrays.asList(
root.retrieve().to(getApiTailUrl("keys"), GHDeployKey[].class)));
for (GHDeployKey h : list)
h.wrap(this);
return list;
}
/**
* Forked repositories have a 'source' attribute that specifies the ultimate source of the forking chain.
*
* @return
* {@link GHRepository} that points to the root repository where this repository is forked
* (indirectly or directly) from. Otherwise null.
* @see #getParent()
*/
public GHRepository getSource() throws IOException {
if (source == null) return null;
if (source.root == null)
source = root.getRepository(source.getFullName());
return source;
}
/**
* Forked repositories have a 'parent' attribute that specifies the repository this repository
* is directly forked from. If we keep traversing {@link #getParent()} until it returns null, that
* is {@link #getSource()}.
*
* @return
* {@link GHRepository} that points to the repository where this repository is forked
* directly from. Otherwise null.
* @see #getSource()
*/
public GHRepository getParent() throws IOException {
if (parent == null) return null;
if (parent.root == null)
parent = root.getRepository(parent.getFullName());
return parent;
}
/**
* Subscribes to this repository to get notifications.
*/
public GHSubscription subscribe(boolean subscribed, boolean ignored) throws IOException {
return new Requester(root)
.with("subscribed", subscribed)
.with("ignored", ignored)
.method("PUT").to(getApiTailUrl("subscription"), GHSubscription.class).wrapUp(this);
}
/**
* Returns the current subscription.
*
* @return null if no subscription exists.
*/
public GHSubscription getSubscription() throws IOException {
try {
return root.retrieve().to(getApiTailUrl("subscription"), GHSubscription.class).wrapUp(this);
} catch (FileNotFoundException e) {
return null;
}
}
public PagedIterable<Contributor> listContributors() throws IOException {
return new PagedIterable<Contributor>() {
public PagedIterator<Contributor> _iterator(int pageSize) {
return new PagedIterator<Contributor>(root.retrieve().asIterator(getApiTailUrl("contributors"), Contributor[].class, pageSize)) {
@Override
protected void wrapUp(Contributor[] page) {
for (Contributor c : page)
c.wrapUp(root);
}
};
}
};
}
public static class Contributor extends GHUser {
private int contributions;
public int getContributions() {
return contributions;
}
@Override
public int hashCode() {
// We ignore contributions in the calculation
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
// We ignore contributions in the calculation
return super.equals(obj);
}
}
/**
* Render a Markdown document.
*
* In {@linkplain MarkdownMode#GFM GFM mode}, issue numbers and user mentions
* are linked accordingly.
*
* @see GitHub#renderMarkdown(String)
*/
public Reader renderMarkdown(String text, MarkdownMode mode) throws IOException {
return new InputStreamReader(
new Requester(root)
.with("text", text)
.with("mode",mode==null?null:mode.toString())
.with("context", getFullName())
.asStream("/markdown"),
"UTF-8");
}
/**
* List all the notifications in a repository for the current user.
*/
public GHNotificationStream listNotifications() {
return new GHNotificationStream(root,getApiTailUrl("/notifications"));
}
@Override
public String toString() {
@@ -1041,6 +1335,7 @@ public class GHRepository extends GHObject {
}
String getApiTailUrl(String tail) {
return "/repos/" + owner.login + "/" + name +'/'+tail;
if (tail.length()>0 && !tail.startsWith("/")) tail='/'+tail;
return "/repos/" + owner.login + "/" + name +tail;
}
}

View File

@@ -0,0 +1,82 @@
package org.kohsuke.github;
import java.util.Locale;
/**
* Search repositories.
*
* @author Kohsuke Kawaguchi
* @see GitHub#searchRepositories()
*/
public class GHRepositorySearchBuilder extends GHSearchBuilder<GHRepository> {
/*package*/ GHRepositorySearchBuilder(GitHub root) {
super(root,RepositorySearchResult.class);
}
/**
* Search terms.
*/
public GHRepositorySearchBuilder q(String term) {
super.q(term);
return this;
}
public GHRepositorySearchBuilder in(String v) {
return q("in:"+v);
}
public GHRepositorySearchBuilder size(String v) {
return q("size:"+v);
}
public GHRepositorySearchBuilder forks(String v) {
return q("forks:"+v);
}
public GHRepositorySearchBuilder created(String v) {
return q("created:"+v);
}
public GHRepositorySearchBuilder pushed(String v) {
return q("pushed:"+v);
}
public GHRepositorySearchBuilder user(String v) {
return q("user:"+v);
}
public GHRepositorySearchBuilder repo(String v) {
return q("repo:"+v);
}
public GHRepositorySearchBuilder language(String v) {
return q("language:"+v);
}
public GHRepositorySearchBuilder stars(String v) {
return q("stars:"+v);
}
public GHRepositorySearchBuilder sort(Sort sort) {
req.with("sort",sort);
return this;
}
public enum Sort { STARS, FORKS, UPDATED }
private static class RepositorySearchResult extends SearchResult<GHRepository> {
private GHRepository[] items;
@Override
/*package*/ GHRepository[] getItems(GitHub root) {
for (GHRepository item : items)
item.wrap(root);
return items;
}
}
@Override
protected String getApiUrl() {
return "/search/repositories";
}
}

View File

@@ -0,0 +1,52 @@
package org.kohsuke.github;
import org.apache.commons.lang.StringUtils;
import java.util.ArrayList;
import java.util.List;
/**
* Base class for various search builders.
*
* @author Kohsuke Kawaguchi
*/
public abstract class GHSearchBuilder<T> extends GHQueryBuilder<T> {
protected final List<String> terms = new ArrayList<String>();
/**
* Data transfer object that receives the result of search.
*/
private final Class<? extends SearchResult<T>> receiverType;
/*package*/ GHSearchBuilder(GitHub root, Class<? extends SearchResult<T>> receiverType) {
super(root);
this.receiverType = receiverType;
}
/**
* Search terms.
*/
public GHQueryBuilder<T> q(String term) {
terms.add(term);
return this;
}
/**
* Performs the search.
*/
@Override
public PagedSearchIterable<T> list() {
return new PagedSearchIterable<T>(root) {
public PagedIterator<T> _iterator(int pageSize) {
req.set("q", StringUtils.join(terms, " "));
return new PagedIterator<T>(adapt(req.asIterator(getApiUrl(), receiverType, pageSize))) {
protected void wrapUp(T[] page) {
// SearchResult.getItems() should do it
}
};
}
};
}
protected abstract String getApiUrl();
}

View File

@@ -0,0 +1,64 @@
package org.kohsuke.github;
import java.io.IOException;
import java.util.Date;
/**
* Represents your subscribing to a repository / conversation thread..
*
* @author Kohsuke Kawaguchi
* @see GHRepository#getSubscription()
* @see GHThread#getSubscription()
*/
public class GHSubscription {
private String created_at, url, repository_url, reason;
private boolean subscribed, ignored;
private GitHub root;
private GHRepository repo;
public Date getCreatedAt() {
return GitHub.parseDate(created_at);
}
public String getUrl() {
return url;
}
public String getRepositoryUrl() {
return repository_url;
}
public String getReason() {
return reason;
}
public boolean isSubscribed() {
return subscribed;
}
public boolean isIgnored() {
return ignored;
}
public GHRepository getRepository() {
return repo;
}
/**
* Removes this subscription.
*/
public void delete() throws IOException {
new Requester(root).method("DELETE").to(url);
}
GHSubscription wrapUp(GHRepository repo) {
this.repo = repo;
return wrapUp(repo.root);
}
GHSubscription wrapUp(GitHub root) {
this.root = root;
return this;
}
}

View File

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

View File

@@ -47,13 +47,13 @@ public class GHTeam {
return id;
}
/**
* Retrieves the current members.
*/
public PagedIterable<GHUser> listMembers() throws IOException {
/**
* Retrieves the current members.
*/
public PagedIterable<GHUser> listMembers() throws IOException {
return new PagedIterable<GHUser>() {
public PagedIterator<GHUser> iterator() {
return new PagedIterator<GHUser>(org.root.retrieve().asIterator(api("/members"), GHUser[].class)) {
public PagedIterator<GHUser> _iterator(int pageSize) {
return new PagedIterator<GHUser>(org.root.retrieve().asIterator(api("/members"), GHUser[].class, pageSize)) {
@Override
protected void wrapUp(GHUser[] page) {
GHUser.wrap(page, org.root);
@@ -64,8 +64,8 @@ public class GHTeam {
}
public Set<GHUser> getMembers() throws IOException {
return Collections.unmodifiableSet(listMembers().asSet());
}
return Collections.unmodifiableSet(listMembers().asSet());
}
/**
* Checks if this team has the specified user as a member.
@@ -89,8 +89,8 @@ public class GHTeam {
public PagedIterable<GHRepository> listRepositories() {
return new PagedIterable<GHRepository>() {
public PagedIterator<GHRepository> iterator() {
return new PagedIterator<GHRepository>(org.root.retrieve().asIterator(api("/repos"), GHRepository[].class)) {
public PagedIterator<GHRepository> _iterator(int pageSize) {
return new PagedIterator<GHRepository>(org.root.retrieve().asIterator(api("/repos"), GHRepository[].class, pageSize)) {
@Override
protected void wrapUp(GHRepository[] page) {
for (GHRepository r : page)

View File

@@ -0,0 +1,149 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.Date;
/**
* A conversation in the notification API.
*
* @see <a href="https://developer.github.com/v3/activity/notifications/">documentation</a>
* @see GHNotificationStream
* @author Kohsuke Kawaguchi
*/
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
"NP_UNWRITTEN_FIELD"}, justification = "JSON API")
public class GHThread extends GHObject {
private GitHub root;
private GHRepository repository;
private Subject subject;
private String reason;
private boolean unread;
private String last_read_at;
private String url,subscription_url;
static class Subject {
String title;
String url;
String latest_comment_url;
String type;
}
private GHThread() {// no external construction allowed
}
/**
* Returns null if the entire thread has never been read.
*/
public Date getLastReadAt() {
return GitHub.parseDate(last_read_at);
}
/**
* @deprecated This object has no HTML URL.
*/
@Override
public URL getHtmlUrl() {
return null;
}
public String getReason() {
return reason;
}
public GHRepository getRepository() {
return repository;
}
// TODO: how to expose the subject?
public boolean isRead() {
return !unread;
}
public String getTitle() {
return subject.title;
}
public String getType() {
return subject.type;
}
public String getLastCommentUrl() {
return subject.latest_comment_url;
}
/**
* If this thread is about an issue, return that issue.
*
* @return null if this thread is not about an issue.
*/
public GHIssue getBoundIssue() throws IOException {
if (!"Issue".equals(subject.type) && "PullRequest".equals(subject.type))
return null;
return repository.getIssue(
Integer.parseInt(subject.url.substring(subject.url.lastIndexOf('/') + 1)));
}
/**
* If this thread is about a pull request, return that pull request.
*
* @return null if this thread is not about a pull request.
*/
public GHPullRequest getBoundPullRequest() throws IOException {
if (!"PullRequest".equals(subject.type))
return null;
return repository.getPullRequest(
Integer.parseInt(subject.url.substring(subject.url.lastIndexOf('/') + 1)));
}
/**
* If this thread is about a commit, return that commit.
*
* @return null if this thread is not about a commit.
*/
public GHCommit getBoundCommit() throws IOException {
if (!"Commit".equals(subject.type))
return null;
return repository.getCommit(subject.url.substring(subject.url.lastIndexOf('/') + 1));
}
/*package*/ GHThread wrap(GitHub root) {
this.root = root;
if (this.repository!=null)
this.repository.wrap(root);
return this;
}
/**
* Marks this thread as read.
*/
public void markAsRead() throws IOException {
new Requester(root).method("PATCH").to(url);
}
/**
* Subscribes to this conversation to get notifications.
*/
public GHSubscription subscribe(boolean subscribed, boolean ignored) throws IOException {
return new Requester(root)
.with("subscribed", subscribed)
.with("ignored", ignored)
.method("PUT").to(subscription_url, GHSubscription.class).wrapUp(root);
}
/**
* Returns the current subscription for this thread.
*
* @return null if no subscription exists.
*/
public GHSubscription getSubscription() throws IOException {
try {
return new Requester(root).to(subscription_url, GHSubscription.class).wrapUp(root);
} catch (FileNotFoundException e) {
return null;
}
}
}

View File

@@ -0,0 +1,58 @@
package org.kohsuke.github;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Provides information for Git Trees
* https://developer.github.com/v3/git/trees/
*
* @author Daniel Teixeira - https://github.com/ddtxra
* @see GHRepository#getTree(String)
*/
public class GHTree {
/* package almost final */GitHub root;
private boolean truncated;
private String sha, url;
private GHTreeEntry[] tree;
/**
* The SHA for this trees
*/
public String getSha() {
return sha;
}
/**
* Return an array of entries of the trees
* @return
*/
public List<GHTreeEntry> getTree() {
return Collections.unmodifiableList(Arrays.asList(tree));
}
/**
* Returns true if the number of items in the tree array exceeded the GitHub maximum limit.
* @return true true if the number of items in the tree array exceeded the GitHub maximum limit otherwise false.
*/
public boolean isTruncated() {
return truncated;
}
/**
* The API URL of this tag, such as
* "url": "https://api.github.com/repos/octocat/Hello-World/trees/fc6274d15fa3ae2ab983129fb037999f264ba9a7",
*/
public URL getUrl() {
return GitHub.parseURL(url);
}
/* package */GHTree wrap(GitHub root) {
this.root = root;
return this;
}
}

View File

@@ -0,0 +1,71 @@
package org.kohsuke.github;
import java.net.URL;
/**
* Provides information for Git Trees
* https://developer.github.com/v3/git/trees/
*
* @author Daniel Teixeira - https://github.com/ddtxra
* @see GHTree
*/
public class GHTreeEntry {
private String path, mode, type, sha, url;
private long size;
/**
* Get the path such as
* "subdir/file.txt"
*
* @return the path
*/
public String getPath() {
return path;
}
/**
* Get mode such as
* 100644
*
* @return the mode
*/
public String getMode() {
return mode;
}
/**
* Gets the size of the file, such as
* 132
* @return The size of the path or 0 if it is a directory
*/
public long getSize() {
return size;
}
/**
* Gets the type such as:
* "blob"
*
* @return The type
*/
public String getType() {
return type;
}
/**
* SHA1 of this object.
*/
public String getSha() {
return sha;
}
/**
* API URL to this Git data, such as
* https://api.github.com/repos/jenkinsci
* /jenkins/git/commits/b72322675eb0114363a9a86e9ad5a170d1d07ac0
*/
public URL getUrl() {
return GitHub.parseURL(url);
}
}

View File

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

View File

@@ -0,0 +1,72 @@
package org.kohsuke.github;
import java.util.Locale;
/**
* Search users.
*
* @author Kohsuke Kawaguchi
* @see GitHub#searchUsers()
*/
public class GHUserSearchBuilder extends GHSearchBuilder<GHUser> {
/*package*/ GHUserSearchBuilder(GitHub root) {
super(root,UserSearchResult.class);
}
/**
* Search terms.
*/
public GHUserSearchBuilder q(String term) {
super.q(term);
return this;
}
public GHUserSearchBuilder type(String v) {
return q("type:"+v);
}
public GHUserSearchBuilder in(String v) {
return q("in:"+v);
}
public GHUserSearchBuilder repos(String v) {
return q("repos:"+v);
}
public GHUserSearchBuilder location(String v) {
return q("location:"+v);
}
public GHUserSearchBuilder language(String v) {
return q("language:"+v);
}
public GHUserSearchBuilder created(String v) {
return q("created:"+v);
}
public GHUserSearchBuilder followers(String v) {
return q("followers:"+v);
}
public GHUserSearchBuilder sort(Sort sort) {
req.with("sort",sort);
return this;
}
public enum Sort { FOLLOWERS, REPOSITORIES, JOINED }
private static class UserSearchResult extends SearchResult<GHUser> {
private GHUser[] items;
@Override
/*package*/ GHUser[] getItems(GitHub root) {
return GHUser.wrap(items,root);
}
}
@Override
protected String getApiUrl() {
return "/search/users";
}
}

View File

@@ -26,8 +26,10 @@ package org.kohsuke.github;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URL;
@@ -38,21 +40,31 @@ import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import org.apache.commons.codec.Charsets;
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.nio.charset.Charset;
/**
* Root of the GitHub API.
*
* <h2>Thread safety</h2>
* <p>
* This library aims to be safe for use by multiple threads concurrently, although
* the library itself makes no attempt to control/serialize potentially conflicting
* operations to GitHub, such as updating &amp; deleting a repository at the same time.
*
* @author Kohsuke Kawaguchi
*/
public class GitHub {
@@ -63,11 +75,13 @@ public class GitHub {
*/
/*package*/ final String encodedAuthorization;
private final Map<String,GHUser> users = new HashMap<String, GHUser>();
private final Map<String,GHOrganization> orgs = new HashMap<String, GHOrganization>();
private final Map<String,GHUser> users = new Hashtable<String, GHUser>();
private final Map<String,GHOrganization> orgs = new Hashtable<String, GHOrganization>();
private final String apiUrl;
/*package*/ final RateLimitHandler rateLimitHandler;
private HttpConnector connector = HttpConnector.DEFAULT;
/**
@@ -106,7 +120,7 @@ public class GitHub {
* @param connector
* HttpConnector to use. Pass null to use default connector.
*/
/* package */ GitHub(String apiUrl, String login, String oauthAccessToken, String password, HttpConnector connector) throws IOException {
/* package */ GitHub(String apiUrl, String login, String oauthAccessToken, String password, HttpConnector connector, RateLimitHandler rateLimitHandler) throws IOException {
if (apiUrl.endsWith("/")) apiUrl = apiUrl.substring(0, apiUrl.length()-1); // normalize
this.apiUrl = apiUrl;
if (null != connector) this.connector = connector;
@@ -116,12 +130,15 @@ public class GitHub {
} else {
if (password!=null) {
String authorization = (login + ':' + password);
encodedAuthorization = "Basic "+new String(Base64.encodeBase64(authorization.getBytes()));
Charset charset = Charsets.UTF_8;
encodedAuthorization = "Basic "+new String(Base64.encodeBase64(authorization.getBytes(charset)), charset);
} else {// anonymous access
encodedAuthorization = null;
}
}
this.rateLimitHandler = rateLimitHandler;
if (login==null && encodedAuthorization!=null)
login = getMyself().getLogin();
this.login = login;
@@ -183,6 +200,15 @@ public class GitHub {
return new GitHubBuilder().build();
}
/**
* Connects to GitHub Enterprise anonymously.
*
* All operations that requires authentication will fail.
*/
public static GitHub connectToEnterpriseAnonymously(String apiUrl) throws IOException {
return new GitHubBuilder().withEndpoint(apiUrl).build();
}
/**
* Is this an anonymous connection
* @return {@code true} if operations that require authentication will fail.
@@ -235,16 +261,17 @@ public class GitHub {
// see issue #78
GHRateLimit r = new GHRateLimit();
r.limit = r.remaining = 1000000;
r.reset = new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1));
return r;
}
}
/**
* Gets the {@link GHUser} that represents yourself.
*/
* Gets the {@link GHUser} that represents yourself.
*/
@WithBridgeMethods(GHUser.class)
public GHMyself getMyself() throws IOException {
requireCredential();
public GHMyself getMyself() throws IOException {
requireCredential();
GHMyself u = retrieve().to("/user", GHMyself.class);
@@ -252,20 +279,20 @@ public class GitHub {
users.put(u.getLogin(), u);
return u;
}
}
/**
* Obtains the object that represents the named user.
*/
public GHUser getUser(String login) throws IOException {
GHUser u = users.get(login);
if (u == null) {
/**
* Obtains the object that represents the named user.
*/
public GHUser getUser(String login) throws IOException {
GHUser u = users.get(login);
if (u == null) {
u = retrieve().to("/users/" + login, GHUser.class);
u.root = this;
users.put(u.getLogin(), u);
}
return u;
}
}
return u;
}
/**
@@ -283,7 +310,7 @@ public class GitHub {
GHUser u = users.get(orig.getLogin());
if (u==null) {
orig.root = this;
users.put(login,orig);
users.put(orig.getLogin(),orig);
return orig;
}
return u;
@@ -324,27 +351,27 @@ public class GitHub {
return r;
}
/**
* Gets complete map of organizations/teams that current user belongs to.
*
* Leverages the new GitHub API /user/teams made available recently to
* get in a single call the complete set of organizations, teams and permissions
* in a single call.
*/
public Map<String, Set<GHTeam>> getMyTeams() throws IOException {
Map<String, Set<GHTeam>> allMyTeams = new HashMap<String, Set<GHTeam>>();
for (GHTeam team : retrieve().to("/user/teams", GHTeam[].class)) {
team.wrapUp(this);
String orgLogin = team.getOrganization().getLogin();
Set<GHTeam> teamsPerOrg = allMyTeams.get(orgLogin);
if (teamsPerOrg == null) {
teamsPerOrg = new HashSet<GHTeam>();
}
teamsPerOrg.add(team);
allMyTeams.put(orgLogin, teamsPerOrg);
/**
* Gets complete map of organizations/teams that current user belongs to.
*
* Leverages the new GitHub API /user/teams made available recently to
* get in a single call the complete set of organizations, teams and permissions
* in a single call.
*/
public Map<String, Set<GHTeam>> getMyTeams() throws IOException {
Map<String, Set<GHTeam>> allMyTeams = new HashMap<String, Set<GHTeam>>();
for (GHTeam team : retrieve().to("/user/teams", GHTeam[].class)) {
team.wrapUp(this);
String orgLogin = team.getOrganization().getLogin();
Set<GHTeam> teamsPerOrg = allMyTeams.get(orgLogin);
if (teamsPerOrg == null) {
teamsPerOrg = new HashSet<GHTeam>();
}
teamsPerOrg.add(team);
allMyTeams.put(orgLogin, teamsPerOrg);
}
return allMyTeams;
}
return allMyTeams;
}
/**
* Public events visible to you. Equivalent of what's displayed on https://github.com/
@@ -383,17 +410,28 @@ public class GitHub {
/**
* Creates a new repository.
*
* To create a repository in an organization, see
* {@link GHOrganization#createRepository(String, String, String, GHTeam, boolean)}
*
* @return
* Newly created repository.
* @deprecated
* Use {@link #createRepository(String)} that uses a builder pattern to let you control every aspect.
*/
public GHRepository createRepository(String name, String description, String homepage, boolean isPublic) throws IOException {
Requester requester = new Requester(this)
.with("name", name).with("description", description).with("homepage", homepage)
.with("public", isPublic ? 1 : 0);
return requester.method("POST").to("/user/repos", GHRepository.class).wrap(this);
return createRepository(name).description(description).homepage(homepage).private_(!isPublic).create();
}
/**
* Starts a builder that creates a new repository.
*
* <p>
* You use the returned builder to set various properties, then call {@link GHCreateRepositoryBuilder#create()}
* to finally createa repository.
*
* <p>
* To create a repository in an organization, see
* {@link GHOrganization#createRepository(String, String, String, GHTeam, boolean)}
*/
public GHCreateRepositoryBuilder createRepository(String name) {
return new GHCreateRepositoryBuilder(this,"/user/repos",name);
}
/**
@@ -403,14 +441,14 @@ public class GitHub {
*
* @see <a href="http://developer.github.com/v3/oauth/#create-a-new-authorization">Documentation</a>
*/
public GHAuthorization createToken(Collection<String> scope, String note, String noteUrl) throws IOException{
Requester requester = new Requester(this)
.with("scopes", scope)
.with("note", note)
.with("note_url", noteUrl);
public GHAuthorization createToken(Collection<String> scope, String note, String noteUrl) throws IOException{
Requester requester = new Requester(this)
.with("scopes", scope)
.with("note", note)
.with("note_url", noteUrl);
return requester.method("POST").to("/authorizations", GHAuthorization.class).wrap(this);
}
return requester.method("POST").to("/authorizations", GHAuthorization.class).wrap(this);
}
/**
* Ensures that the credential is valid.
@@ -424,6 +462,29 @@ public class GitHub {
}
}
private static class GHApiInfo {
private String rate_limit_url;
void check(String apiUrl) throws IOException {
if (rate_limit_url==null)
throw new IOException(apiUrl+" doesn't look like GitHub API URL");
// make sure that the URL is legitimate
new URL(rate_limit_url);
}
}
/**
* Ensures that the API URL is valid.
*
* <p>
* This method returns normally if the endpoint is reachable and verified to be GitHub API URL.
* Otherwise this method throws {@link IOException} to indicate the problem.
*/
public void checkApiUrlValidity() throws IOException {
retrieve().to("/", GHApiInfo.class).check(apiUrl);
}
/**
* Search issues.
*/
@@ -431,6 +492,81 @@ public class GitHub {
return new GHIssueSearchBuilder(this);
}
/**
* Search users.
*/
public GHUserSearchBuilder searchUsers() {
return new GHUserSearchBuilder(this);
}
/**
* Search repositories.
*/
public GHRepositorySearchBuilder searchRepositories() {
return new GHRepositorySearchBuilder(this);
}
/**
* Search content.
*/
public GHContentSearchBuilder searchContent() {
return new GHContentSearchBuilder(this);
}
/**
* List all the notifications.
*/
public GHNotificationStream listNotifications() {
return new GHNotificationStream(this,"/notifications");
}
/**
* This provides a dump of every public repository, in the order that they were created.
* @see <a href="https://developer.github.com/v3/repos/#list-all-public-repositories">documentation</a>
*/
public PagedIterable<GHRepository> listAllPublicRepositories() {
return listAllPublicRepositories(null);
}
/**
* This provides a dump of every public repository, in the order that they were created.
*
* @param since
* The integer ID of the last Repository that youve seen. See {@link GHRepository#getId()}
* @see <a href="https://developer.github.com/v3/repos/#list-all-public-repositories">documentation</a>
*/
public PagedIterable<GHRepository> listAllPublicRepositories(final String since) {
return new PagedIterable<GHRepository>() {
public PagedIterator<GHRepository> _iterator(int pageSize) {
return new PagedIterator<GHRepository>(retrieve().with("since",since).asIterator("/repositories", GHRepository[].class, pageSize)) {
@Override
protected void wrapUp(GHRepository[] page) {
for (GHRepository c : page)
c.wrap(GitHub.this);
}
};
}
};
}
/**
* Render a Markdown document in raw mode.
*
* <p>
* It takes a Markdown document as plaintext and renders it as plain Markdown
* without a repository context (just like a README.md file is rendered this
* is the simplest way to preview a readme online).
*
* @see GHRepository#renderMarkdown(String, MarkdownMode)
*/
public Reader renderMarkdown(String text) throws IOException {
return new InputStreamReader(
new Requester(this)
.with(new ByteArrayInputStream(text.getBytes("UTF-8")))
.contentType("text/plain;charset=UTF-8")
.asStream("/markdown/raw"),
"UTF-8");
}
/*package*/ static URL parseURL(String s) {
try {

View File

@@ -1,107 +1,128 @@
package org.kohsuke.github;
import org.apache.commons.io.IOUtils;
import org.kohsuke.github.extras.ImpatientHttpConnector;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Map;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.Properties;
/**
*
*
* @since 1.59
*/
public class GitHubBuilder {
private String endpoint = GitHub.GITHUB_URL;
// default scoped so unit tests can read them.
/* private */ String endpoint = GitHub.GITHUB_URL;
/* private */ String user;
/* private */ String password;
/* private */ String oauthToken;
private HttpConnector connector;
private RateLimitHandler rateLimitHandler = RateLimitHandler.WAIT;
public GitHubBuilder() {
}
/**
* First check if the credentials are configured using the ~/.github properties file.
*
*
* If no user is specified it means there is no configuration present so check the environment instead.
*
*
* If there is still no user it means there are no credentials defined and throw an IOException.
*
*
* @return the configured Builder from credentials defined on the system or in the environment.
*
*
* @throws IOException If there are no credentials defined in the ~/.github properties file or the process environment.
*/
public static GitHubBuilder fromCredentials() throws IOException {
GitHubBuilder builder;
try {
builder = fromPropertyFile();
if (builder.user != null)
return builder;
else {
// this is the case where the ~/.github file exists but has no content.
builder = fromEnvironment();
if (builder.user != null)
return builder;
else
throw new IOException("Failed to resolve credentials from ~/.github or the environment.");
}
} catch (FileNotFoundException e) {
builder = fromEnvironment();
if (builder.user != null)
return builder;
else
throw new IOException("Failed to resolve credentials from ~/.github or the environment.", e);
}
Exception cause = null;
GitHubBuilder builder;
try {
builder = fromPropertyFile();
if (builder.oauthToken != null || builder.user != null)
return builder;
} catch (FileNotFoundException e) {
// fall through
cause = e;
}
builder = fromEnvironment();
if (builder.oauthToken != null || builder.user != null)
return builder;
else
throw (IOException)new IOException("Failed to resolve credentials from ~/.github or the environment.").initCause(cause);
}
/**
* @deprecated
* Use {@link #fromEnvironment()} to pick up standard set of environment variables, so that
* different clients of this library will all recognize one consistent set of coordinates.
*/
public static GitHubBuilder fromEnvironment(String loginVariableName, String passwordVariableName, String oauthVariableName) throws IOException {
Properties env = new Properties();
Object loginValue = System.getenv(loginVariableName);
if (loginValue != null)
env.put("login", loginValue);
Object passwordValue = System.getenv(passwordVariableName);
if (passwordValue != null)
env.put("password", passwordValue);
Object oauthValue = System.getenv(oauthVariableName);
if (oauthValue != null)
env.put("oauth", oauthValue);
return fromProperties(env);
return fromEnvironment(loginVariableName, passwordVariableName, oauthVariableName, "");
}
private static void loadIfSet(String envName, Properties p, String propName) {
String v = System.getenv(envName);
if (v != null)
p.put(propName, v);
}
/**
* @deprecated
* Use {@link #fromEnvironment()} to pick up standard set of environment variables, so that
* different clients of this library will all recognize one consistent set of coordinates.
*/
public static GitHubBuilder fromEnvironment(String loginVariableName, String passwordVariableName, String oauthVariableName, String endpointVariableName) throws IOException {
Properties env = new Properties();
loadIfSet(loginVariableName,env,"login");
loadIfSet(passwordVariableName,env,"password");
loadIfSet(oauthVariableName,env,"oauth");
loadIfSet(endpointVariableName,env,"endpoint");
return fromProperties(env);
}
/**
* Creates {@link GitHubBuilder} by picking up coordinates from environment variables.
*
* <p>
* The following environment variables are recognized:
*
* <ul>
* <li>GITHUB_LOGIN: username like 'kohsuke'
* <li>GITHUB_PASSWORD: raw password
* <li>GITHUB_OAUTH: OAuth token to login
* <li>GITHUB_ENDPOINT: URL of the API endpoint
* </ul>
*
* <p>
* See class javadoc for the relationship between these coordinates.
*
* <p>
* For backward compatibility, the following environment variables are recognized but discouraged:
* login, password, oauth
*/
public static GitHubBuilder fromEnvironment() throws IOException {
Properties props = new Properties();
Map<String, String> env = System.getenv();
for (Map.Entry<String, String> element : env.entrySet()) {
props.put(element.getKey(), element.getValue());
}
Properties props = new Properties();
for (Entry<String, String> e : System.getenv().entrySet()) {
String name = e.getKey().toLowerCase(Locale.ENGLISH);
if (name.startsWith("github_")) name=name.substring(7);
props.put(name,e.getValue());
}
return fromProperties(props);
}
@@ -110,7 +131,7 @@ public class GitHubBuilder {
File propertyFile = new File(homeDir, ".github");
return fromPropertyFile(propertyFile.getPath());
}
public static GitHubBuilder fromPropertyFile(String propertyFileName) throws IOException {
Properties props = new Properties();
FileInputStream in = null;
@@ -128,6 +149,7 @@ public class GitHubBuilder {
GitHubBuilder self = new GitHubBuilder();
self.withOAuthToken(props.getProperty("oauth"), props.getProperty("login"));
self.withPassword(props.getProperty("login"), props.getProperty("password"));
self.withEndpoint(props.getProperty("endpoint", GitHub.GITHUB_URL));
return self;
}
@@ -152,8 +174,25 @@ public class GitHubBuilder {
this.connector = connector;
return this;
}
public GitHubBuilder withRateLimitHandler(RateLimitHandler handler) {
this.rateLimitHandler = handler;
return this;
}
/**
* Configures {@linkplain #withConnector(HttpConnector) connector}
* that uses HTTP library in JRE but use a specific proxy, instead of
* the system default one.
*/
public GitHubBuilder withProxy(final Proxy p) {
return withConnector(new ImpatientHttpConnector(new HttpConnector() {
public HttpURLConnection connect(URL url) throws IOException {
return (HttpURLConnection) url.openConnection(p);
}
}));
}
public GitHub build() throws IOException {
return new GitHub(endpoint, user, oauthToken, password, connector);
return new GitHub(endpoint, user, oauthToken, password, connector, rateLimitHandler);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,29 @@
package org.kohsuke.github;
import java.util.Locale;
/**
* Rendering mode of markdown.
*
* @author Kohsuke Kawaguchi
* @see GitHub#renderMarkdown(String)
* @see GHRepository#renderMarkdown(String, MarkdownMode)
*/
public enum MarkdownMode {
/**
* Render a document as plain Markdown, just like README files are rendered.
*/
MARKDOWN,
/**
* Render a document as user-content, e.g. like user comments or issues are rendered.
* In GFM mode, hard line breaks are always taken into account, and issue and user
* mentions are linked accordingly.
*
* @see GHRepository#renderMarkdown(String, MarkdownMode)
*/
GFM;
public String toString() {
return name().toLowerCase(Locale.ENGLISH);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,61 @@
package org.kohsuke.github;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.HttpURLConnection;
/**
* Pluggable strategy to determine what to do when the API rate limit is reached.
*
* @author Kohsuke Kawaguchi
* @see GitHubBuilder#withRateLimitHandler(RateLimitHandler)
*/
public abstract class RateLimitHandler {
/**
* Called when the library encounters HTTP error indicating that the API rate limit is reached.
*
* <p>
* Any exception thrown from this method will cause the request to fail, and the caller of github-api
* will receive an exception. If this method returns normally, another request will be attempted.
* For that to make sense, the implementation needs to wait for some time.
*
* @see <a href="https://developer.github.com/v3/#rate-limiting">API documentation from GitHub</a>
* @param e
* Exception from Java I/O layer. If you decide to fail the processing, you can throw
* this exception (or wrap this exception into another exception and throw it.)
* @param uc
* Connection that resulted in an error. Useful for accessing other response headers.
*/
public abstract void onError(IOException e, HttpURLConnection uc) throws IOException;
/**
* Block until the API rate limit is reset. Useful for long-running batch processing.
*/
public static final RateLimitHandler WAIT = new RateLimitHandler() {
@Override
public void onError(IOException e, HttpURLConnection uc) throws IOException {
try {
Thread.sleep(parseWaitTime(uc));
} catch (InterruptedException _) {
throw (InterruptedIOException)new InterruptedIOException().initCause(e);
}
}
private long parseWaitTime(HttpURLConnection uc) {
String v = uc.getHeaderField("X-RateLimit-Reset");
if (v==null) return 10000; // can't tell
return Math.max(10000, Long.parseLong(v)*1000 - System.currentTimeMillis());
}
};
/**
* Fail immediately.
*/
public static final RateLimitHandler FAIL = new RateLimitHandler() {
@Override
public void onError(IOException e, HttpURLConnection uc) throws IOException {
throw (IOException)new IOException("API rate limit reached").initCause(e);
}
};
}

View File

@@ -23,13 +23,13 @@
*/
package org.kohsuke.github;
import static org.kohsuke.github.GitHub.MAPPER;
import com.fasterxml.jackson.databind.JsonMappingException;
import org.apache.commons.io.IOUtils;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
@@ -42,19 +42,20 @@ import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import org.apache.commons.io.IOUtils;
import javax.annotation.WillClose;
import javax.net.ssl.HttpsURLConnection;
import static java.util.Arrays.asList;
import static org.kohsuke.github.GitHub.*;
/**
* A builder pattern for making HTTP call and parsing its output.
@@ -62,8 +63,11 @@ import javax.net.ssl.HttpsURLConnection;
* @author Kohsuke Kawaguchi
*/
class Requester {
private static final List<String> METHODS_WITHOUT_BODY = asList("GET", "DELETE");
private final GitHub root;
private final List<Entry> args = new ArrayList<Entry>();
private final Map<String,String> headers = new LinkedHashMap<String, String>();
/**
* Request method.
@@ -72,6 +76,11 @@ class Requester {
private String contentType = "application/x-www-form-urlencoded";
private InputStream body;
/**
* Current connection.
*/
private HttpURLConnection uc;
private static class Entry {
String key;
Object value;
@@ -86,6 +95,15 @@ class Requester {
this.root = root;
}
/**
* Sets the request HTTP header.
*
* If a header of the same name is already set, this method overrides it.
*/
public void setHeader(String name, String value) {
headers.put(name,value);
}
/**
* Makes a request with authentication credential.
*/
@@ -102,13 +120,25 @@ class Requester {
public Requester with(String key, Integer value) {
if (value!=null)
_with(key, value.intValue());
_with(key, value);
return this;
}
public Requester with(String key, boolean value) {
return _with(key, value);
}
public Requester with(String key, Boolean value) {
return _with(key, value);
}
public Requester with(String key, Enum e) {
if (e==null) return _with(key, null);
// by convention Java constant names are upper cases, but github uses
// lower-case constants. GitHub also uses '-', which in Java we always
// replace by '_'
return with(key, e.toString().toLowerCase(Locale.ENGLISH).replace('_','-'));
}
public Requester with(String key, String value) {
return _with(key, value);
@@ -122,7 +152,7 @@ class Requester {
return _with(key, value);
}
public Requester with(InputStream body) {
public Requester with(@WillClose/*later*/ InputStream body) {
this.body = body;
return this;
}
@@ -190,12 +220,20 @@ class Requester {
private <T> T _to(String tailApiUrl, Class<T> type, T instance) throws IOException {
while (true) {// loop while API rate limit is hit
HttpURLConnection uc = setupConnection(root.getApiURL(tailApiUrl));
if (METHODS_WITHOUT_BODY.contains(method) && !args.isEmpty()) {
StringBuilder qs=new StringBuilder();
for (Entry arg : args) {
qs.append(qs.length()==0 ? '?' : '&');
qs.append(arg.key).append('=').append(URLEncoder.encode(arg.value.toString(),"UTF-8"));
}
tailApiUrl += qs.toString();
}
setupConnection(root.getApiURL(tailApiUrl));
buildRequest(uc);
buildRequest();
try {
T result = parse(uc, type, instance);
T result = parse(type, instance);
if (type != null && type.isArray()) { // we might have to loop for pagination - done through recursion
final String links = uc.getHeaderField("link");
if (links != null && links.contains("rel=\"next\"")) {
@@ -216,7 +254,7 @@ class Requester {
}
return result;
} catch (IOException e) {
handleApiError(e,uc);
handleApiError(e);
}
}
}
@@ -226,20 +264,47 @@ class Requester {
*/
public int asHttpStatusCode(String tailApiUrl) throws IOException {
while (true) {// loop while API rate limit is hit
HttpURLConnection uc = setupConnection(root.getApiURL(tailApiUrl));
setupConnection(root.getApiURL(tailApiUrl));
buildRequest(uc);
uc.setRequestMethod("GET");
buildRequest();
try {
return uc.getResponseCode();
} catch (IOException e) {
handleApiError(e,uc);
handleApiError(e);
}
}
}
private void buildRequest(HttpURLConnection uc) throws IOException {
if (!method.equals("GET")) {
public InputStream asStream(String tailApiUrl) throws IOException {
while (true) {// loop while API rate limit is hit
setupConnection(root.getApiURL(tailApiUrl));
// if the download link is encoded with a token on the query string, the default behavior of POST will fail
uc.setRequestMethod("GET");
buildRequest();
try {
return wrapStream(uc.getInputStream());
} catch (IOException e) {
handleApiError(e);
}
}
}
public String getResponseHeader(String header) {
return uc.getHeaderField(header);
}
/**
* Set up the request parameters or POST payload.
*/
private void buildRequest() throws IOException {
if (isMethodWithBody()) {
uc.setDoOutput(true);
uc.setRequestProperty("Content-type", contentType);
@@ -263,117 +328,138 @@ class Requester {
}
}
private boolean isMethodWithBody() {
return !METHODS_WITHOUT_BODY.contains(method);
}
/**
* Loads pagenated resources.
*
* Every iterator call reports a new batch.
*/
/*package*/ <T> Iterator<T> asIterator(String _tailApiUrl, final Class<T> type) {
/*package*/ <T> Iterator<T> asIterator(String tailApiUrl, Class<T> type, int pageSize) {
method("GET");
if (pageSize!=0)
args.add(new Entry("per_page",pageSize));
StringBuilder s = new StringBuilder(tailApiUrl);
if (!args.isEmpty()) {
boolean first=true;
boolean first = true;
try {
for (Entry a : args) {
_tailApiUrl += first ? '?' : '&';
s.append(first ? '?' : '&');
first = false;
_tailApiUrl += URLEncoder.encode(a.key,"UTF-8")+'='+URLEncoder.encode(a.value.toString(),"UTF-8");
s.append(URLEncoder.encode(a.key, "UTF-8"));
s.append('=');
s.append(URLEncoder.encode(a.value.toString(), "UTF-8"));
}
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e); // UTF-8 is mandatory
}
}
final String tailApiUrl = _tailApiUrl;
try {
return new PagingIterator<T>(type, root.getApiURL(s.toString()));
} catch (IOException e) {
throw new Error(e);
}
}
return new Iterator<T>() {
/**
* The next batch to be returned from {@link #next()}.
*/
T next;
/**
* URL of the next resource to be retrieved, or null if no more data is available.
*/
URL url;
class PagingIterator<T> implements Iterator<T> {
{
try {
url = root.getApiURL(tailApiUrl);
} catch (IOException e) {
throw new Error(e);
}
}
private final Class<T> type;
public boolean hasNext() {
fetch();
return next!=null;
}
/**
* The next batch to be returned from {@link #next()}.
*/
private T next;
public T next() {
fetch();
T r = next;
if (r==null) throw new NoSuchElementException();
next = null;
return r;
}
/**
* URL of the next resource to be retrieved, or null if no more data is available.
*/
private URL url;
public void remove() {
throw new UnsupportedOperationException();
}
PagingIterator(Class<T> type, URL url) {
this.url = url;
this.type = type;
}
private void fetch() {
if (next!=null) return; // already fetched
if (url==null) return; // no more data to fetch
public boolean hasNext() {
fetch();
return next!=null;
}
try {
while (true) {// loop while API rate limit is hit
HttpURLConnection uc = setupConnection(url);
try {
next = parse(uc,type,null);
assert next!=null;
findNextURL(uc);
return;
} catch (IOException e) {
handleApiError(e,uc);
}
}
} catch (IOException e) {
throw new Error(e);
}
}
public T next() {
fetch();
T r = next;
if (r==null) throw new NoSuchElementException();
next = null;
return r;
}
/**
* Locate the next page from the pagination "Link" tag.
*/
private void findNextURL(HttpURLConnection uc) throws MalformedURLException {
url = null; // start defensively
String link = uc.getHeaderField("Link");
if (link==null) return;
public void remove() {
throw new UnsupportedOperationException();
}
for (String token : link.split(", ")) {
if (token.endsWith("rel=\"next\"")) {
// found the next page. This should look something like
// <https://api.github.com/repos?page=3&per_page=100>; rel="next"
int idx = token.indexOf('>');
url = new URL(token.substring(1,idx));
private void fetch() {
if (next!=null) return; // already fetched
if (url==null) return; // no more data to fetch
try {
while (true) {// loop while API rate limit is hit
setupConnection(url);
try {
next = parse(type,null);
assert next!=null;
findNextURL();
return;
} catch (IOException e) {
handleApiError(e);
}
}
// no more "next" link. we are done.
} catch (IOException e) {
throw new Error(e);
}
};
}
/**
* Locate the next page from the pagination "Link" tag.
*/
private void findNextURL() throws MalformedURLException {
url = null; // start defensively
String link = uc.getHeaderField("Link");
if (link==null) return;
for (String token : link.split(", ")) {
if (token.endsWith("rel=\"next\"")) {
// found the next page. This should look something like
// <https://api.github.com/repos?page=3&per_page=100>; rel="next"
int idx = token.indexOf('>');
url = new URL(token.substring(1,idx));
return;
}
}
// no more "next" link. we are done.
}
}
private HttpURLConnection setupConnection(URL url) throws IOException {
HttpURLConnection uc = root.getConnector().connect(url);
private void setupConnection(URL url) throws IOException {
uc = root.getConnector().connect(url);
// if the authentication is needed but no credential is given, try it anyway (so that some calls
// that do work with anonymous access in the reduced form should still work.)
if (root.encodedAuthorization!=null)
uc.setRequestProperty("Authorization", root.encodedAuthorization);
for (Map.Entry<String, String> e : headers.entrySet()) {
String v = e.getValue();
if (v!=null)
uc.setRequestProperty(e.getKey(), v);
}
try {
uc.setRequestMethod(method);
} catch (ProtocolException e) {
@@ -387,16 +473,21 @@ class Requester {
}
}
uc.setRequestProperty("Accept-Encoding", "gzip");
return uc;
}
private <T> T parse(HttpURLConnection uc, Class<T> type, T instance) throws IOException {
private <T> T parse(Class<T> type, T instance) throws IOException {
if (uc.getResponseCode()==304)
return null; // special case handling for 304 unmodified, as the content will be ""
InputStreamReader r = null;
try {
r = new InputStreamReader(wrapStream(uc, uc.getInputStream()), "UTF-8");
r = new InputStreamReader(wrapStream(uc.getInputStream()), "UTF-8");
String data = IOUtils.toString(r);
if (type!=null)
return MAPPER.readValue(data,type);
try {
return MAPPER.readValue(data,type);
} catch (JsonMappingException e) {
throw (IOException)new IOException("Failed to deserialize "+data).initCause(e);
}
if (instance!=null)
return MAPPER.readerForUpdating(instance).<T>readValue(data);
return null;
@@ -408,7 +499,7 @@ class Requester {
/**
* Handles the "Content-Encoding" header.
*/
private InputStream wrapStream(HttpURLConnection uc, InputStream in) throws IOException {
private InputStream wrapStream(InputStream in) throws IOException {
String encoding = uc.getContentEncoding();
if (encoding==null || in==null) return in;
if (encoding.equals("gzip")) return new GZIPInputStream(in);
@@ -417,38 +508,29 @@ class Requester {
}
/**
* If the error is because of the API limit, wait 10 sec and return normally.
* Otherwise throw an exception reporting an error.
* Handle API error by either throwing it or by returning normally to retry.
*/
/*package*/ void handleApiError(IOException e, HttpURLConnection uc) throws IOException {
/*package*/ void handleApiError(IOException e) throws IOException {
if (uc.getResponseCode() == 401) // Unauthorized == bad creds
throw e;
if ("0".equals(uc.getHeaderField("X-RateLimit-Remaining"))) {
// API limit reached. wait 10 secs and return normally
try {
Thread.sleep(10000);
return;
} catch (InterruptedException _) {
throw (InterruptedIOException)new InterruptedIOException().initCause(e);
}
root.rateLimitHandler.onError(e,uc);
return;
}
if (e instanceof FileNotFoundException)
throw e; // pass through 404 Not Found to allow the caller to handle it intelligently
InputStream es = wrapStream(uc, uc.getErrorStream());
InputStream es = wrapStream(uc.getErrorStream());
try {
if (es!=null)
throw (IOException)new IOException(IOUtils.toString(es,"UTF-8")).initCause(e);
else
if (es!=null) {
if (e instanceof FileNotFoundException) {
// pass through 404 Not Found to allow the caller to handle it intelligently
throw (IOException) new FileNotFoundException(IOUtils.toString(es, "UTF-8")).initCause(e);
} else
throw (IOException) new IOException(IOUtils.toString(es, "UTF-8")).initCause(e);
} else
throw e;
} finally {
IOUtils.closeQuietly(es);
}
}
private Set<String> toSet(String s) {
Set<String> r = new HashSet<String>();
for (String t : s.split(","))
r.add(t.trim());
return r;
}
}

View File

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

View File

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

View File

@@ -9,8 +9,7 @@ are used in favor of using string handle (such as `GHUser.isMemberOf(GHOrganizat
The library supports both github.com and GitHub Enterprise.
There are some corners of the GitHub API that's not yet implemented, but
the library is implemented with the right abstractions and libraries to make it very easy to improve the coverage.
Most of the GitHub APIs are covered, although there are some corners that are still not yet implemented.
Sample Usage
-----
@@ -42,4 +41,11 @@ This library comes with a pluggable connector to use different HTTP client imple
through `HttpConnector`. In particular, this means you can use [OkHttp](http://square.github.io/okhttp/),
so we can make use of it's HTTP response cache.
Making a conditional request against the GitHub API and receiving a 304 response
[does not count against the rate limit](http://developer.github.com/v3/#conditional-requests).
[does not count against the rate limit](http://developer.github.com/v3/#conditional-requests).
The following code shows an example of how to set up persistent cache on the disk:
Cache cache = new Cache(cacheDirectory, 10 * 1024 * 1024); // 10MB cache
GitHub gitHub = GitHubBuilder.fromCredentials()
.withConnector(new OkHttpConnector(new OkUrlFactory(new OkHttpClient().setCache(cache))))
.build();

View File

@@ -15,6 +15,7 @@
<item name="Introduction" href="/index.html"/>
<item name="Download" href="http://mvnrepository.com/artifact/${project.groupId}/${project.artifactId}"/>
<item name="Source code" href="https://github.com/kohsuke/${project.artifactId}"/>
<item name="Mailing List" href="https://groups.google.com/forum/#!forum/github-api"/>
</menu>
<menu name="References">

View File

@@ -1,22 +1,25 @@
import org.kohsuke.github.GHIssue;
import org.kohsuke.github.GHOrganization;
import org.kohsuke.github.GHTeam;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GHUser;
import org.kohsuke.github.GitHub;
import org.kohsuke.github.PagedIterable;
import org.kohsuke.github.PagedSearchIterable;
import java.util.Arrays;
import java.util.Map;
import java.util.Collection;
/**
* @author Kohsuke Kawaguchi
*/
public class Foo {
public static void main(String[] args) throws Exception {
PagedSearchIterable<GHIssue> reviewbybees = GitHub.connect().searchIssues().mentions("reviewbybees").isOpen().list();
for (GHIssue r : reviewbybees) {
System.out.println(r.getTitle());
Collection<GHRepository> lst = GitHub.connect().getUser("kohsuke").getRepositories().values();
for (GHRepository r : lst) {
System.out.println(r.getName());
}
System.out.println(lst.size());
}
private static void testRateLimit() throws Exception {
GitHub g = GitHub.connectAnonymously();
for (GHUser u : g.getOrganization("jenkinsci").listMembers()) {
u.getFollowersCount();
}
System.out.println("total="+reviewbybees.getTotalCount());
}
}

View File

@@ -1,12 +1,10 @@
package org.kohsuke.github;
import org.apache.commons.io.IOUtils;
import org.junit.Assert;
import org.junit.Before;
import org.kohsuke.randname.RandomNameGenerator;
import java.io.FileInputStream;
import java.util.Properties;
import java.io.File;
/**
* @author Kohsuke Kawaguchi
@@ -17,18 +15,13 @@ public abstract class AbstractGitHubApiTestBase extends Assert {
@Before
public void setUp() throws Exception {
Properties props = new Properties();
java.io.File f = new java.io.File(System.getProperty("user.home"), ".github.kohsuke2");
File f = new File(System.getProperty("user.home"), ".github.kohsuke2");
if (f.exists()) {
FileInputStream in = new FileInputStream(f);
try {
props.load(in);
gitHub = GitHub.connect(props.getProperty("login"),props.getProperty("oauth"));
} finally {
IOUtils.closeQuietly(in);
}
// use the non-standard credential preferentially, so that developers of this library do not have
// to clutter their event stream.
gitHub = GitHubBuilder.fromPropertyFile(f.getPath()).withRateLimitHandler(RateLimitHandler.FAIL).build();
} else {
gitHub = GitHub.connect();
gitHub = GitHubBuilder.fromCredentials().withRateLimitHandler(RateLimitHandler.FAIL).build();
}
}

View File

@@ -3,6 +3,8 @@ package org.kohsuke.github;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
import org.junit.Assume;
import org.junit.Test;
import org.kohsuke.github.GHCommit.File;
@@ -12,6 +14,7 @@ import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Pattern;
/**
* Unit test for simple App.
@@ -35,6 +38,21 @@ public class AppTest extends AbstractGitHubApiTestBase {
getUser().getRepository(targetName).delete();
}
@Test
public void testRepositoryWithAutoInitializationCRUD() throws IOException {
String name = "github-api-test-autoinit";
deleteRepository(name);
GHRepository r = gitHub.createRepository(name)
.description("a test repository for auto init")
.homepage("http://github-api.kohsuke.org/")
.autoInit(true).create();
r.enableIssueTracker(false);
r.enableDownloads(false);
r.enableWiki(false);
assertNotNull(r.getReadme());
getUser().getRepository(name).delete();
}
private void deleteRepository(final String name) throws IOException {
GHRepository repository = getUser().getRepository(name);
if(repository != null) {
@@ -204,31 +222,31 @@ public class AppTest extends AbstractGitHubApiTestBase {
@Test
public void testMyTeamsContainsAllMyOrganizations() throws IOException {
Map<String, Set<GHTeam>> teams = gitHub.getMyTeams();
Map<String, GHOrganization> myOrganizations = gitHub.getMyOrganizations();
assertEquals(teams.keySet(), myOrganizations.keySet());
Map<String, Set<GHTeam>> teams = gitHub.getMyTeams();
Map<String, GHOrganization> myOrganizations = gitHub.getMyOrganizations();
assertEquals(teams.keySet(), myOrganizations.keySet());
}
@Test
public void testMyTeamsShouldIncludeMyself() throws IOException {
Map<String, Set<GHTeam>> teams = gitHub.getMyTeams();
for (Entry<String, Set<GHTeam>> teamsPerOrg : teams.entrySet()) {
String organizationName = teamsPerOrg.getKey();
for (GHTeam team : teamsPerOrg.getValue()) {
String teamName = team.getName();
assertTrue("Team " + teamName + " in organization " + organizationName
+ " does not contain myself",
shouldBelongToTeam(organizationName, teamName));
Map<String, Set<GHTeam>> teams = gitHub.getMyTeams();
for (Entry<String, Set<GHTeam>> teamsPerOrg : teams.entrySet()) {
String organizationName = teamsPerOrg.getKey();
for (GHTeam team : teamsPerOrg.getValue()) {
String teamName = team.getName();
assertTrue("Team " + teamName + " in organization " + organizationName
+ " does not contain myself",
shouldBelongToTeam(organizationName, teamName));
}
}
}
}
private boolean shouldBelongToTeam(String organizationName, String teamName) throws IOException {
GHOrganization org = gitHub.getOrganization(organizationName);
assertNotNull(org);
GHTeam team = org.getTeamByName(teamName);
assertNotNull(team);
return team.hasMember(gitHub.getMyself());
GHOrganization org = gitHub.getOrganization(organizationName);
assertNotNull(org);
GHTeam team = org.getTeamByName(teamName);
assertNotNull(team);
return team.hasMember(gitHub.getMyself());
}
@Test
@@ -283,7 +301,8 @@ public class AppTest extends AbstractGitHubApiTestBase {
@Test
public void testGetTeamsForRepo() throws Exception {
kohsuke();
assertEquals(1, gitHub.getOrganization("github-api-test-org").getRepository("testGetTeamsForRepo").getTeams().size());
// 'Core Developers' and 'Owners'
assertEquals(2, gitHub.getOrganization("github-api-test-org").getRepository("testGetTeamsForRepo").getTeams().size());
}
@Test
@@ -322,6 +341,8 @@ public class AppTest extends AbstractGitHubApiTestBase {
System.out.println(commit);
assertEquals(1, commit.getParents().size());
assertEquals(1,commit.getFiles().size());
assertEquals("https://github.com/jenkinsci/jenkins/commit/08c1c9970af4d609ae754fbe803e06186e3206f7",
commit.getHtmlUrl().toString());
File f = commit.getFiles().get(0);
assertEquals(48,f.getLinesChanged());
@@ -348,7 +369,7 @@ public class AppTest extends AbstractGitHubApiTestBase {
sha1.add(c.getSHA1());
}
assertEquals("1cccddb22e305397151b2b7b87b4b47d74ca337b",sha1.get(0));
assertEquals(29,sha1.size());
assertEquals(29, sha1.size());
}
@Test
@@ -595,10 +616,10 @@ public class AppTest extends AbstractGitHubApiTestBase {
}
@Test
public void testRef() throws IOException {
GHRef masterRef = gitHub.getRepository("jenkinsci/jenkins").getRef("heads/master");
assertEquals("https://api.github.com/repos/jenkinsci/jenkins/git/refs/heads/master", masterRef.getUrl().toString());
}
public void testRef() throws IOException {
GHRef masterRef = gitHub.getRepository("jenkinsci/jenkins").getRef("heads/master");
assertEquals("https://api.github.com/repos/jenkinsci/jenkins/git/refs/heads/master", masterRef.getUrl().toString());
}
@Test
public void directoryListing() throws IOException {
@@ -615,8 +636,8 @@ public class AppTest extends AbstractGitHubApiTestBase {
@Test
public void testAddDeployKey() throws IOException {
GHRepository myRepository = Iterables.get(gitHub.getMyself().getRepositories().values(),0);
final GHDeployKey newDeployKey = myRepository.addDeployKey("test", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDUt0RAycC5cS42JKh6SecfFZBR1RrF+2hYMctz4mk74/arBE+wFb7fnSHGzdGKX2h5CFOWODifRCJVhB7hlVxodxe+QkQQYAEL/x1WVCJnGgTGQGOrhOMj95V3UE5pQKhsKD608C+u5tSofcWXLToP1/wZ7U4/AHjqYi08OLsWToHCax55TZkvdt2jo0hbIoYU+XI9Q8Uv4ONDN1oabiOdgeKi8+crvHAuvNleiBhWVBzFh8KdfzaH5uNdw7ihhFjEd1vzqACsjCINCjdMfzl6jD9ExuWuE92nZJnucls2cEoNC6k2aPmrZDg9hA32FXVpyseY+bDUWFU6LO2LG6PB kohsuke@atlas");
GHRepository myRepository = getTestRepository();
final GHDeployKey newDeployKey = myRepository.addDeployKey("test", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDUt0RAycC5cS42JKh6SecfFZBR1RrF+2hYMctz4mk74/arBE+wFb7fnSHGzdGKX2h5CFOWODifRCJVhB7hlVxodxe+QkQQYAEL/x1WVCJnGgTGQGOrhOMj95V3UE5pQKhsKD608C+u5tSofcWXLToP1/wZ7U4/AHjqYi08OLsWToHCax55TZkvdt2jo0hbIoYU+XI9Q8Uv4ONDN1oabiOdgeKi8+crvHAuvNleiBhWVBzFh8KdfzaH5uNdw7ihhFjEd1vzqACsjCINCjdMfzl6jD9ExuWuE92nZJnucls2cEoNC6k2aPmrZDg9hA32FXVpyseY+bDUWFU6LO2LG6PB kohsuke@atlas");
try {
assertNotNull(newDeployKey.getId());
@@ -633,11 +654,11 @@ public class AppTest extends AbstractGitHubApiTestBase {
@Test
public void testCommitStatusContext() throws IOException {
GHRepository myRepository = Iterables.get(gitHub.getMyself().getRepositories().values(), 0);
GHRef masterRef = myRepository.getRef("heads/master");
GHCommitStatus commitStatus = myRepository.createCommitStatus(masterRef.getObject().getSha(), GHCommitState.SUCCESS, "http://www.example.com", "test", "test/context");
assertEquals("test/context", commitStatus.getContext());
GHRepository myRepository = getTestRepository();
GHRef masterRef = myRepository.getRef("heads/master");
GHCommitStatus commitStatus = myRepository.createCommitStatus(masterRef.getObject().getSha(), GHCommitState.SUCCESS, "http://www.example.com", "test", "test/context");
assertEquals("test/context", commitStatus.getContext());
}
@Test
@@ -658,6 +679,172 @@ public class AppTest extends AbstractGitHubApiTestBase {
}
}
@Test // issue #99
public void testReadme() throws IOException {
GHContent readme = gitHub.getRepository("github-api-test-org/test-readme").getReadme();
assertEquals(readme.getName(),"README.md");
assertEquals(readme.getContent(),"This is a markdown readme.\n");
}
@Test
public void testTrees() throws IOException {
GHTree masterTree = gitHub.getRepository("kohsuke/github-api").getTree("master");
boolean foundReadme = false;
for(GHTreeEntry e : masterTree.getTree()){
if("readme".equalsIgnoreCase(e.getPath().replaceAll("\\.md", ""))){
foundReadme = true;
break;
}
}
assertTrue(foundReadme);
}
@Test
public void testTreesRecursive() throws IOException {
GHTree masterTree = gitHub.getRepository("kohsuke/github-api").getTreeRecursive("master", 1);
boolean foundThisFile = false;
for(GHTreeEntry e : masterTree.getTree()){
if(e.getPath().endsWith(AppTest.class.getSimpleName() + ".java")){
foundThisFile = true;
break;
}
}
assertTrue(foundThisFile);
}
@Test
public void testRepoLabel() throws IOException {
GHRepository r = gitHub.getRepository("github-api-test-org/test-labels");
List<GHLabel> lst = r.listLabels().asList();
for (GHLabel l : lst) {
System.out.println(l.getName());
}
assertTrue(lst.size() > 5);
GHLabel e = r.getLabel("enhancement");
assertEquals("enhancement",e.getName());
assertNotNull(e.getUrl());
assertTrue(Pattern.matches("[0-9a-fA-F]{6}",e.getColor()));
{// CRUD
GHLabel t = r.createLabel("test", "123456");
GHLabel t2 = r.getLabel("test");
assertEquals(t.getName(), t2.getName());
assertEquals(t.getColor(), "123456");
assertEquals(t.getColor(), t2.getColor());
assertEquals(t.getUrl(), t2.getUrl());
t.delete();
}
}
@Test
public void testSubscribers() throws IOException {
boolean kohsuke = false;
GHRepository mr = gitHub.getRepository("kohsuke/github-api");
for (GHUser u : mr.listSubscribers()) {
System.out.println(u.getLogin());
kohsuke |= u.getLogin().equals("kohsuke");
}
assertTrue(kohsuke);
System.out.println("---");
boolean githubApi = false;
for (GHRepository r : gitHub.getUser("kohsuke").listRepositories()) {
System.out.println(r.getName());
githubApi |= r.equals(mr);
}
assertTrue(githubApi);
}
@Test
public void testListAllRepositories() throws Exception {
Iterator<GHRepository> itr = gitHub.listAllPublicRepositories().iterator();
for (int i=0; i<30; i++) {
assertTrue(itr.hasNext());
GHRepository r = itr.next();
System.out.println(r.getFullName());
assertNotNull(r.getUrl());
assertNotEquals(0,r.getId());
}
}
@Test // issue #162
public void testIssue162() throws Exception {
GHRepository r = gitHub.getRepository("kohsuke/github-api");
List<GHContent> contents = r.getDirectoryContent("", "gh-pages");
for (GHContent content : contents) {
if (content.isFile()) {
String content1 = content.getContent();
String content2 = r.getFileContent(content.getPath(), "gh-pages").getContent();
System.out.println(content.getPath());
assertEquals(content1, content2);
}
}
}
@Test
public void markDown() throws Exception {
assertEquals("<p><strong>Test日本語</strong></p>", IOUtils.toString(gitHub.renderMarkdown("**Test日本語**")).trim());
String actual = IOUtils.toString(gitHub.getRepository("kohsuke/github-api").renderMarkdown("@kohsuke to fix issue #1", MarkdownMode.GFM));
System.out.println(actual);
assertTrue(actual.contains("href=\"https://github.com/kohsuke\""));
assertTrue(actual.contains("href=\"https://github.com/kohsuke/github-api/pull/1\""));
assertTrue(actual.contains("class=\"user-mention\""));
assertTrue(actual.contains("class=\"issue-link "));
assertTrue(actual.contains("to fix issue"));
}
@Test
public void searchUsers() throws Exception {
PagedSearchIterable<GHUser> r = gitHub.searchUsers().q("tom").repos(">42").followers(">1000").list();
GHUser u = r.iterator().next();
System.out.println(u.getName());
assertNotNull(u.getId());
assertTrue(r.getTotalCount() > 0);
}
@Test
public void searchRepositories() throws Exception {
PagedSearchIterable<GHRepository> r = gitHub.searchRepositories().q("tetris").language("assembly").sort(GHRepositorySearchBuilder.Sort.STARS).list();
GHRepository u = r.iterator().next();
System.out.println(u.getName());
assertNotNull(u.getId());
assertEquals("Assembly", u.getLanguage());
assertTrue(r.getTotalCount() > 0);
}
@Test
public void searchContent() throws Exception {
PagedSearchIterable<GHContent> r = gitHub.searchContent().q("addClass").in("file").language("js").repo("jquery/jquery").list();
GHContent c = r.iterator().next();
System.out.println(c.getName());
assertNotNull(c.getDownloadUrl());
assertNotNull(c.getOwner());
assertEquals("jquery/jquery",c.getOwner().getFullName());
assertTrue(r.getTotalCount() > 0);
}
@Test
public void notifications() throws Exception {
boolean found=false;
for (GHThread t : gitHub.listNotifications().nonBlocking(true).read(true)) {
if (!found) {
found = true;
t.markAsRead(); // test this by calling it once on old nofication
}
assertNotNull(t.getTitle());
assertNotNull(t.getReason());
System.out.println(t.getTitle());
System.out.println(t.getLastReadAt());
System.out.println(t.getType());
System.out.println();
}
assertTrue(found);
gitHub.listNotifications().markAsRead();
}
private void kohsuke() {
String login = getUser().getLogin();
Assume.assumeTrue(login.equals("kohsuke") || login.equals("kohsuke2"));

View File

@@ -0,0 +1,28 @@
package org.kohsuke.github;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import org.junit.Test;
import java.io.IOException;
/**
* @author Kohsuke Kawaguchi
*/
public class CommitTest extends AbstractGitHubApiTestBase {
@Test // issue 152
public void lastStatus() throws IOException {
GHTag t = gitHub.getRepository("stapler/stapler").listTags().iterator().next();
t.getCommit().getLastStatus();
}
@Test // issue 230
public void listFiles() throws Exception {
GHRepository repo = gitHub.getRepository("stapler/stapler");
PagedIterable<GHCommit> commits = repo.queryCommits().path("pom.xml").list();
for (GHCommit commit : Iterables.limit(commits, 10)) {
GHCommit expected = repo.getCommit( commit.getSHA1() );
assertEquals(expected.getFiles().size(), commit.getFiles().size());
}
}
}

View File

@@ -43,6 +43,14 @@ public class GHContentIntegrationTest extends AbstractGitHubApiTestBase {
assertTrue(entries.size() == 3);
}
@Test
public void testGetDirectoryContentTrailingSlash() throws Exception {
//Used to truncate the ?ref=master, see gh-224 https://github.com/kohsuke/github-api/pull/224
List<GHContent> entries = repo.getDirectoryContent("ghcontent-ro/a-dir-with-3-entries/", "master");
assertTrue(entries.get(0).getUrl().endsWith("?ref=master"));
}
@Test
public void testCRUDContent() throws Exception {
GHContentUpdateResponse created = repo.createContent("this is an awesome file I created\n", "Creating a file for integration tests.", createdFilename);

View File

@@ -0,0 +1,43 @@
package org.kohsuke.github;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
public class GHOrganizationTest extends AbstractGitHubApiTestBase {
public static final String GITHUB_API_TEST = "github-api-test";
private GHOrganization org;
@Override
public void setUp() throws Exception {
super.setUp();
org = gitHub.getOrganization("github-api-test-org");
}
@Test
public void testCreateRepository() throws IOException {
GHRepository repository = org.createRepository(GITHUB_API_TEST,
"a test repository used to test kohsuke's github-api", "http://github-api.kohsuke.org/", "Core Developers", true);
Assert.assertNotNull(repository);
}
@Test
public void testCreateRepositoryWithAutoInitialization() throws IOException {
GHRepository repository = org.createRepository(GITHUB_API_TEST)
.description("a test repository used to test kohsuke's github-api")
.homepage("http://github-api.kohsuke.org/")
.team(org.getTeamByName("Core Developers"))
.autoInit(true).create();
Assert.assertNotNull(repository);
Assert.assertNotNull(repository.getReadme());
}
@After
public void cleanUp() throws Exception {
GHRepository repository = org.getRepository(GITHUB_API_TEST);
repository.delete();
}
}

View File

@@ -1,50 +1,57 @@
package org.kohsuke.github;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import junit.framework.TestCase;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
/**
* Unit test for {@link GitHub}.
*/
public class GitHubTest extends TestCase {
public class GitHubTest {
@Test
public void testGitHubServerWithHttp() throws Exception {
GitHub hub = GitHub.connectToEnterprise("http://enterprise.kohsuke.org/api/v3", "bogus","bogus");
assertEquals("http://enterprise.kohsuke.org/api/v3/test", hub.getApiURL("/test").toString());
}
@Test
public void testGitHubServerWithHttps() throws Exception {
GitHub hub = GitHub.connectToEnterprise("https://enterprise.kohsuke.org/api/v3", "bogus","bogus");
assertEquals("https://enterprise.kohsuke.org/api/v3/test", hub.getApiURL("/test").toString());
}
@Test
public void testGitHubServerWithoutServer() throws Exception {
GitHub hub = GitHub.connectUsingPassword("kohsuke", "bogus");
assertEquals("https://api.github.com/test", hub.getApiURL("/test").toString());
}
@Test
public void testGitHubBuilderFromEnvironment() throws IOException {
Map<String, String>props = new HashMap<String, String>();
props.put("login", "bogus");
props.put("oauth", "bogus");
props.put("password", "bogus");
setupEnvironment(props);
GitHubBuilder builder = GitHubBuilder.fromEnvironment();
assertEquals("bogus", builder.user);
assertEquals("bogus", builder.oauthToken);
assertEquals("bogus", builder.password);
Map<String, String>props = new HashMap<String, String>();
props.put("login", "bogus");
props.put("oauth", "bogus");
props.put("password", "bogus");
setupEnvironment(props);
GitHubBuilder builder = GitHubBuilder.fromEnvironment();
assertEquals("bogus", builder.user);
assertEquals("bogus", builder.oauthToken);
assertEquals("bogus", builder.password);
}
/*
@@ -55,58 +62,68 @@ public class GitHubTest extends TestCase {
* Its used to wire in values for the github credentials to test that the GitHubBuilder works properly to resolve them.
*/
private void setupEnvironment(Map<String, String> newenv) {
try
{
Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
theEnvironmentField.setAccessible(true);
Map<String, String> env = (Map<String, String>) theEnvironmentField.get(null);
env.putAll(newenv);
Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment");
theCaseInsensitiveEnvironmentField.setAccessible(true);
Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null);
cienv.putAll(newenv);
}
catch (NoSuchFieldException e)
{
try {
Class[] classes = Collections.class.getDeclaredClasses();
Map<String, String> env = System.getenv();
for(Class cl : classes) {
if("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {
Field field = cl.getDeclaredField("m");
field.setAccessible(true);
Object obj = field.get(env);
Map<String, String> map = (Map<String, String>) obj;
map.clear();
map.putAll(newenv);
}
}
} catch (Exception e2) {
e2.printStackTrace();
}
} catch (Exception e1) {
e1.printStackTrace();
}
}
public void testGitHubBuilderFromCustomEnvironment() throws IOException {
Map<String, String>props = new HashMap<String, String>();
props.put("customLogin", "bogusLogin");
props.put("customOauth", "bogusOauth");
props.put("customPassword", "bogusPassword");
setupEnvironment(props);
GitHubBuilder builder = GitHubBuilder.fromEnvironment("customLogin", "customPassword", "customOauth");
assertEquals("bogusLogin", builder.user);
assertEquals("bogusOauth", builder.oauthToken);
assertEquals("bogusPassword", builder.password);
try {
Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
theEnvironmentField.setAccessible(true);
Map<String, String> env = (Map<String, String>) theEnvironmentField.get(null);
env.putAll(newenv);
Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment");
theCaseInsensitiveEnvironmentField.setAccessible(true);
Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null);
cienv.putAll(newenv);
} catch (NoSuchFieldException e) {
try {
Class[] classes = Collections.class.getDeclaredClasses();
Map<String, String> env = System.getenv();
for (Class cl : classes) {
if ("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {
Field field = cl.getDeclaredField("m");
field.setAccessible(true);
Object obj = field.get(env);
Map<String, String> map = (Map<String, String>) obj;
map.clear();
map.putAll(newenv);
}
}
} catch (Exception e2) {
e2.printStackTrace();
}
} catch (Exception e1) {
e1.printStackTrace();
}
}
@Test
public void testGitHubBuilderFromCustomEnvironment() throws IOException {
Map<String, String> props = new HashMap<String, String>();
props.put("customLogin", "bogusLogin");
props.put("customOauth", "bogusOauth");
props.put("customPassword", "bogusPassword");
props.put("customEndpoint", "bogusEndpoint");
setupEnvironment(props);
GitHubBuilder builder = GitHubBuilder.fromEnvironment("customLogin", "customPassword", "customOauth", "customEndpoint");
assertEquals("bogusLogin", builder.user);
assertEquals("bogusOauth", builder.oauthToken);
assertEquals("bogusPassword", builder.password);
assertEquals("bogusEndpoint", builder.endpoint);
}
@Test
public void testGitHubEnterpriseDoesNotHaveRateLimit() throws IOException {
GitHub github = spy(new GitHubBuilder().build());
when(github.retrieve()).thenThrow(FileNotFoundException.class);
GHRateLimit rateLimit = github.getRateLimit();
assertThat(rateLimit.getResetDate(), notNullValue());
}
@Test
public void testGitHubIsApiUrlValid() throws IOException {
GitHub github = GitHub.connectAnonymously();
github.checkApiUrlValidity();
}
}

View File

@@ -5,6 +5,7 @@ import org.junit.Test;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
/**
* @author Kohsuke Kawaguchi
@@ -18,18 +19,70 @@ public class PullRequestTest extends AbstractGitHubApiTestBase {
assertEquals(name, p.getTitle());
}
@Test // Requires push access to the test repo to pass
@Test
public void createPullRequestComment() throws Exception {
String name = rnd.next();
GHPullRequest p = getRepository().createPullRequest(name, "stable", "master", "## test");
p.comment("Some comment");
}
@Test
public void testPullRequestReviewComments() throws Exception {
String name = rnd.next();
GHPullRequest p = getRepository().createPullRequest(name, "stable", "master", "## test");
System.out.println(p.getUrl());
assertTrue(p.listReviewComments().asList().isEmpty());
p.createReviewComment("Sample review comment", p.getHead().getSha(), "cli/pom.xml", 5);
List<GHPullRequestReviewComment> comments = p.listReviewComments().asList();
assertEquals(1, comments.size());
GHPullRequestReviewComment comment = comments.get(0);
assertEquals("Sample review comment", comment.getBody());
comment.update("Updated review comment");
comments = p.listReviewComments().asList();
assertEquals(1, comments.size());
comment = comments.get(0);
assertEquals("Updated review comment", comment.getBody());
comment.delete();
comments = p.listReviewComments().asList();
assertTrue(comments.isEmpty());
}
@Test
public void testMergeCommitSHA() throws Exception {
String name = rnd.next();
GHPullRequest p = getRepository().createPullRequest(name, "mergeable-branch", "master", "## test");
for (int i=0; i<100; i++) {
GHPullRequest updated = getRepository().getPullRequest(p.getNumber());
if (updated.getMergeCommitSha()!=null) {
// make sure commit exists
GHCommit commit = getRepository().getCommit(updated.getMergeCommitSha());
assertNotNull(commit);
return;
}
// mergeability computation takes time. give it more chance
Thread.sleep(100);
}
// hmm?
fail();
}
@Test
// Requires push access to the test repo to pass
public void setLabels() throws Exception {
GHPullRequest p = getRepository().createPullRequest(rnd.next(), "stable", "master", "## test");
String label = rnd.next();
p.setLabels(label);
Collection<GHIssue.Label> labels = getRepository().getPullRequest(p.getNumber()).getLabels();
Collection<GHLabel> labels = getRepository().getPullRequest(p.getNumber()).getLabels();
assertEquals(1, labels.size());
assertEquals(label, labels.iterator().next().getName());
}
@Test // Requires push access to the test repo to pass
@Test
// Requires push access to the test repo to pass
public void setAssignee() throws Exception {
GHPullRequest p = getRepository().createPullRequest(rnd.next(), "stable", "master", "## test");
GHMyself user = gitHub.getMyself();
@@ -38,6 +91,22 @@ public class PullRequestTest extends AbstractGitHubApiTestBase {
assertEquals(user, getRepository().getPullRequest(p.getNumber()).getAssignee());
}
@Test
public void testGetUser() throws IOException {
GHPullRequest p = getRepository().createPullRequest(rnd.next(), "stable", "master", "## test");
GHPullRequest prSingle = getRepository().getPullRequest(p.getNumber());
assertNotNull(prSingle.getUser().root);
prSingle.getMergeable();
assertNotNull(prSingle.getUser().root);
PagedIterable<GHPullRequest> ghPullRequests = getRepository().listPullRequests(GHIssueState.OPEN);
for (GHPullRequest pr : ghPullRequests) {
assertNotNull(pr.getUser().root);
pr.getMergeable();
assertNotNull(pr.getUser().root);
}
}
@After
public void cleanUp() throws Exception {
for (GHPullRequest pr : getRepository().getPullRequests(GHIssueState.OPEN)) {

View File

@@ -0,0 +1,89 @@
package org.kohsuke.github;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.Iterator;
import static org.mockito.Mockito.when;
/**
* @author Luciano P. Sabenca (luciano.sabenca [at] movile [com] | lucianosabenca [at] gmail [dot] com
*/
public class RepositoryMockTest {
@Mock
GitHub mockGitHub;
@Mock
Iterator<GHUser[]> iterator;
@Mock
GHRepository mockRepository;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void listCollaborators() throws Exception {
GHUser user1 = new GHUser();
user1.login = "login1";
GHUser user2 = new GHUser();
user2.login = "login2";
when(iterator.hasNext()).thenReturn(true, false, true);
when(iterator.next()).thenReturn(new GHUser[]{user1}, new GHUser[]{user2});
Requester requester = Mockito.mock(Requester.class);
when(mockGitHub.retrieve()).thenReturn(requester);
when(requester.asIterator("/repos/*/*/collaborators",
GHUser[].class, 0)).thenReturn(iterator, iterator);
PagedIterable<GHUser> pagedIterable = Mockito.mock(PagedIterable.class);
when(mockRepository.listCollaborators()).thenReturn(pagedIterable);
PagedIterator<GHUser> userPagedIterator = new PagedIterator<GHUser>(iterator) {
@Override
protected void wrapUp(GHUser[] page) {
}
};
PagedIterator<GHUser> userPagedIterator2 = new PagedIterator<GHUser>(iterator) {
@Override
protected void wrapUp(GHUser[] page) {
}
};
when(pagedIterable.iterator()).thenReturn(userPagedIterator, userPagedIterator2);
Iterator<GHUser> returnIterator1 = mockRepository.listCollaborators().iterator();
Assert.assertTrue(returnIterator1.hasNext());
GHUser user = returnIterator1.next();
Assert.assertEquals(user, user1);
Assert.assertFalse(returnIterator1.hasNext());
Iterator returnIterator2 = mockRepository.listCollaborators().iterator();
Assert.assertTrue(returnIterator2.hasNext());
user = returnIterator1.next();
Assert.assertEquals(user, user2);
}
}

View File

@@ -1,91 +1,53 @@
package org.kohsuke.github;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.kohsuke.github.GHRepository.Contributor;
import java.util.Iterator;
import static org.mockito.Mockito.when;
import java.io.IOException;
/**
* @author Luciano P. Sabenca (luciano.sabenca [at] movile [com] | lucianosabenca [at] gmail [dot] com
* @author Kohsuke Kawaguchi
*/
public class RepositoryTest {
public class RepositoryTest extends AbstractGitHubApiTestBase {
@Test
public void subscription() throws Exception {
GHRepository r = getRepository();
assertNull(r.getSubscription());
@Mock
GitHub mockGitHub;
GHSubscription s = r.subscribe(true, false);
assertEquals(s.getRepository(), r);
@Mock
Iterator<GHUser[]> iterator;
s.delete();
@Mock
GHRepository mockRepository;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
assertNull(r.getSubscription());
}
@Test
public void listCollaborators() throws Exception {
GHUser user1 = new GHUser();
user1.login = "login1";
public void listContributors() throws IOException {
GHRepository r = gitHub.getOrganization("stapler").getRepository("stapler");
int i=0;
boolean kohsuke = false;
GHUser user2 = new GHUser();
user2.login = "login2";
for (Contributor c : r.listContributors()) {
System.out.println(c.getName());
assertTrue(c.getContributions()>0);
if (c.getLogin().equals("kohsuke"))
kohsuke = true;
if (i++ > 5)
break;
}
assertTrue(kohsuke);
}
when(iterator.hasNext()).thenReturn(true, false, true);
when(iterator.next()).thenReturn(new GHUser[]{user1}, new GHUser[]{user2});
Requester requester = Mockito.mock(Requester.class);
when(mockGitHub.retrieve()).thenReturn(requester);
when(requester.asIterator("/repos/*/*/collaborators",
GHUser[].class)).thenReturn(iterator, iterator);
PagedIterable<GHUser> pagedIterable = Mockito.mock(PagedIterable.class);
when(mockRepository.listCollaborators()).thenReturn(pagedIterable);
PagedIterator<GHUser> userPagedIterator = new PagedIterator<GHUser>(iterator) {
@Override
protected void wrapUp(GHUser[] page) {
}
};
PagedIterator<GHUser> userPagedIterator2 = new PagedIterator<GHUser>(iterator) {
@Override
protected void wrapUp(GHUser[] page) {
}
};
when(pagedIterable.iterator()).thenReturn(userPagedIterator, userPagedIterator2);
Iterator<GHUser> returnIterator1 = mockRepository.listCollaborators().iterator();
Assert.assertTrue(returnIterator1.hasNext());
GHUser user = returnIterator1.next();
Assert.assertEquals(user, user1);
Assert.assertFalse(returnIterator1.hasNext());
Iterator returnIterator2 = mockRepository.listCollaborators().iterator();
Assert.assertTrue(returnIterator2.hasNext());
user = returnIterator1.next();
Assert.assertEquals(user, user2);
private GHRepository getRepository() throws IOException {
return gitHub.getOrganization("github-api-test-org").getRepository("jenkins");
}
@Test
public void listLanguages() throws IOException {
GHRepository r = gitHub.getRepository("kohsuke/github-api");
String mainLanguage = r.getLanguage();
assertTrue(r.listLanguages().containsKey(mainLanguage));
}
}

View File

@@ -0,0 +1,30 @@
package org.kohsuke.github;
import org.junit.Test;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
/**
* @author Kohsuke Kawaguchi
*/
public class UserTest extends AbstractGitHubApiTestBase {
@Test
public void listFollowsAndFollowers() throws IOException {
GHUser u = gitHub.getUser("rtyler");
assertNotEquals(
count50(u.listFollowers()),
count50(u.listFollows()));
}
private Set<GHUser> count50(PagedIterable<GHUser> l) {
Set<GHUser> users = new HashSet<GHUser>();
PagedIterator<GHUser> itr = l.iterator();
for (int i=0; i<50 && itr.hasNext(); i++) {
users.add(itr.next());
}
assertEquals(50, users.size());
return users;
}
}