Compare commits

...

167 Commits

Author SHA1 Message Date
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
Kohsuke Kawaguchi
c40100b6da [maven-release-plugin] prepare release github-api-1.61 2015-02-14 10:28:51 -08:00
Kohsuke Kawaguchi
6396818740 MRELEASE-812 botched 1.60 2015-02-14 10:27:13 -08:00
Kohsuke Kawaguchi
6df5a0d47b Added a test 2015-02-14 10:13:25 -08:00
Kohsuke Kawaguchi
15c18c5547 Improved the search API 2015-02-14 10:08:46 -08:00
Kohsuke Kawaguchi
4b6981c2e7 Added issue-search capability 2015-02-14 09:53:29 -08:00
Kohsuke Kawaguchi
1b4025300c Merge remote-tracking branch 'origin/master' 2015-02-14 09:32:53 -08:00
Kohsuke Kawaguchi
983c871bff More compilation error fix 2015-02-14 09:30:50 -08:00
Kohsuke Kawaguchi
609f532f8c Merge pull request #143 2015-02-14 08:29:09 -08:00
Kohsuke Kawaguchi
c6fc03c73a In this library domain objects should be immutable. 2015-02-14 08:28:55 -08:00
Kohsuke Kawaguchi
b6e48cc4f9 Use GHObject as much as we can 2015-02-14 08:27:20 -08:00
Kohsuke Kawaguchi
50f43cc178 Inserted backward compatibility methods 2015-02-14 08:26:49 -08:00
Kohsuke Kawaguchi
f421067a0d Looks like Windows line ends have crept in. 2015-02-14 07:01:32 -08:00
Kohsuke Kawaguchi
edd9a2d5b6 Using newly added GHObject in more places. 2015-02-14 06:59:46 -08:00
Kohsuke Kawaguchi
41e0329f55 doc improvement 2015-02-14 06:52:48 -08:00
Kohsuke Kawaguchi
21ea916e0d Pull request #143 introduced wrong file permissions 2015-02-14 06:48:56 -08:00
Kohsuke Kawaguchi
3b6ca3020e Pull request #143 introduced wrong file permissions 2015-02-14 06:47:52 -08:00
Kohsuke Kawaguchi
3737845b78 Merge pull request #143 2015-02-14 06:47:26 -08:00
Kohsuke Kawaguchi
a30c78cd12 restored backward compatibility in the public API 2015-02-14 06:46:27 -08:00
Kohsuke Kawaguchi
f8fba41a30 Merge pull request #141 from alvaro1728/master
Trivial change to enable creating/updating binary content (files).
2015-02-14 09:42:55 -05:00
Kohsuke Kawaguchi
052902fb49 Merge pull request #146 2015-02-14 06:41:06 -08:00
Surya Gaddipati
58143c26bc Use GHCommitState for deployment status state 2015-01-12 10:04:01 -06:00
Surya Gaddipati
5d83894056 Add method for listing deployments 2015-01-02 16:46:56 -06:00
Rob Schoening
e35667525f fix #145 GHTeam.getMembers() does not page properly 2014-12-28 15:47:57 -08:00
Surya Gaddipati
2f318152d8 Complete api implementation for setting/retriving deployment status on a deployment 2014-12-20 17:09:59 -06:00
Surya Gaddipati
bc518a9ae8 Complete implementation of create deployment api 2014-12-20 11:52:49 -06:00
Kohsuke Kawaguchi
425ae2d536 Merge pull request #142 from suryagaddipati/master
Add code for creating deployments for a repo
2014-12-19 11:45:15 -08:00
Kohsuke Kawaguchi
a6cacd4aba Merge pull request #137 from simonecarriero/master
added 'diverged' constant to GHCompare.Status enum
2014-12-19 11:44:10 -08:00
Kohsuke Kawaguchi
5a7083537c Merge pull request #136 from rtyley/page-team-repositories
Add paging support for Team's Repositories
2014-12-19 11:43:56 -08:00
Surya Gaddipati
e15f7a59fd Merge pull request #139 from farmdawgnation/mocking-scope
Put mockito in the test scope.
2014-12-19 12:53:15 -06:00
Surya Gaddipati
a2fa526aa0 Add code for creating deployments for a repo 2014-12-19 12:47:00 -06:00
mendeza
7e959d6a87 Added binary content support - without tabs. 2014-12-18 21:15:29 -08:00
mendeza
5121fe1cbf Added binary content support. 2014-12-18 21:03:28 -08:00
Matt Farmer
3c3d4fc151 Put mockito in the test scope. 2014-11-16 15:31:04 -05:00
Simone Carriero
a7f75c9a6c added 'diverged' constant to GHCompare.Status enum 2014-11-06 12:18:16 +01:00
Roberto Tyley
e7262b8fbe Add paging support for Team's Repositories
The team repositories endpoint does do paging, so gotta support that.
2014-11-06 09:40:37 +00:00
Kohsuke Kawaguchi
af3099c526 [maven-release-plugin] prepare for next development iteration 2014-10-08 12:17:53 -07:00
Kohsuke Kawaguchi
2b9d47cea8 [maven-release-plugin] prepare release github-api-1.59 2014-10-08 12:17:49 -07:00
Kohsuke Kawaguchi
5db90d3fc4 This test is invalid if ~/.github file exists, which is quite common 2014-10-08 12:15:32 -07:00
Kohsuke Kawaguchi
372d5ff758 Fixed a test regression
Presumably due to the behaviour change on GitHub API?
2014-10-08 12:07:09 -07:00
Kohsuke Kawaguchi
ebf39eaea1 [INFRA-142]
Reworked version of https://github.com/kohsuke/github-api/pull/133/files
2014-10-08 11:29:17 -07:00
Michael O'Cleirigh
556786f2e1 Merge pull request #131 from mocleiri/resolve-credentials-from-environment
Modify GitHubBuilder to resolve user credentials from the system environ...
2014-09-30 14:07:35 -04:00
Michael O'Cleirigh
0f64994537 Make Github.connect() fail if no credentials are setup
With the previous change if no credentials were defined Github.connect() would
fall back on an anonymous connection.

This commit changes the behaviour back to what it was before so that if there
are no credentials defined in the ~/.github file and no credentials defined
in the environment an IOException is thrown to alert the method caller.

The caller can call Github.connectAnonymously() if that scenario is allowed.

This should handle most cases unless callers are depending on the
FileNotFoundException being specifically thrown instead of an IOException.
2014-09-30 13:58:06 -04:00
Michael O'Cleirigh
ac64c2022b Merge pull request #132 from mocleiri/add-getFiles-method-to-GHCompare
Add GHCompare.getFiles() method to be able to see the precise files chan...
2014-09-30 12:03:54 -04:00
Michael O'Cleirigh
9802132b6f Add GHCompare.getFiles() method to be able to see the precise files changed.
There is a file field inside of GHCompare but no getter to extract the values
for analysis.

There are contents in that field so I've added a new get method so that they
can be extracted.
2014-09-30 11:51:57 -04:00
Michael O'Cleirigh
4d6c5c14f1 Modify GitHubBuilder to resolve user credentials from the system environment
Using the Jenkins EnvInject or Credentials Binding Plugins its possible to
pass credentials as Environment Variables.

Its useful for Github.connect() to be able to directly read the values of the
'login', 'password' and 'oauth' properties directly from the environment.

This commit modifies the base Github.connect() method to resolve credentials
in two steps:

1. ~/.github credentials file if it exists.
2. login, password or oauth variables from the environment

A further fromEnvironment() method is provided to support
loading from non-standard variable names.

The old Github.connect() method would throw an IOException if the ~/.github file
did not exist.  Now it will fail silently instead dropping back to the anonymous
users access level.

Added new unit tests into GitHubTest.
2014-09-29 13:42:28 -04:00
Kohsuke Kawaguchi
d228a5fb93 Merge pull request #128 from ndeloof/patch-1
Update github scopes according to https://developer.github.com/v3/oauth/#scopes
2014-09-26 18:10:15 -07:00
Michael O'Cleirigh
7b46ef10c8 Merge pull request #129 from mocleiri/fix-pull-request-remote-repo-get-commit
Allow pullRequest.getHead().getRepository().getCommit(headSha1) to work
2014-09-26 15:14:25 -04:00
Michael O'Cleirigh
8eb9fba051 Allow pullRequest.getHead().getRepository().getCommit(headSha1) to work
The wrong .wrap method was used for pull requests initialized by state
(GHRepository.getPullReqests).

The wrong wrap call was introduced in 9fd34aec7f

This commit sets it back to the .wrapUp method which makes sure the pull request
substructure has the repo object set properly.

Without this change a NullPointerException is thrown on the last line of this
code because the repo object inside of the remoteRepository object is null:

GHRepository repo = github.getRepository(targetRepository);

List<GHPullRequest> openPullRequests = repo.getPullRequests(GHIssueState.OPEN);

for (GHPullRequest pullRequest : openPullRequests) {

    GHCommitPointer head = pullRequest.getHead();

	GHRepository remoteRepository = head.getRepository();

	String commitId = head.getSha();

	GHCommit headCommit = remoteRepository.getCommit(commitId);
2014-09-22 10:53:13 -04:00
Nicolas De loof
7b58182683 Update github scopes according to https://developer.github.com/v3/oauth/#scopes 2014-09-12 23:16:43 +02:00
Kohsuke Kawaguchi
95fbf9274b Merge pull request #124 from ohtake/connector
Allow to use custom HttpConnector when only OAuth token is given
2014-09-04 10:32:11 -07:00
Kohsuke Kawaguchi
09557dfa0f Merge pull request #123 from rtyley/use-issues-endpoints-for-pull-requests
Use issues endpoints for pull requests
2014-09-04 10:31:25 -07:00
Kohsuke Kawaguchi
4029fcc1ca Merge pull request #122 from tbruyelle/browserDownloadUrl
Add missing field browser_download_url in GHAsset
2014-09-04 10:29:23 -07:00
OHTAKE Tomohiro
d6627b1e34 Use custom HttpConnector when only OAuth token is given 2014-09-04 13:18:35 +09:00
OHTAKE Tomohiro
86d75fd767 Introduce GitHubBuilder for flexible GitHub instance creation 2014-09-04 13:17:43 +09:00
Roberto Tyley
e2220bb3b3 Fix setting labels and assignee on PullRequests
Setting labels and assignee on Pull requests failed silently, because
the API endpoint being hit contained '/pulls/' rather than '/issues/'.

"Every pull request is an issue, but not every issue is a pull request.
For this reason, “shared” actions for both features, like manipulating
assignees, labels and milestones, are provided within the Issues API."

https://developer.github.com/v3/pulls/#labels-assignees-and-milestones
2014-09-03 23:34:58 +01:00
Roberto Tyley
1a9b8bd1da Separate out clean-up of Pull requests created during test
If the test assertion fails, we want the pull-request to be closed anyway.
You can't have more than one pull-request merging a given branch into
another, so leaving the PR hanging will cause subsequent test runs to fail
in setup.
2014-09-03 23:18:22 +01:00
Thomas Bruyelle
9eda2d3f77 Add missing field browser_download_url in GHAsset 2014-09-03 22:01:43 +02:00
Kohsuke Kawaguchi
e6d59df705 [maven-release-plugin] prepare for next development iteration 2014-08-30 14:15:29 -07:00
Kohsuke Kawaguchi
db845850b2 [maven-release-plugin] prepare release github-api-1.58 2014-08-30 14:15:25 -07:00
Kohsuke Kawaguchi
d516597659 See if this will get me release with Maven 3.1 2014-08-30 14:13:45 -07:00
Kohsuke Kawaguchi
3af5a8145a Merge branch 'pull-117' 2014-08-30 14:03:58 -07:00
Kohsuke Kawaguchi
31bebd4a7a Design pattern in this library demands that these methods be on GHRef 2014-08-30 14:03:34 -07:00
Kohsuke Kawaguchi
ccb87258b0 Merge pull request #116 from DavidTanner/patch-1
Remove getPath()
2014-08-30 13:54:05 -07:00
Kohsuke Kawaguchi
2ab71e88bd Merge pull request #115 from bernd/add-missing-event-types
Add missing GitHub event types.
2014-08-30 13:53:23 -07:00
Kohsuke Kawaguchi
78bd7585bb Merge pull request #114 from ndeloof/master
get repository full name (including owner)
2014-08-30 13:53:02 -07:00
Kohsuke Kawaguchi
92cc81d9b8 Merge pull request #107 from msperisen/general-pagination
General pagination
2014-08-30 13:52:35 -07:00
Matt Farmer
97a1c741de Implement support for deleting a ref using the GitHub API. 2014-08-22 23:33:04 -04:00
Matt Farmer
077d693959 Add support for updating a ref using the API. 2014-08-22 23:30:22 -04:00
David Tanner
94831fc10a Remove getPath()
When getting the path and providing an enterprise url for the apiUrl, the /api/v3 portion gets duplicated.  Since they will be combined on line 231 of GitHub.java there is no point just grabbing the path.  See https://github.com/janinko/ghprb/issues/178, and https://issues.jenkins-ci.org/browse/JENKINS-24145?focusedCommentId=208270#comment-208270
2014-08-19 13:22:25 -06:00
Surya Gaddipati
1f298a88b0 [maven-release-plugin] prepare for next development iteration 2014-08-19 13:29:40 -05:00
Bernd Ahlers
311180d12e Add missing DEPLOYMENT, DEPLOYMENT_STATUS, RELEASE and STATUS events. 2014-08-18 16:54:17 +02:00
Nicolas De Loof
0efb206881 get repository full name (including owner) 2014-08-06 04:48:46 +02:00
t865095
a58a5b56b2 Merge branch 'master' of https://github.com/kohsuke/github-api 2014-07-08 14:14:59 +02:00
t865095
b9764c0a44 introduce pagination for all paged api calls and introduce cache invalidation 2014-07-08 13:52:53 +02:00
60 changed files with 3410 additions and 929 deletions

19
pom.xml
View File

@@ -3,11 +3,11 @@
<parent>
<groupId>org.kohsuke</groupId>
<artifactId>pom</artifactId>
<version>9</version>
<version>14</version>
</parent>
<artifactId>github-api</artifactId>
<version>1.57</version>
<version>1.67</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>HEAD</tag>
<tag>github-api-1.67</tag>
</scm>
<distributionManagement>
@@ -35,7 +35,7 @@
<plugin>
<groupId>com.infradna.tool</groupId>
<artifactId>bridge-method-injector</artifactId>
<version>1.12</version>
<version>1.14</version>
<executions>
<execution>
<goals>
@@ -77,7 +77,7 @@
<dependency>
<groupId>com.infradna.tool</groupId>
<artifactId>bridge-method-annotation</artifactId>
<version>1.12</version>
<version>1.14</version>
</dependency>
<dependency>
<groupId>org.kohsuke.stapler</groupId>
@@ -107,6 +107,7 @@
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
@@ -138,4 +139,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

@@ -1,26 +1,23 @@
package org.kohsuke.github;
import java.io.IOException;
import java.util.Date;
import java.net.URL;
/**
* Asset in a release.
*
* @see GHRelease#getAssets()
*/
public class GHAsset {
public class GHAsset extends GHObject {
GitHub root;
GHRepository owner;
private String url;
private String id;
private String name;
private String label;
private String state;
private String content_type;
private long size;
private long download_count;
private Date created_at;
private Date updated_at;
private String browser_download_url;
public String getContentType() {
return content_type;
@@ -31,18 +28,10 @@ public class GHAsset {
this.content_type = contentType;
}
public Date getCreatedAt() {
return created_at;
}
public long getDownloadCount() {
return download_count;
}
public String getId() {
return id;
}
public String getLabel() {
return label;
}
@@ -72,12 +61,16 @@ public class GHAsset {
return state;
}
public Date getUpdatedAt() {
return updated_at;
/**
* @deprecated This object has no HTML URL.
*/
@Override
public URL getHtmlUrl() {
return null;
}
public String getUrl() {
return url;
public String getBrowserDownloadUrl() {
return browser_download_url;
}
private void edit(String key, Object value) throws IOException {

View File

@@ -10,85 +10,83 @@ 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 {
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 class GHAuthorization extends GHObject {
public static final String USER = "user";
public static final String USER_EMAIL = "user:email";
public static final String USER_FOLLOW = "user:follow";
public static final String PUBLIC_REPO = "public_repo";
public static final String REPO = "repo";
public static final String REPO_STATUS = "repo:status";
public static final String DELETE_REPO = "delete_repo";
public static final String NOTIFICATIONS = "notifications";
public static final String GIST = "gist";
public static final String READ_HOOK = "read:repo_hook";
public static final String WRITE_HOOK = "write:repo_hook";
public static final String AMIN_HOOK = "admin:repo_hook";
public static final String READ_ORG = "read:org";
public static final String WRITE_ORG = "write:org";
public static final String ADMIN_ORG = "admin:org";
public static final String READ_KEY = "read:public_key";
public static final String WRITE_KEY = "write:public_key";
public static final String ADMIN_KEY = "admin:public_key";
private GitHub root;
private int id;
private String url;
private List<String> scopes;
private String token;
private App app;
private String note;
private String note_url;
private String updated_at;
private String created_at;
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 int getId() {
return id;
}
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;
}
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);
}
public Date getCreatedAt() {
return GitHub.parseDate(created_at);
}
public Date getUpdatedAt() {
return GitHub.parseDate(updated_at);
/*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;
}
private static class App {
private String url;
private String name;
}
}

View File

@@ -32,26 +32,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;
}
}
/**
@@ -153,14 +153,13 @@ public class GHCommit {
Stats stats;
List<Parent> parents;
User author,committer;
public ShortInfo getCommitShortInfo() {
return commit;
}
return commit;
}
/**
/**
* The repository that contains the commit.
*/
public GHRepository getOwner() {

View File

@@ -12,13 +12,11 @@ import java.util.Date;
* @see GHCommit#listComments()
* @see GHCommit#createComment(String, String, Integer, Integer)
*/
public class GHCommitComment {
public class GHCommitComment extends GHObject {
private GHRepository owner;
String updated_at, created_at;
String body, url, html_url, commit_id;
String body, html_url, commit_id;
Integer line;
int id;
String path;
User user;
@@ -32,14 +30,6 @@ public class GHCommitComment {
return owner;
}
public Date getCreatedAt() {
return GitHub.parseDate(created_at);
}
public Date getUpdatedAt() {
return GitHub.parseDate(updated_at);
}
/**
* URL like 'https://github.com/kohsuke/sandbox-ant/commit/8ae38db0ea5837313ab5f39d43a6f73de3bd9000#commitcomment-1252827' to
* show this commit comment in a browser.
@@ -75,10 +65,6 @@ public class GHCommitComment {
return line!=null ? line : -1;
}
public int getId() {
return id;
}
/**
* Gets the user who put this comment.
*/

View File

@@ -1,21 +1,19 @@
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 {
String created_at, updated_at;
public class GHCommitStatus extends GHObject {
String state;
String target_url,description;
int id;
String url;
String context;
GHUser creator;
@@ -27,14 +25,6 @@ public class GHCommitStatus {
return this;
}
public Date getCreatedAt() {
return GitHub.parseDate(created_at);
}
public Date getUpdatedAt() {
return GitHub.parseDate(updated_at);
}
public GHCommitState getState() {
for (GHCommitState s : GHCommitState.values()) {
if (s.name().equalsIgnoreCase(state))
@@ -56,22 +46,19 @@ public class GHCommitStatus {
return description;
}
public int getId() {
return id;
}
/**
* API URL of this commit status.
*/
public String getUrl() {
return url;
}
public GHUser getCreator() {
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

@@ -68,7 +68,10 @@ public class GHCompare {
public Commit[] getCommits() {
return commits;
}
public GHCommit.File[] getFiles() {
return files;
}
public GHCompare wrap(GHRepository owner) {
this.owner = owner;
@@ -145,6 +148,6 @@ public class GHCompare {
}
public static enum Status {
behind, ahead, identical
behind, ahead, identical, diverged
}
}

View File

@@ -1,8 +1,10 @@
package org.kohsuke.github;
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 +16,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 +34,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 +67,34 @@ 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()}
*/
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 +109,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 +132,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.
*/
@@ -118,10 +151,10 @@ public class GHContent {
return new PagedIterable<GHContent>() {
public PagedIterator<GHContent> iterator() {
return new PagedIterator<GHContent>(owner.root.retrieve().asIterator(url, GHContent[].class)) {
return new PagedIterator<GHContent>(root.retrieve().asIterator(url, GHContent[].class)) {
@Override
protected void wrapUp(GHContent[] page) {
GHContent.wrap(page,owner);
GHContent.wrap(page, repository);
}
};
}
@@ -129,13 +162,21 @@ public class GHContent {
}
public GHContentUpdateResponse update(String newContent, String commitMessage) throws IOException {
return update(newContent, commitMessage, null);
return update(newContent.getBytes(), commitMessage, null);
}
public GHContentUpdateResponse update(String newContent, String commitMessage, String branch) throws IOException {
String encodedContent = DatatypeConverter.printBase64Binary(newContent.getBytes());
return update(newContent.getBytes(), commitMessage, branch);
}
Requester requester = new Requester(owner.root)
public GHContentUpdateResponse update(byte[] newContentBytes, String commitMessage) throws IOException {
return update(newContentBytes, commitMessage, null);
}
public GHContentUpdateResponse update(byte[] newContentBytes, String commitMessage, String branch) throws IOException {
String encodedContent = DatatypeConverter.printBase64Binary(newContentBytes);
Requester requester = new Requester(root)
.with("path", path)
.with("message", commitMessage)
.with("sha", sha)
@@ -148,8 +189,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;
@@ -160,7 +201,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)
@@ -172,18 +213,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

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

@@ -0,0 +1,61 @@
package org.kohsuke.github;
import java.net.URL;
public class GHDeployment extends GHObject {
private GHRepository owner;
private GitHub root;
protected String sha;
protected String ref;
protected String task;
protected Object payload;
protected String environment;
protected String description;
protected String statuses_url;
protected String repository_url;
protected GHUser creator;
GHDeployment wrap(GHRepository owner) {
this.owner = owner;
this.root = owner.root;
if(creator != null) creator.wrapUp(root);
return this;
}
public URL getStatusesUrl() {
return GitHub.parseURL(statuses_url);
}
public URL getRepositoryUrl() {
return GitHub.parseURL(repository_url);
}
public String getTask() {
return task;
}
public String getPayload() {
return (String) payload;
}
public String getEnvironment() {
return environment;
}
public GHUser getCreator() {
return creator;
}
public String getRef() {
return ref;
}
public String getSha(){
return sha;
}
/**
* @deprecated This object has no HTML URL.
*/
@Override
public URL getHtmlUrl() {
return null;
}
}

View File

@@ -0,0 +1,55 @@
package org.kohsuke.github;
import java.io.IOException;
import java.util.List;
//Based on https://developer.github.com/v3/repos/deployments/#create-a-deployment
public class GHDeploymentBuilder {
private final GHRepository repo;
private final Requester builder;
public GHDeploymentBuilder(GHRepository repo) {
this.repo = repo;
this.builder = new Requester(repo.root);
}
public GHDeploymentBuilder(GHRepository repo, String ref) {
this(repo);
ref(ref);
}
public GHDeploymentBuilder ref(String branch) {
builder.with("ref",branch);
return this;
}
public GHDeploymentBuilder task(String task) {
builder.with("task",task);
return this;
}
public GHDeploymentBuilder autoMerge(boolean autoMerge) {
builder.with("auto_merge",autoMerge);
return this;
}
public GHDeploymentBuilder requiredContexts(List<String> requiredContexts) {
builder.with("required_contexts",requiredContexts);
return this;
}
public GHDeploymentBuilder payload(String payload) {
builder.with("payload",payload);
return this;
}
public GHDeploymentBuilder environment(String environment) {
builder.with("environment",environment);
return this;
}
public GHDeploymentBuilder description(String description) {
builder.with("description",description);
return this;
}
public GHDeployment create() throws IOException {
return builder.to(repo.getApiTailUrl("deployments"),GHDeployment.class).wrap(repo);
}
}

View File

@@ -0,0 +1,8 @@
package org.kohsuke.github;
/**
* Represents the state of deployment
*/
public enum GHDeploymentState {
PENDING, SUCCESS, ERROR, FAILURE
}

View File

@@ -0,0 +1,42 @@
package org.kohsuke.github;
import java.net.URL;
public class GHDeploymentStatus extends GHObject {
private GHRepository owner;
private GitHub root;
protected GHUser creator;
protected String state;
protected String description;
protected String target_url;
protected String deployment_url;
protected String repository_url;
public GHDeploymentStatus wrap(GHRepository owner) {
this.owner = owner;
this.root = owner.root;
if(creator != null) creator.wrapUp(root);
return this;
}
public URL getTargetUrl() {
return GitHub.parseURL(target_url);
}
public URL getDeploymentUrl() {
return GitHub.parseURL(deployment_url);
}
public URL getRepositoryUrl() {
return GitHub.parseURL(repository_url);
}
public GHDeploymentState getState() {
return GHDeploymentState.valueOf(state.toUpperCase());
}
/**
* @deprecated This object has no HTML URL.
*/
@Override
public URL getHtmlUrl() {
return null;
}
}

View File

@@ -0,0 +1,30 @@
package org.kohsuke.github;
import java.io.IOException;
public class GHDeploymentStatusBuilder {
private final Requester builder;
private GHRepository repo;
private int deploymentId;
public GHDeploymentStatusBuilder(GHRepository repo, int deploymentId, GHDeploymentState state) {
this.repo = repo;
this.deploymentId = deploymentId;
this.builder = new Requester(repo.root);
this.builder.with("state",state.toString().toLowerCase());
}
public GHDeploymentStatusBuilder description(String description) {
this.builder.with("description",description);
return this;
}
public GHDeploymentStatusBuilder targetUrl(String targetUrl) {
this.builder.with("target_url",targetUrl);
return this;
}
public GHDeploymentStatus create() throws IOException {
return builder.to(repo.getApiTailUrl("deployments")+"/"+deploymentId+"/statuses",GHDeploymentStatus.class).wrap(repo);
}
}

View File

@@ -12,6 +12,8 @@ public enum GHEvent {
COMMIT_COMMENT,
CREATE,
DELETE,
DEPLOYMENT,
DEPLOYMENT_STATUS,
DOWNLOAD,
FOLLOW,
FORK,
@@ -25,6 +27,8 @@ public enum GHEvent {
PULL_REQUEST,
PULL_REQUEST_REVIEW_COMMENT,
PUSH,
RELEASE,
STATUS,
TEAM_ADD,
WATCH
}

View File

@@ -120,6 +120,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 +159,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,8 +3,8 @@ 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.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
@@ -16,17 +16,18 @@ 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 {
public class GHGist extends GHObject {
/*package almost final*/ GHUser owner;
/*package almost final*/ GitHub root;
private String url, forks_url, commits_url, id, git_pull_url, git_push_url, html_url;
private String forks_url, commits_url, id, git_pull_url, git_push_url, html_url;
@JsonProperty("public")
private boolean _public;
private String created_at, updated_at, description;
private String description;
private int comments;
@@ -41,13 +42,6 @@ public class GHGist {
return owner;
}
/**
* API URL of this gist, such as 'https://api.github.com/gists/12345'
*/
public String getUrl() {
return url;
}
public String getForksUrl() {
return forks_url;
}
@@ -56,13 +50,6 @@ public class GHGist {
return commits_url;
}
/**
* ID of this gist, such as '12345'
*/
public String getId() {
return id;
}
/**
* URL like https://gist.github.com/gists/12345.git
*/
@@ -74,22 +61,14 @@ public class GHGist {
return git_push_url;
}
public String getHtmlUrl() {
return html_url;
public URL getHtmlUrl() {
return GitHub.parseURL(html_url);
}
public boolean isPublic() {
return _public;
}
public Date getCreatedAt() {
return GitHub.parseDate(created_at);
}
public Date getUpdatedAt() {
return GitHub.parseDate(updated_at);
}
public String getDescription() {
return description;
}

View File

@@ -1,6 +1,7 @@
package org.kohsuke.github;
import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
@@ -10,18 +11,17 @@ import java.util.Map;
/**
* @author Kohsuke Kawaguchi
*/
public class GHHook {
public class GHHook extends GHObject {
/**
* Repository that the hook belongs to.
*/
/*package*/ transient GHRepository repository;
String created_at, updated_at, name;
String name;
List<String> events;
boolean active;
Map<String,String> config;
int id;
/*package*/ GHHook wrap(GHRepository owner) {
this.repository = owner;
return this;
@@ -46,14 +46,18 @@ public class GHHook {
return Collections.unmodifiableMap(config);
}
public int getId() {
return id;
}
/**
* 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));
}
/**
* @deprecated This object has no HTML URL.
*/
@Override
public URL getHtmlUrl() {
return null;
}
}

View File

@@ -37,52 +37,46 @@ import java.util.Locale;
*
* @author Eric Maupin
* @author Kohsuke Kawaguchi
* @see GHRepository#getIssue(int)
* @see GitHub#searchIssues()
* @see GHIssueSearchBuilder
*/
public class GHIssue {
public class GHIssue extends GHObject {
GitHub root;
GHRepository owner;
// API v3
// 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, created_at, html_url;
protected String title, html_url;
protected GHIssue.PullRequest pull_request;
protected GHMilestone milestone;
protected String url, updated_at;
protected int id;
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) {
this.owner = owner;
this.root = owner.root;
if(milestone != null) milestone.wrap(owner);
if(assignee != null) assignee.wrapUp(root);
if(user != null) user.wrapUp(root);
if(closed_by != null) closed_by.wrapUp(root);
if(milestone != null) milestone.wrap(owner);
return wrap(owner.root);
}
/*package*/ GHIssue wrap(GitHub root) {
this.root = root;
if(assignee != null) assignee.wrapUp(root);
if(user != null) user.wrapUp(root);
if(closed_by != null) closed_by.wrapUp(root);
return this;
}
@@ -117,7 +111,7 @@ public class GHIssue {
* The HTML page of this issue,
* like https://github.com/jenkinsci/jenkins/issues/100
*/
public URL getUrl() {
public URL getHtmlUrl() {
return GitHub.parseURL(html_url);
}
@@ -129,28 +123,20 @@ public class GHIssue {
return Enum.valueOf(GHIssueState.class, state.toUpperCase(Locale.ENGLISH));
}
public Collection<Label> getLabels() {
public Collection<GHLabel> getLabels() throws IOException {
if(labels == null){
return Collections.EMPTY_LIST;
return Collections.emptyList();
}
return Collections.unmodifiableList(labels);
}
public Date getCreatedAt() {
return GitHub.parseDate(created_at);
}
public Date getUpdatedAt() {
return GitHub.parseDate(updated_at);
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.
@@ -163,6 +149,10 @@ public class GHIssue {
new Requester(root)._with(key, value).method("PATCH").to(getApiRoute());
}
private void editIssue(String key, Object value) throws IOException {
new Requester(root)._with(key, value).method("PATCH").to(getIssuesApiRoute());
}
/**
* Closes this issue.
*/
@@ -186,25 +176,25 @@ public class GHIssue {
}
public void assignTo(GHUser user) throws IOException {
edit("assignee",user.getLogin());
editIssue("assignee",user.getLogin());
}
public void setLabels(String... labels) throws IOException {
edit("labels",labels);
editIssue("labels",labels);
}
/**
* 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() {
@@ -222,54 +212,69 @@ public class GHIssue {
return getIssuesApiRoute();
}
private String getIssuesApiRoute() {
protected String getIssuesApiRoute() {
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;
}
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

@@ -32,12 +32,10 @@ import java.util.Date;
*
* @author Kohsuke Kawaguchi
*/
public class GHIssueComment {
public class GHIssueComment extends GHObject {
GHIssue owner;
private String body, gravatar_id, created_at, updated_at;
private URL url;
private int id;
private String body, gravatar_id;
private GHUser user;
/*package*/ GHIssueComment wrapUp(GHIssue owner) {
@@ -59,22 +57,6 @@ public class GHIssueComment {
return body;
}
public Date getCreatedAt() {
return GitHub.parseDate(created_at);
}
public Date getUpdatedAt() {
return GitHub.parseDate(updated_at);
}
public int getId() {
return id;
}
public URL getUrl() {
return url;
}
/**
* Gets the ID of the user who posted this comment.
*/
@@ -89,4 +71,12 @@ public class GHIssueComment {
public GHUser getUser() throws IOException {
return owner.root.getUser(user.getLogin());
}
/**
* @deprecated This object has no HTML URL.
*/
@Override
public URL getHtmlUrl() {
return null;
}
}

View File

@@ -0,0 +1,66 @@
package org.kohsuke.github;
import java.util.Locale;
/**
* Search issues.
*
* @author Kohsuke Kawaguchi
* @see GitHub#searchIssues()
*/
public class GHIssueSearchBuilder extends GHSearchBuilder<GHIssue> {
/*package*/ GHIssueSearchBuilder(GitHub root) {
super(root,IssueSearchResult.class);
}
/**
* Search terms.
*/
public GHIssueSearchBuilder q(String term) {
super.q(term);
return this;
}
public GHIssueSearchBuilder mentions(GHUser u) {
return mentions(u.getLogin());
}
public GHIssueSearchBuilder mentions(String login) {
return q("mentions:"+login);
}
public GHIssueSearchBuilder isOpen() {
return q("is:open");
}
public GHIssueSearchBuilder isClosed() {
return q("is:closed");
}
public GHIssueSearchBuilder isMerged() {
return q("is:merged");
}
public GHIssueSearchBuilder sort(Sort sort) {
req.with("sort",sort.toString().toLowerCase(Locale.ENGLISH));
return this;
}
public enum Sort { COMMENTS, CREATED, UPDATED }
private static class IssueSearchResult extends SearchResult<GHIssue> {
private GHIssue[] items;
@Override
/*package*/ GHIssue[] getItems(GitHub root) {
for (GHIssue i : items)
i.wrap(root);
return items;
}
}
@Override
protected String getApiUrl() {
return "/search/issues";
}
}

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;
@@ -9,62 +10,58 @@ import java.util.Locale;
* @author Yusuke Kokubo
*
*/
public class GHMilestone {
public class GHMilestone extends GHObject {
GitHub root;
GHRepository owner;
GHRepository owner;
GHUser creator;
private String state, due_on, title, url, created_at, 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;
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 getUrl() {
return url;
}
public Date getCreatedAt() {
return GitHub.parseDate(created_at);
}
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);
}
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.
@@ -81,9 +78,9 @@ public class GHMilestone {
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

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

View File

@@ -0,0 +1,69 @@
package org.kohsuke.github;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import java.io.IOException;
import java.net.URL;
import java.util.Date;
/**
* Most (all?) domain objects in GitHub seems to have these 4 properties.
*/
public abstract class GHObject {
protected String url;
protected int id;
protected String created_at;
protected String updated_at;
/*package*/ GHObject() {
}
/**
* When was this resource created?
*/
@WithBridgeMethods(value=String.class, adapterMethod="createdAtStr")
public Date getCreatedAt() throws IOException {
return GitHub.parseDate(created_at);
}
private Object createdAtStr(Date id, Class type) {
return created_at;
}
/**
* API URL of this object.
*/
@WithBridgeMethods(value=String.class, adapterMethod="urlToString")
public URL getUrl() {
return GitHub.parseURL(url);
}
/**
* URL of this object for humans, which renders some HTML.
*/
@WithBridgeMethods(value=String.class, adapterMethod="urlToString")
public abstract URL getHtmlUrl();
/**
* When was this resource last updated?
*/
public Date getUpdatedAt() throws IOException {
return GitHub.parseDate(updated_at);
}
/**
* Unique ID number of this resource.
*/
@WithBridgeMethods(value=String.class, adapterMethod="intToString")
public int getId() {
return id;
}
private Object intToString(int id, Class type) {
return String.valueOf(id);
}
private Object urlToString(URL url, Class type) {
return url==null ? null : url.toString();
}
}

View File

@@ -2,8 +2,10 @@ package org.kohsuke.github;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -14,15 +16,14 @@ import java.util.TreeMap;
*
* @author Kohsuke Kawaguchi
*/
public abstract class GHPerson {
public abstract class GHPerson extends GHObject {
/*package almost final*/ GitHub root;
// core data fields that exist even for "small" user data (such as the user info in pull request)
protected String login, avatar_url, url, gravatar_id;
protected int id;
protected String login, avatar_url, gravatar_id;
// other fields (that only show up in full data)
protected String location,blog,email,name,created_at,company;
protected String location,blog,email,name,company;
protected String html_url;
protected int followers,following,public_repos,public_gists;
@@ -36,7 +37,7 @@ public abstract class GHPerson {
*
* 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);
@@ -196,9 +197,14 @@ public abstract class GHPerson {
return location;
}
public String getCreatedAt() throws IOException {
public Date getCreatedAt() throws IOException {
populate();
return created_at;
return super.getCreatedAt();
}
public Date getUpdatedAt() throws IOException {
populate();
return super.getCreatedAt();
}
/**
@@ -209,8 +215,9 @@ public abstract class GHPerson {
return blog;
}
public String getHtmlUrl() {
return html_url;
@Override
public URL getHtmlUrl() {
return GitHub.parseURL(html_url);
}
/**
@@ -236,13 +243,6 @@ public abstract class GHPerson {
return following;
}
/**
* What appears to be a GitHub internal unique number that identifies this user.
*/
public int getId() {
return id;
}
public int getFollowersCount() throws IOException {
populate();
return followers;

View File

@@ -37,31 +37,38 @@ 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;
/**
* GitHub doesn't return some properties of {@link GHIssue} when requesting the GET on the 'pulls' API
* route as opposed to 'issues' API route. This flag remembers whether we made the GET call on the 'issues' route
* on this object to fill in those missing details
*/
private transient boolean fetchedIssueDetails;
GHPullRequest wrapUp(GHRepository owner) {
this.wrap(owner);
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;
}
@@ -78,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
*/
@@ -101,9 +108,9 @@ public class GHPullRequest extends GHIssue {
public GHCommitPointer getHead() {
return head;
}
@Deprecated
public Date getIssueUpdatedAt() {
@Deprecated
public Date getIssueUpdatedAt() throws IOException {
return super.getUpdatedAt();
}
@@ -119,64 +126,65 @@ public class GHPullRequest extends GHIssue {
return GitHub.parseDate(merged_at);
}
@Override
public Collection<Label> getLabels() {
return super.getLabels();
}
@Override
public Collection<GHLabel> getLabels() throws IOException {
fetchIssue();
return super.getLabels();
}
@Override
public GHUser getClosedBy() {
return null;
}
@Override
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
//
//
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;
}
/**
* Fully populate the data by retrieving missing data.
@@ -186,7 +194,7 @@ 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);
}
/**
@@ -196,7 +204,7 @@ public class GHPullRequest extends GHIssue {
return new PagedIterable<GHPullRequestCommitDetail>() {
public PagedIterator<GHPullRequestCommitDetail> iterator() {
return new PagedIterator<GHPullRequestCommitDetail>(root.retrieve().asIterator(
String.format("%s/commits", getApiURL().getPath()),
String.format("%s/commits", getApiURL()),
GHPullRequestCommitDetail[].class)) {
@Override
protected void wrapUp(GHPullRequestCommitDetail[] page) {
@@ -218,4 +226,10 @@ public class GHPullRequest extends GHIssue {
new Requester(root).method("PUT").with("commit_message",msg).to(getApiRoute()+"/merge");
}
private void fetchIssue() throws IOException {
if (!fetchedIssueDetails) {
new Requester(root).to(getIssuesApiRoute(), this);
fetchedIssueDetails = true;
}
}
}

View File

@@ -1,5 +1,7 @@
package org.kohsuke.github;
import java.util.Date;
/**
* Rate limit.
* @author Kohsuke Kawaguchi
@@ -10,12 +12,28 @@ 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
*/
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 java.io.IOException;
import java.net.URL;
/**
@@ -8,6 +9,7 @@ import java.net.URL;
* @author Michael Clarke
*/
public class GHRef {
/*package almost final*/ GitHub root;
private String ref, url;
private GHObject object;
@@ -33,6 +35,48 @@ public class GHRef {
return object;
}
/**
* Updates this ref to the specified commit.
*
* @param sha
* The SHA1 value to set this reference to
*/
public void updateTo(String sha) throws IOException {
updateTo(sha, false);
}
/**
* Updates this ref to the specified commit.
*
* @param sha
* The SHA1 value to set this reference to
* @param force
* Whether or not to force this ref update.
*/
public void updateTo(String sha, Boolean force) throws IOException {
new Requester(root)
.with("sha", sha).with("force", force).method("PATCH").to(url, GHRef.class).wrap(root);
}
/**
* Deletes this ref from the repository using the GitHub API.
*/
public void delete() throws IOException {
new Requester(root).method("DELETE").to(url);
}
/*package*/ GHRef wrap(GitHub root) {
this.root = root;
return this;
}
/*package*/ static GHRef[] wrap(GHRef[] in, GitHub root) {
for (GHRef r : in) {
r.wrap(root);
}
return in;
}
public static class GHObject {
private String type, sha, url;

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;
@@ -15,22 +16,19 @@ import static java.lang.String.format;
* @see GHRepository#getReleases()
* @see GHRepository#createRelease(String)
*/
public class GHRelease {
public class GHRelease extends GHObject {
GitHub root;
GHRepository owner;
private String url;
private String html_url;
private String assets_url;
private String upload_url;
private long id;
private String tag_name;
private String target_commitish;
private String name;
private String body;
private boolean draft;
private boolean prerelease;
private Date created_at;
private Date published_at;
private String tarball_url;
private String zipball_url;
@@ -39,48 +37,16 @@ public class GHRelease {
return assets_url;
}
public void setAssetsUrl(String assets_url) {
this.assets_url = assets_url;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public Date getCreatedAt() {
return created_at;
}
public void setCreatedAt(Date created_at) {
this.created_at = created_at;
}
public boolean isDraft() {
return draft;
}
public void setDraft(boolean draft) {
this.draft = draft;
}
public String getHtmlUrl() {
return html_url;
}
public void setHtmlUrl(String html_url) {
this.html_url = html_url;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
public URL getHtmlUrl() {
return GitHub.parseURL(html_url);
}
public String getName() {
@@ -103,74 +69,34 @@ public class GHRelease {
return prerelease;
}
public void setPrerelease(boolean prerelease) {
this.prerelease = prerelease;
}
public Date getPublished_at() {
return published_at;
}
public void setPublished_at(Date published_at) {
this.published_at = published_at;
}
public GitHub getRoot() {
return root;
}
public void setRoot(GitHub root) {
this.root = root;
}
public String getTagName() {
return tag_name;
}
public void setTagName(String tag_name) {
this.tag_name = tag_name;
}
public String getTargetCommitish() {
return target_commitish;
}
public void setTargetCommitish(String target_commitish) {
this.target_commitish = target_commitish;
}
public String getUploadUrl() {
return upload_url;
}
public void setUploadUrl(String upload_url) {
this.upload_url = upload_url;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getZipballUrl() {
return zipball_url;
}
public void setZipballUrl(String zipballUrl) {
this.zipball_url = zipballUrl;
}
public String getTarballUrl() {
return tarball_url;
}
public void setTarballUrl(String tarballUrl) {
this.tarball_url = tarballUrl;
}
GHRelease wrap(GHRepository owner) {
this.owner = owner;
this.root = owner.root;
@@ -195,7 +121,7 @@ public class GHRelease {
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))

View File

@@ -25,28 +25,18 @@ package org.kohsuke.github;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import java.io.FileNotFoundException;
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.net.URL;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
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;
import java.util.*;
import static java.util.Arrays.*;
import static java.util.Arrays.asList;
/**
* A repository on GitHub.
@@ -54,11 +44,10 @@ import static java.util.Arrays.*;
* @author Kohsuke Kawaguchi
*/
@SuppressWarnings({"UnusedDeclaration"})
public class GHRepository {
public class GHRepository extends GHObject {
/*package almost final*/ GitHub root;
private String description, homepage, name;
private String url; // this is the API url
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 GHUser owner; // not fully populated. beware.
@@ -66,7 +55,7 @@ public class GHRepository {
@JsonProperty("private")
private boolean _private;
private int watchers,forks,open_issues,size,network_count,subscribers_count;
private String created_at, pushed_at;
private String pushed_at;
private Map<Integer,GHMilestone> milestones = new HashMap<Integer, GHMilestone>();
private String default_branch,language;
@@ -74,6 +63,61 @@ public class GHRepository {
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)) {
@Override
protected void wrapUp(GHDeploymentStatus[] page) {
for (GHDeploymentStatus c : page)
c.wrap(GHRepository.this);
}
};
}
};
}
public PagedIterable<GHDeployment> listDeployments(String sha,String ref,String task,String environment){
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)) {
@Override
protected void wrapUp(GHDeployment[] page) {
for (GHDeployment c : page)
c.wrap(GHRepository.this);
}
};
}
};
}
private String join(List<String> params, String joinStr) {
StringBuilder output = new StringBuilder();
for(String param: params){
if(param != null){
output.append(param+joinStr);
}
}
return output.toString();
}
private String getParam(String name, String value) {
return StringUtils.trimToNull(value)== null? null: name+"="+value;
}
public GHDeploymentStatusBuilder createDeployStatus(int deploymentId, GHDeploymentState ghDeploymentState) {
return new GHDeploymentStatusBuilder(this,deploymentId,ghDeploymentState);
}
private static class GHRepoPermission {
boolean pull,push,admin;
}
@@ -87,13 +131,6 @@ public class GHRepository {
return homepage;
}
/**
* URL of this repository, like 'http://github.com/kohsuke/jenkins'
*/
public String getUrl() {
return html_url;
}
/**
* Gets the git:// URL to this repository, such as "git://github.com/kohsuke/jenkins.git"
* This URL is read-only.
@@ -124,6 +161,10 @@ public class GHRepository {
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
*/
@@ -131,6 +172,13 @@ public class GHRepository {
return name;
}
/**
* Full repository name including the owner or organization. For example 'jenkinsci/jenkins' in case of http://github.com/jenkinsci/jenkins
*/
public String getFullName() {
return full_name;
}
public boolean hasPullAccess() {
return permissions!=null && permissions.pull;
}
@@ -155,7 +203,7 @@ public class GHRepository {
}
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) {
@@ -168,8 +216,8 @@ public class GHRepository {
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()),
.to(getApiTailUrl(String.format("issues?state=%s&milestone=%s",
state.toString().toLowerCase(), milestone == null ? "none" : "" + milestone.getNumber())),
GHIssue[].class
), this));
}
@@ -206,7 +254,7 @@ public class GHRepository {
*/
public GHRef createRef(String name, String sha) throws IOException {
return new Requester(root)
.with("ref", name).with("sha", sha).method("POST").to(getApiTailUrl("git/refs"), GHRef.class);
.with("ref", name).with("sha", sha).method("POST").to(getApiTailUrl("git/refs"), GHRef.class).wrap(root);
}
/**
@@ -245,7 +293,19 @@ public class GHRepository {
};
}
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;
}
@@ -298,12 +358,8 @@ public class GHRepository {
return GitHub.parseDate(pushed_at);
}
public Date getCreatedAt() {
return GitHub.parseDate(created_at);
}
/**
* 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.
@@ -335,7 +391,7 @@ public class GHRepository {
return new PagedIterable<GHUser>() {
public PagedIterator<GHUser> iterator() {
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)) {
@Override
protected void wrapUp(GHUser[] users) {
@@ -356,7 +412,7 @@ public class GHRepository {
*/
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;
}
@@ -365,7 +421,7 @@ public class GHRepository {
* 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 {
@@ -387,7 +443,7 @@ public class GHRepository {
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()));
}
}
@@ -395,14 +451,14 @@ public class GHRepository {
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));
.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(""));
}
/**
@@ -443,7 +499,7 @@ public class GHRepository {
*/
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);
}
@@ -456,7 +512,7 @@ public class GHRepository {
* 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);
}
/**
@@ -466,7 +522,7 @@ public class GHRepository {
* 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++) {
@@ -485,7 +541,7 @@ public class GHRepository {
* 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);
}
/**
@@ -503,11 +559,11 @@ public class GHRepository {
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)) {
return new PagedIterator<GHPullRequest>(root.retrieve().asIterator(getApiTailUrl("pulls?state="+state.name().toLowerCase(Locale.ENGLISH)), GHPullRequest[].class)) {
@Override
protected void wrapUp(GHPullRequest[] page) {
for (GHPullRequest pr : page)
pr.wrap(GHRepository.this);
pr.wrapUp(GHRepository.this);
}
};
}
@@ -542,14 +598,14 @@ public class GHRepository {
*/
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)));
root.retrieve().to(getApiTailUrl("hooks"), GHHook[].class)));
for (GHHook h : list)
h.wrap(this);
return list;
}
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 root.retrieve().to(getApiTailUrl("hooks/" + id), GHHook.class).wrap(this);
}
/**
@@ -561,7 +617,7 @@ public class GHRepository {
* @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);
}
@@ -579,31 +635,60 @@ public class GHRepository {
* @throws IOException on failure communicating with GitHub
*/
public GHRef[] getRefs() throws IOException {
return root.retrieve().to(String.format("/repos/%s/%s/git/refs", owner.login, name), GHRef[].class);
return GHRef.wrap(root.retrieve().to(String.format("/repos/%s/%s/git/refs", owner.login, name), GHRef[].class),root);
}
/**
* Retrienved all refs of the given type for the current GitHub repository.
* Retrieves all refs of the given type for the current GitHub repository.
* @param refType the type of reg to search for e.g. <tt>tags</tt> or <tt>commits</tt>
* @return an array of all refs matching the request type
* @throws IOException on failure communicating with GitHub, potentially due to an invalid ref type being requested
*/
public GHRef[] getRefs(String refType) throws IOException {
return root.retrieve().to(String.format("/repos/%s/%s/git/refs/%s", owner.login, name, refType), GHRef[].class);
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);
}
* 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.
*/
@@ -701,10 +786,10 @@ public class GHRepository {
}
/**
* @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);
}
/**
@@ -724,6 +809,54 @@ public class GHRepository {
};
}
/**
* 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() {
return new PagedIterator<GHLabel>(root.retrieve().asIterator(getApiTailUrl("labels"), GHLabel[].class)) {
@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 new PagedIterable<GHUser>() {
public PagedIterator<GHUser> iterator() {
return new PagedIterator<GHUser>(root.retrieve().asIterator(getApiTailUrl("subscribers"), GHUser[].class)) {
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.
@@ -862,10 +995,10 @@ public class GHRepository {
*/
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;
}
/**
@@ -885,16 +1018,16 @@ public class GHRepository {
};
}
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);
@@ -902,12 +1035,9 @@ public class GHRepository {
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 {
@@ -916,31 +1046,40 @@ public class GHRepository {
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);
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, commitMessage, path, null);
return createContent(content.getBytes(), commitMessage, path, null);
}
public GHContentUpdateResponse createContent(String content, String commitMessage, String path, String branch) throws IOException {
return createContent(content.getBytes(), commitMessage, path, branch);
}
public GHContentUpdateResponse createContent(byte[] contentBytes, String commitMessage, String path) throws IOException {
return createContent(contentBytes, commitMessage, path, null);
}
public GHContentUpdateResponse createContent(byte[] contentBytes, String commitMessage, String path, String branch) throws IOException {
Requester requester = new Requester(root)
.with("path", path)
.with("message", commitMessage)
.with("content", DatatypeConverter.printBase64Binary(content.getBytes()))
.with("content", DatatypeConverter.printBase64Binary(contentBytes))
.method("PUT");
if (branch != null) {
@@ -955,26 +1094,127 @@ public class GHRepository {
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 new Requester(root).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() {
return new PagedIterator<Contributor>(root.retrieve().asIterator(getApiTailUrl("contributors"), Contributor[].class)) {
@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;
}
}
/**
* 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() {
@@ -997,6 +1237,7 @@ public class GHRepository {
}
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.toString().toLowerCase(Locale.ENGLISH));
return this;
}
public enum Sort { STARS, FORKS, UPDATED }
private static class RepositorySearchResult extends SearchResult<GHRepository> {
private GHRepository[] items;
@Override
/*package*/ GHRepository[] getItems(GitHub root) {
for (GHRepository item : items)
item.wrap(root);
return items;
}
}
@Override
protected String getApiUrl() {
return "/search/repositories";
}
}

View File

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

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

@@ -15,6 +15,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

@@ -1,8 +1,7 @@
package org.kohsuke.github;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
@@ -51,8 +50,21 @@ public class GHTeam {
/**
* Retrieves the current members.
*/
public PagedIterable<GHUser> listMembers() throws IOException {
return new PagedIterable<GHUser>() {
public PagedIterator<GHUser> iterator() {
return new PagedIterator<GHUser>(org.root.retrieve().asIterator(api("/members"), GHUser[].class)) {
@Override
protected void wrapUp(GHUser[] page) {
GHUser.wrap(page, org.root);
}
};
}
};
}
public Set<GHUser> getMembers() throws IOException {
return new HashSet<GHUser>(Arrays.asList(GHUser.wrap(org.root.retrieve().to(api("/members"), GHUser[].class), org.root)));
return Collections.unmodifiableSet(listMembers().asSet());
}
/**
@@ -68,19 +80,36 @@ public class GHTeam {
}
public Map<String,GHRepository> getRepositories() throws IOException {
GHRepository[] repos = org.root.retrieve().to(api("/repos"), GHRepository[].class);
Map<String,GHRepository> m = new TreeMap<String, GHRepository>();
for (GHRepository r : repos) {
m.put(r.getName(),r.wrap(org.root));
for (GHRepository r : listRepositories()) {
m.put(r.getName(), r);
}
return m;
}
public PagedIterable<GHRepository> listRepositories() {
return new PagedIterable<GHRepository>() {
public PagedIterator<GHRepository> iterator() {
return new PagedIterator<GHRepository>(org.root.retrieve().asIterator(api("/repos"), GHRepository[].class)) {
@Override
protected void wrapUp(GHRepository[] page) {
for (GHRepository r : page)
r.wrap(org.root);
}
};
}
};
}
/**
* Adds a member to the team.
*
* The user will be invited to the organization if required.
*
* @since 1.59
*/
public void add(GHUser u) throws IOException {
org.root.retrieve().method("PUT").to(api("/members/" + u.getLogin()), null);
org.root.retrieve().method("PUT").to(api("/memberships/" + u.getLogin()), null);
}
/**

View File

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

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

@@ -69,6 +69,24 @@ public class GHUser extends GHPerson {
return new GHPersonSet<GHUser>(Arrays.asList(wrap(followers,root)));
}
/**
* Lists all the subscribed (aka watched) repositories.
*
* https://developer.github.com/v3/activity/watching/
*/
public PagedIterable<GHRepository> listSubscriptions() {
return new PagedIterable<GHRepository>() {
public PagedIterator<GHRepository> iterator() {
return new PagedIterator<GHRepository>(root.retrieve().asIterator(getApiTailUrl("subscriptions"), GHRepository[].class)) {
protected void wrapUp(GHRepository[] page) {
for (GHRepository c : page)
c.wrap(root);
}
};
}
};
}
/**
* Returns true if this user belongs to the specified organization.
*/
@@ -162,4 +180,9 @@ public class GHUser extends GHPerson {
}
return false;
}
String getApiTailUrl(String tail) {
if (tail.length()>0 && !tail.startsWith("/")) tail='/'+tail;
return "/users/" + login + tail;
}
}

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.toString().toLowerCase(Locale.ENGLISH));
return this;
}
public enum Sort { FOLLOWERS, REPOSITORIES, JOINED }
private static class UserSearchResult extends SearchResult<GHUser> {
private GHUser[] items;
@Override
/*package*/ GHUser[] getItems(GitHub root) {
return GHUser.wrap(items,root);
}
}
@Override
protected String getApiUrl() {
return "/search/users";
}
}

View File

@@ -26,10 +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.File;
import java.io.FileInputStream;
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;
@@ -40,14 +40,13 @@ import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TimeZone;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -57,6 +56,12 @@ import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
/**
* Root of the GitHub API.
*
* <h2>Thread safety</h2>
* <p>
* This library aims to be safe for use by multiple threads concurrently, although
* the library itself makes no attempt to control/serialize potentially conflicting
* operations to GitHub, such as updating &amp; deleting a repository at the same time.
*
* @author Kohsuke Kawaguchi
*/
public class GitHub {
@@ -67,19 +72,14 @@ 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;
private HttpConnector connector = HttpConnector.DEFAULT;
/*package*/ final RateLimitHandler rateLimitHandler;
/**
* Connects to GitHub.com
*/
private GitHub(String login, String oauthAccessToken, String password) throws IOException {
this (GITHUB_URL, login, oauthAccessToken, password);
}
private HttpConnector connector = HttpConnector.DEFAULT;
/**
* Creates a client API root object.
@@ -114,10 +114,13 @@ public class GitHub {
* Secret OAuth token.
* @param password
* User's password. Always used in conjunction with the {@code login} parameter
* @param connector
* HttpConnector to use. Pass null to use default connector.
*/
private GitHub(String apiUrl, String login, String oauthAccessToken, String password) 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;
if (oauthAccessToken!=null) {
encodedAuthorization = "token "+oauthAccessToken;
@@ -133,21 +136,14 @@ public class GitHub {
if (login==null && encodedAuthorization!=null)
login = getMyself().getLogin();
this.login = login;
this.rateLimitHandler = rateLimitHandler;
}
/**
* Obtains the credential from "~/.github"
* Obtains the credential from "~/.github" or from the System Environment Properties.
*/
public static GitHub connect() throws IOException {
Properties props = new Properties();
File homeDir = new File(System.getProperty("user.home"));
FileInputStream in = new FileInputStream(new File(homeDir, ".github"));
try {
props.load(in);
} finally {
IOUtils.closeQuietly(in);
}
return new GitHub(GITHUB_URL,props.getProperty("login"), props.getProperty("oauth"),props.getProperty("password"));
return GitHubBuilder.fromCredentials().build();
}
/**
@@ -159,15 +155,15 @@ public class GitHub {
* For historical reasons, this parameter still accepts the bare domain name, but that's considered deprecated.
*/
public static GitHub connectToEnterprise(String apiUrl, String oauthAccessToken) throws IOException {
return connectUsingOAuth(apiUrl, oauthAccessToken);
return new GitHubBuilder().withEndpoint(apiUrl).withOAuthToken(oauthAccessToken).build();
}
public static GitHub connectToEnterprise(String apiUrl, String login, String password) throws IOException {
return new GitHub(apiUrl, login, null, password);
return new GitHubBuilder().withEndpoint(apiUrl).withPassword(login, password).build();
}
public static GitHub connect(String login, String oauthAccessToken) throws IOException {
return new GitHub(login,oauthAccessToken,null);
return new GitHubBuilder().withOAuthToken(oauthAccessToken, login).build();
}
/**
@@ -176,19 +172,19 @@ public class GitHub {
* Use {@link #connectUsingPassword(String, String)} or {@link #connectUsingOAuth(String)}.
*/
public static GitHub connect(String login, String oauthAccessToken, String password) throws IOException {
return new GitHub(login,oauthAccessToken,password);
return new GitHubBuilder().withOAuthToken(oauthAccessToken, login).withPassword(login, password).build();
}
public static GitHub connectUsingPassword(String login, String password) throws IOException {
return new GitHub(login,null,password);
return new GitHubBuilder().withPassword(login, password).build();
}
public static GitHub connectUsingOAuth(String oauthAccessToken) throws IOException {
return new GitHub(null, oauthAccessToken, null);
return new GitHubBuilder().withOAuthToken(oauthAccessToken).build();
}
public static GitHub connectUsingOAuth(String githubServer, String oauthAccessToken) throws IOException {
return new GitHub(githubServer,null, oauthAccessToken,null);
return new GitHubBuilder().withEndpoint(githubServer).withOAuthToken(oauthAccessToken).build();
}
/**
* Connects to GitHub anonymously.
@@ -196,7 +192,7 @@ public class GitHub {
* All operations that requires authentication will fail.
*/
public static GitHub connectAnonymously() throws IOException {
return new GitHub(null,null,null);
return new GitHubBuilder().build();
}
/**
@@ -256,11 +252,11 @@ public class GitHub {
}
/**
* 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);
@@ -268,20 +264,29 @@ 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;
}
/**
* clears all cached data in order for external changes (modifications and del
*/
public void refreshCache() {
users.clear();
orgs.clear();
}
/**
* Interns the given {@link GHUser}.
@@ -290,7 +295,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;
@@ -331,33 +336,32 @@ 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/
*/
public List<GHEventInfo> getEvents() throws IOException {
// TODO: pagination
GHEventInfo[] events = retrieve().to("/events", GHEventInfo[].class);
for (GHEventInfo e : events)
e.wrapUp(this);
@@ -411,14 +415,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.
@@ -432,6 +436,89 @@ public class GitHub {
}
}
/**
* Search issues.
*/
public GHIssueSearchBuilder searchIssues() {
return new GHIssueSearchBuilder(this);
}
/**
* Search users.
*/
public GHUserSearchBuilder searchUsers() {
return new GHUserSearchBuilder(this);
}
/**
* Search repositories.
*/
public GHRepositorySearchBuilder searchRepositories() {
return new GHRepositorySearchBuilder(this);
}
/**
* Search content.
*/
public GHContentSearchBuilder searchContent() {
return new GHContentSearchBuilder(this);
}
/**
* List all the notifications.
*/
public GHNotificationStream listNotifications() {
return new GHNotificationStream(this,"/notifications");
}
/**
* This provides a dump of every public repository, in the order that they were created.
* @see <a href="https://developer.github.com/v3/repos/#list-all-public-repositories">documentation</a>
*/
public PagedIterable<GHRepository> listAllPublicRepositories() {
return listAllPublicRepositories(null);
}
/**
* This provides a dump of every public repository, in the order that they were created.
*
* @param since
* The integer ID of the last Repository that 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() {
return new PagedIterator<GHRepository>(retrieve().with("since",since).asIterator("/repositories", GHRepository[].class)) {
@Override
protected void wrapUp(GHRepository[] page) {
for (GHRepository c : page)
c.wrap(GitHub.this);
}
};
}
};
}
/**
* Render a Markdown document in raw mode.
*
* <p>
* It takes a Markdown document as plaintext and renders it as plain Markdown
* without a repository context (just like a README.md file is rendered this
* is the simplest way to preview a readme online).
*
* @see GHRepository#renderMarkdown(String, MarkdownMode)
*/
public Reader renderMarkdown(String text) throws IOException {
return new InputStreamReader(
new Requester(this)
.with(new ByteArrayInputStream(text.getBytes("UTF-8")))
.contentType("text/plain;charset=UTF-8")
.asStream("/markdown/raw"),
"UTF-8");
}
/*package*/ static URL parseURL(String s) {
try {
return s==null ? null : new URL(s);
@@ -467,5 +554,5 @@ public class GitHub {
MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
private static final String GITHUB_URL = "https://api.github.com";
/* package */ static final String GITHUB_URL = "https://api.github.com";
}

View File

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

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,7 +1,9 @@
package org.kohsuke.github;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* {@link Iterable} that returns {@link PagedIterator}
@@ -21,4 +23,15 @@ public abstract class PagedIterable<T> implements Iterable<T> {
}
return r;
}
/**
* Eagerly walk {@link Iterable} and return the result in a set.
*/
public Set<T> asSet() {
LinkedHashSet<T> r = new LinkedHashSet<T>();
for(PagedIterator<T> i = iterator(); i.hasNext();) {
r.addAll(i.nextPage());
}
return r;
}
}

View File

@@ -0,0 +1,60 @@
package org.kohsuke.github;
import java.util.Iterator;
/**
* {@link PagedIterable} enhanced to report search result specific information.
*
* @author Kohsuke Kawaguchi
*/
public abstract class PagedSearchIterable<T> extends PagedIterable<T> {
private final GitHub root;
/**
* As soon as we have any result fetched, it's set here so that we can report the total count.
*/
private SearchResult<T> result;
/*package*/ PagedSearchIterable(GitHub root) {
this.root = root;
}
/**
* Returns the total number of hit, including the results that's not yet fetched.
*/
public int getTotalCount() {
populate();
return result.total_count;
}
public boolean isIncomplete() {
populate();
return result.incomplete_results;
}
private void populate() {
if (result==null)
iterator().hasNext();
}
/**
* Adapts {@link Iterator}.
*/
protected Iterator<T[]> adapt(final Iterator<? extends SearchResult<T>> base) {
return new Iterator<T[]>() {
public boolean hasNext() {
return base.hasNext();
}
public T[] next() {
SearchResult<T> v = base.next();
if (result==null) result = v;
return v.getItems(root);
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}

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,15 +23,16 @@
*/
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;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
@@ -43,13 +44,16 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import org.apache.commons.io.IOUtils;
import static org.kohsuke.github.GitHub.*;
/**
* A builder pattern for making HTTP call and parsing its output.
@@ -59,6 +63,7 @@ import org.apache.commons.io.IOUtils;
class Requester {
private final GitHub root;
private final List<Entry> args = new ArrayList<Entry>();
private final Map<String,String> headers = new LinkedHashMap<String, String>();
/**
* Request method.
@@ -67,6 +72,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;
@@ -81,6 +91,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.
*/
@@ -104,6 +123,10 @@ class Requester {
public Requester with(String key, boolean value) {
return _with(key, value);
}
public Requester with(String key, Boolean value) {
return _with(key, value);
}
public Requester with(String key, String value) {
return _with(key, value);
@@ -129,6 +152,19 @@ class Requester {
return this;
}
/**
* Unlike {@link #with(String, String)}, overrides the existing value
*/
public Requester set(String key, Object value) {
for (Entry e : args) {
if (e.key.equals(key)) {
e.value = value;
return this;
}
}
return _with(key,value);
}
public Requester method(String method) {
this.method = method;
return this;
@@ -172,14 +208,41 @@ 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 (method.equals("GET") && !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 {
return 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\"")) {
Pattern nextLinkPattern = Pattern.compile(".*<(.*)>; rel=\"next\"");
Matcher nextLinkMatcher = nextLinkPattern.matcher(links);
if (nextLinkMatcher.find()) {
final String link = nextLinkMatcher.group(1);
T nextResult = _to(link, type, instance);
final int resultLength = Array.getLength(result);
final int nextResultLength = Array.getLength(nextResult);
T concatResult = (T) Array.newInstance(type.getComponentType(), resultLength + nextResultLength);
System.arraycopy(result, 0, concatResult, 0, resultLength);
System.arraycopy(nextResult, 0, concatResult, resultLength, nextResultLength);
result = concatResult;
}
}
}
return result;
} catch (IOException e) {
handleApiError(e,uc);
handleApiError(e);
}
}
}
@@ -189,19 +252,41 @@ 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);
buildRequest();
try {
return uc.getResponseCode();
} catch (IOException e) {
handleApiError(e,uc);
handleApiError(e);
}
}
}
private void buildRequest(HttpURLConnection uc) throws IOException {
public InputStream asStream(String tailApiUrl) throws IOException {
while (true) {// loop while API rate limit is hit
setupConnection(root.getApiURL(tailApiUrl));
buildRequest();
try {
return wrapStream(uc.getInputStream());
} catch (IOException e) {
handleApiError(e);
}
}
}
public String getResponseHeader(String header) {
return uc.getHeaderField(header);
}
/**
* Set up the request parameters or POST payload.
*/
private void buildRequest() throws IOException {
if (!method.equals("GET")) {
uc.setDoOutput(true);
uc.setRequestProperty("Content-type", contentType);
@@ -290,14 +375,14 @@ class Requester {
try {
while (true) {// loop while API rate limit is hit
HttpURLConnection uc = setupConnection(url);
setupConnection(url);
try {
next = parse(uc,type,null);
next = parse(type,null);
assert next!=null;
findNextURL(uc);
findNextURL();
return;
} catch (IOException e) {
handleApiError(e,uc);
handleApiError(e);
}
}
} catch (IOException e) {
@@ -308,7 +393,7 @@ class Requester {
/**
* Locate the next page from the pagination "Link" tag.
*/
private void findNextURL(HttpURLConnection uc) throws MalformedURLException {
private void findNextURL() throws MalformedURLException {
url = null; // start defensively
String link = uc.getHeaderField("Link");
if (link==null) return;
@@ -329,14 +414,20 @@ class Requester {
}
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) {
@@ -350,16 +441,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;
@@ -371,7 +467,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);
@@ -380,28 +476,25 @@ 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);
}
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);

View File

@@ -0,0 +1,16 @@
package org.kohsuke.github;
/**
* Represents the result of a search
*
* @author Kohsuke Kawaguchi
*/
abstract class SearchResult<T> {
int total_count;
boolean incomplete_results;
/**
* Wraps up the retrieved object and return them. Only called once.
*/
/*package*/ abstract T[] getItems(GitHub root);
}

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,23 +1,17 @@
import org.kohsuke.github.GHOrganization;
import org.kohsuke.github.GHTeam;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GitHub;
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 {
GHOrganization org = GitHub.connect().getOrganization("jenkinsci");
Map<String, GHTeam> teams = org.getTeams();
System.out.println(teams.size());
int sz = 0;
for (GHTeam t : org.listTeams()) {
sz++;
Collection<GHRepository> lst = GitHub.connect().getUser("kohsuke").getRepositories().values();
for (GHRepository r : lst) {
System.out.println(r.getName());
}
System.out.println(sz);
System.out.println(lst.size());
}
}

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

313
src/test/java/org/kohsuke/github/AppTest.java Normal file → Executable file
View File

@@ -1,22 +1,20 @@
package org.kohsuke.github;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
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;
import org.kohsuke.github.GHOrganization.Permission;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
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.
@@ -82,6 +80,51 @@ public class AppTest extends AbstractGitHubApiTestBase {
o.close();
}
@Test
public void testCreateDeployment() throws IOException {
GHRepository repository = getTestRepository();
GHDeployment deployment = repository.createDeployment("master")
.payload("{\"user\":\"atmos\",\"room_id\":123456}")
.description("question")
.create();
assertNotNull(deployment.getCreator());
assertNotNull(deployment.getId());
}
@Test
public void testListDeployments() throws IOException {
GHRepository repository = getTestRepository();
GHDeployment deployment = repository.createDeployment("master")
.payload("{\"user\":\"atmos\",\"room_id\":123456}")
.description("question")
.environment("unittest")
.create();
assertNotNull(deployment.getCreator());
assertNotNull(deployment.getId());
ArrayList<GHDeployment> deployments = Lists.newArrayList(repository.listDeployments(null, "master", null, "unittest"));
assertNotNull(deployments);
assertFalse(Iterables.isEmpty(deployments));
GHDeployment unitTestDeployment = deployments.get(0);
assertEquals("unittest",unitTestDeployment.getEnvironment());
assertEquals("master", unitTestDeployment.getRef());
}
@Test
public void testGetDeploymentStatuses() throws IOException {
GHRepository repository = getTestRepository();
GHDeployment deployment = repository.createDeployment("master")
.description("question")
.payload("{\"user\":\"atmos\",\"room_id\":123456}")
.create();
GHDeploymentStatus ghDeploymentStatus = repository.createDeployStatus(deployment.getId(), GHDeploymentState.SUCCESS)
.description("success")
.targetUrl("http://www.github.com").create();
Iterable<GHDeploymentStatus> deploymentStatuses = repository.getDeploymentStatuses(deployment.getId());
assertNotNull(deploymentStatuses);
assertEquals(1,Iterables.size(deploymentStatuses));
assertEquals(ghDeploymentStatus.getId(), Iterables.get(deploymentStatuses, 0).getId());
}
@Test
public void testGetIssues() throws Exception {
List<GHIssue> closedIssues = gitHub.getUser("kohsuke").getRepository("github-api").getIssues(GHIssueState.CLOSED);
@@ -164,31 +207,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
@@ -243,7 +286,7 @@ public class AppTest extends AbstractGitHubApiTestBase {
@Test
public void testGetTeamsForRepo() throws Exception {
kohsuke();
assertEquals(1,gitHub.getOrganization("github-api-test-org").getRepository("testGetTeamsForRepo").getTeams().size());
assertEquals(1, gitHub.getOrganization("github-api-test-org").getRepository("testGetTeamsForRepo").getTeams().size());
}
@Test
@@ -266,7 +309,7 @@ public class AppTest extends AbstractGitHubApiTestBase {
assertNotNull(t.getName());
sz++;
}
assertTrue(sz<100);
assertTrue(sz < 100);
}
@Test
@@ -458,7 +501,7 @@ public class AppTest extends AbstractGitHubApiTestBase {
long start = System.currentTimeMillis();
Map<String, GHRepository> repos = j.getRepositories();
long end = System.currentTimeMillis();
System.out.printf("%d repositories in %dms\n",repos.size(),end-start);
System.out.printf("%d repositories in %dms\n", repos.size(), end - start);
}
@Test
@@ -484,7 +527,7 @@ public class AppTest extends AbstractGitHubApiTestBase {
state = lst.get(0);
System.out.println(state);
assertEquals("testing!",state.getDescription());
assertEquals("http://kohsuke.org/",state.getTargetUrl());
assertEquals("http://kohsuke.org/", state.getTargetUrl());
}
@Test
@@ -555,10 +598,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 {
@@ -575,8 +618,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 = 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");
try {
assertNotNull(newDeployKey.getId());
@@ -593,11 +636,195 @@ 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 = 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());
}
@Test
public void testMemberPagenation() throws IOException {
Set<GHUser> all = new HashSet<GHUser>();
for (GHUser u : gitHub.getOrganization("github-api-test-org").getTeamByName("Core Developers").listMembers()) {
System.out.println(u.getLogin());
all.add(u);
}
assertFalse(all.isEmpty());
}
@Test
public void testIssueSearch() throws IOException {
PagedSearchIterable<GHIssue> r = gitHub.searchIssues().mentions("kohsuke").isOpen().list();
for (GHIssue i : r) {
System.out.println(i.getTitle());
}
}
@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() {

View File

@@ -0,0 +1,16 @@
package org.kohsuke.github;
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();
}
}

View File

@@ -1,5 +1,12 @@
package org.kohsuke.github;
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;
/**
@@ -21,4 +28,81 @@ public class GitHubTest extends TestCase {
GitHub hub = GitHub.connectUsingPassword("kohsuke", "bogus");
assertEquals("https://api.github.com/test", hub.getApiURL("/test").toString());
}
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);
}
/*
* Copied from StackOverflow: http://stackoverflow.com/a/7201825/2336755
*
* This allows changing the in memory process environment.
*
* 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");
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);
}
}

View File

@@ -1,18 +1,67 @@
package org.kohsuke.github;
import org.junit.After;
import org.junit.Test;
import java.io.IOException;
import java.util.Collection;
/**
* @author Kohsuke Kawaguchi
*/
public class PullRequestTest extends AbstractGitHubApiTestBase {
@Test
public void createPullRequest() throws Exception {
GHRepository j = gitHub.getOrganization("github-api-test-org").getRepository("jenkins");
String name = rnd.next();
GHPullRequest p = j.createPullRequest(name, "stable", "master", "## test");
GHPullRequest p = getRepository().createPullRequest(name, "stable", "master", "## test");
System.out.println(p.getUrl());
assertEquals(name, p.getTitle());
p.close();
}
@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<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
public void setAssignee() throws Exception {
GHPullRequest p = getRepository().createPullRequest(rnd.next(), "stable", "master", "## test");
GHMyself user = gitHub.getMyself();
p.assignTo(user);
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);
assertFalse(pr.getMergeable());
assertNotNull(pr.getUser().root);
}
}
@After
public void cleanUp() throws Exception {
for (GHPullRequest pr : getRepository().getPullRequests(GHIssueState.OPEN)) {
pr.close();
}
}
private GHRepository getRepository() throws IOException {
return gitHub.getOrganization("github-api-test-org").getRepository("jenkins");
}
}

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