Compare commits

...

50 Commits

Author SHA1 Message Date
Kohsuke Kawaguchi
3d03659508 [maven-release-plugin] prepare release github-api-1.85 2017-02-28 21:06:08 -08:00
Kohsuke Kawaguchi
0374d2de48 Merge pull request #346 from stephenc/close-the-connections
Ensure that connections are closed for error responses
2017-02-27 12:33:18 -08:00
Stephen Connolly
2627dc5ee4 Ensure that connections are closed for error responses
- This was endless fun to trace, but I found it at last. This should
stop the `WARNING: A connection to https://api.github.com/ was leaked.
Did you forget to close a response body?` messages in the logs when
using the OkHttpConnector.
2017-02-23 12:52:29 +00:00
Kohsuke Kawaguchi
1f4325e7db Merge pull request #329 from stephenc/patch-1
Correct algebra in #327
2017-01-16 18:31:33 -08:00
Stephen Connolly
f2e7b40425 Correct algebra in #327 2017-01-10 10:15:04 +00:00
Kohsuke Kawaguchi
4ee3086b6d [maven-release-plugin] prepare for next development iteration 2017-01-09 16:50:15 -08:00
Kohsuke Kawaguchi
a3a715c3ba [maven-release-plugin] prepare release github-api-1.84 2017-01-09 16:50:08 -08:00
Kohsuke Kawaguchi
6cad4a3c33 static import for conciseness 2017-01-09 16:43:03 -08:00
Kohsuke Kawaguchi
b9b6f4fd44 INFO level logging is harmful as it's reported to stdout by default 2017-01-09 16:42:03 -08:00
Kohsuke Kawaguchi
17d1994a53 [maven-release-plugin] prepare for next development iteration 2017-01-09 16:38:02 -08:00
Kohsuke Kawaguchi
13184e72e1 [maven-release-plugin] prepare release github-api-1.83 2017-01-09 16:37:56 -08:00
Kohsuke Kawaguchi
911e8d21a7 Made the test case runnable, at least for me 2017-01-09 16:26:58 -08:00
Kohsuke Kawaguchi
5b69a2925f Merge pull request #324 2017-01-09 16:18:48 -08:00
Kohsuke Kawaguchi
d1c900a620 Marking the fact that these APIs are still in preview and subject to change 2017-01-09 16:18:40 -08:00
Kohsuke Kawaguchi
6bfeb54f3c Just exposing permission type enum until there's more to this relation than one property 2017-01-09 16:14:30 -08:00
Kohsuke Kawaguchi
198fede915 Merge pull request #325 2017-01-09 16:06:14 -08:00
Kohsuke Kawaguchi
1212ae3eb3 Touch up for uniformity
- Prefer typed 'URL' over 'String' that is URL
- Mark API as @Preview to communicate that this is subject to change

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

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

View File

@@ -3,11 +3,11 @@
<parent>
<groupId>org.kohsuke</groupId>
<artifactId>pom</artifactId>
<version>14</version>
<version>17</version>
</parent>
<artifactId>github-api</artifactId>
<version>1.80</version>
<version>1.85</version>
<name>GitHub API for Java</name>
<url>http://github-api.kohsuke.org/</url>
<description>GitHub API for Java</description>
@@ -16,7 +16,7 @@
<connection>scm:git:git@github.com/kohsuke/${project.artifactId}.git</connection>
<developerConnection>scm:git:ssh://git@github.com/kohsuke/${project.artifactId}.git</developerConnection>
<url>http://${project.artifactId}.kohsuke.org/</url>
<tag>github-api-1.80</tag>
<tag>github-api-1.85</tag>
</scm>
<distributionManagement>

View File

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

View File

@@ -1,13 +1,17 @@
package org.kohsuke.github;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.kohsuke.github.BranchProtection.RequiredStatusChecks;
import static org.kohsuke.github.Previews.LOKI;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import static org.kohsuke.github.Previews.LOKI;
import org.kohsuke.github.BranchProtection.RequiredStatusChecks;
import com.fasterxml.jackson.annotation.JsonProperty;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* A branch in a repository.
@@ -22,6 +26,10 @@ public class GHBranch {
private String name;
private Commit commit;
@JsonProperty("protected")
private boolean protection;
private String protection_url;
public static class Commit {
String sha;
@@ -45,6 +53,23 @@ public class GHBranch {
return name;
}
/**
* Returns true if the push to this branch is restricted via branch protection.
*/
@Preview @Deprecated
public boolean isProtected() {
return protection;
}
/**
* Returns API URL that deals with the protection of this branch.
*/
@Preview @Deprecated
public URL getProtectionUrl() {
return GitHub.parseURL(protection_url);
}
/**
* The commit that this branch currently points to.
*/

View File

@@ -37,6 +37,12 @@ public class GHCommit {
private int comment_count;
static class Tree {
String sha;
}
private Tree tree;
@WithBridgeMethods(value = GHAuthor.class, castRequired = true)
public GitUser getAuthor() {
return author;
@@ -224,6 +230,13 @@ public class GHCommit {
return stats.deletions;
}
/**
* Use this method to walk the tree
*/
public GHTree getTree() throws IOException {
return owner.getTree(getCommitShortInfo().tree.sha);
}
/**
* URL of this commit like "https://github.com/kohsuke/sandbox-ant/commit/8ae38db0ea5837313ab5f39d43a6f73de3bd9000"
*/

View File

@@ -29,13 +29,15 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import static org.kohsuke.github.Previews.SQUIRREL_GIRL;
import static org.kohsuke.github.Previews.*;
/**
* Represents an issue on GitHub.
@@ -51,7 +53,8 @@ public class GHIssue extends GHObject implements Reactable{
GHRepository owner;
// API v3
protected GHUser assignee;
protected GHUser assignee; // not sure what this field is now that 'assignees' exist
protected GHUser[] assignees;
protected String state;
protected int number;
protected String closed_at;
@@ -81,6 +84,7 @@ public class GHIssue extends GHObject implements Reactable{
/*package*/ GHIssue wrap(GitHub root) {
this.root = root;
if(assignee != null) assignee.wrapUp(root);
if(assignees!=null) GHUser.wrap(assignees,root);
if(user != null) user.wrapUp(root);
if(closed_by != null) closed_by.wrapUp(root);
return this;
@@ -187,7 +191,7 @@ public class GHIssue extends GHObject implements Reactable{
}
public void assignTo(GHUser user) throws IOException {
editIssue("assignee", user.getLogin());
setAssignees(user);
}
public void setLabels(String... labels) throws IOException {
@@ -242,6 +246,40 @@ public class GHIssue extends GHObject implements Reactable{
};
}
public void addAssignees(GHUser... assignees) throws IOException {
addAssignees(Arrays.asList(assignees));
}
public void addAssignees(Collection<GHUser> assignees) throws IOException {
List<String> names = toLogins(assignees);
root.retrieve().method("POST").with("assignees",names).to(getIssuesApiRoute()+"/assignees",this);
}
public void setAssignees(GHUser... assignees) throws IOException {
setAssignees(Arrays.asList(assignees));
}
public void setAssignees(Collection<GHUser> assignees) throws IOException {
editIssue("assignees",toLogins(assignees));
}
public void removeAssignees(GHUser... assignees) throws IOException {
removeAssignees(Arrays.asList(assignees));
}
public void removeAssignees(Collection<GHUser> assignees) throws IOException {
List<String> names = toLogins(assignees);
root.retrieve().method("DELETE").with("assignees",names).inBody().to(getIssuesApiRoute()+"/assignees",this);
}
private List<String> toLogins(Collection<GHUser> assignees) {
List<String> names = new ArrayList<String>(assignees.size());
for (GHUser a : assignees) {
names.add(a.getLogin());
}
return names;
}
protected String getApiRoute() {
return getIssuesApiRoute();
}
@@ -253,7 +291,11 @@ public class GHIssue extends GHObject implements Reactable{
public GHUser getAssignee() {
return assignee;
}
public List<GHUser> getAssignees() {
return Collections.unmodifiableList(Arrays.asList(assignees));
}
/**
* User who submitted the issue.
*/

View File

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

View File

@@ -41,6 +41,11 @@ public class GHIssueSearchBuilder extends GHSearchBuilder<GHIssue> {
return q("is:merged");
}
public GHIssueSearchBuilder order(GHDirection v) {
req.with("order",v);
return this;
}
public GHIssueSearchBuilder sort(Sort sort) {
req.with("sort",sort);
return this;

View File

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

View File

@@ -178,6 +178,39 @@ public class GHMyself extends GHUser {
return listRepositories();
}
/**
* List your organization memberships
*/
public PagedIterable<GHMembership> listOrgMemberships() {
return listOrgMemberships(null);
}
/**
* List your organization memberships
*
* @param state
* Filter by a specific state
*/
public PagedIterable<GHMembership> listOrgMemberships(final GHMembership.State state) {
return new PagedIterable<GHMembership>() {
public PagedIterator<GHMembership> _iterator(int pageSize) {
return new PagedIterator<GHMembership>(root.retrieve().with("state",state).asIterator("/user/memberships/orgs", GHMembership[].class, pageSize)) {
@Override
protected void wrapUp(GHMembership[] page) {
GHMembership.wrap(page,root);
}
};
}
};
}
/**
* Gets your membership in a specific organization.
*/
public GHMembership getMembership(GHOrganization o) throws IOException {
return root.retrieve().to("/user/memberships/orgs/"+o.getLogin(),GHMembership.class).wrap(root);
}
// public void addEmails(Collection<String> emails) throws IOException {
//// new Requester(root,ApiVersion.V3).withCredential().to("/user/emails");
// root.retrieveWithAuth3()

View File

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

View File

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

View File

@@ -93,10 +93,10 @@ public abstract class GHPerson extends GHObject {
}
/**
* Loads repository list in a pagenated fashion.
* Loads repository list in a paginated fashion.
*
* <p>
* For a person with a lot of repositories, GitHub returns the list of repositories in a pagenated fashion.
* For a person with a lot of repositories, GitHub returns the list of repositories in a paginated fashion.
* Unlike {@link #getRepositories()}, this method allows the caller to start processing data as it arrives.
*
* Every {@link Iterator#next()} call results in I/O. Exceptions that occur during the processing is wrapped

View File

@@ -213,7 +213,7 @@ public class GHPullRequest extends GHIssue {
public PagedIterable<GHPullRequestFileDetail> listFiles() {
return new PagedIterable<GHPullRequestFileDetail>() {
public PagedIterator<GHPullRequestFileDetail> _iterator(int pageSize) {
return new PagedIterator<GHPullRequestFileDetail>(root.retrieve().asIterator(String.format("%s/files", getApiURL()),
return new PagedIterator<GHPullRequestFileDetail>(root.retrieve().asIterator(String.format("%s/files", getApiRoute()),
GHPullRequestFileDetail[].class, pageSize)) {
@Override
protected void wrapUp(GHPullRequestFileDetail[] page) {
@@ -247,7 +247,7 @@ public class GHPullRequest extends GHIssue {
return new PagedIterable<GHPullRequestCommitDetail>() {
public PagedIterator<GHPullRequestCommitDetail> _iterator(int pageSize) {
return new PagedIterator<GHPullRequestCommitDetail>(root.retrieve().asIterator(
String.format("%s/commits", getApiURL()),
String.format("%s/commits", getApiRoute()),
GHPullRequestCommitDetail[].class, pageSize)) {
@Override
protected void wrapUp(GHPullRequestCommitDetail[] page) {

View File

@@ -121,9 +121,7 @@ public class GHRelease extends GHObject {
* Java 7 or greater. Options for fixing this for earlier JVMs can be found here
* http://stackoverflow.com/questions/12361090/server-name-indication-sni-on-java but involve more complicated
* handling of the HTTP requests to github's API.
*
* @throws IOException
*/
*/
public GHAsset uploadAsset(File file, String contentType) throws IOException {
Requester builder = new Requester(owner.root);

View File

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

View File

@@ -31,6 +31,7 @@ import org.apache.commons.lang.StringUtils;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.Reader;
@@ -50,8 +51,8 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import static java.util.Arrays.asList;
import static org.kohsuke.github.Previews.DRAX;
import static java.util.Arrays.*;
import static org.kohsuke.github.Previews.*;
/**
* A repository on GitHub.
@@ -448,25 +449,24 @@ public class GHRepository extends GHObject {
* Lists up the collaborators on this repository.
*
* @return Users
* @throws IOException
*/
public PagedIterable<GHUser> listCollaborators() throws IOException {
return new PagedIterable<GHUser>() {
public PagedIterator<GHUser> _iterator(int pageSize) {
return listUsers("collaborators");
}
return new PagedIterator<GHUser>(root.retrieve().asIterator(getApiTailUrl("collaborators"), GHUser[].class, pageSize)) {
@Override
protected void wrapUp(GHUser[] users) {
for (GHUser user : users) {
user.wrapUp(root);
}
}
};
}
};
/**
* Lists all <a href="https://help.github.com/articles/assigning-issues-and-pull-requests-to-other-github-users/">the available assignees</a>
* to which issues may be assigned.
*/
public PagedIterable<GHUser> listAssignees() throws IOException {
return listUsers("assignees");
}
/**
* Checks if the given user is an assignee for this repository.
*/
public boolean hasAssignee(GHUser u) throws IOException {
return root.retrieve().asHttpStatusCode(getApiTailUrl("assignees/" + u.getLogin()))/100==2;
}
/**
@@ -480,6 +480,29 @@ public class GHRepository extends GHObject {
return r;
}
/**
* Obtain permission for a given user in this repository.
* @param user a {@link GHUser#getLogin}
* @throws FileNotFoundException under some conditions (e.g., private repo you can see but are not an admin of); treat as unknown
* @throws HttpException with a 403 under other conditions (e.g., public repo you have no special rights to); treat as unknown
*/
@Deprecated @Preview
public GHPermissionType getPermission(String user) throws IOException {
GHPermission perm = root.retrieve().withPreview(KORRA).to(getApiTailUrl("collaborators/" + user + "/permission"), GHPermission.class);
perm.wrapUp(root);
return perm.getPermissionType();
}
/**
* Obtain permission for a given user in this repository.
* @throws FileNotFoundException under some conditions (e.g., private repo you can see but are not an admin of); treat as unknown
* @throws HttpException with a 403 under other conditions (e.g., public repo you have no special rights to); treat as unknown
*/
@Deprecated @Preview
public GHPermissionType getPermission(GHUser u) throws IOException {
return getPermission(u.getLogin());
}
/**
* If this repository belongs to an organization, return a set of teams.
*/
@@ -504,7 +527,6 @@ public class GHRepository extends GHObject {
}
private void modifyCollaborators(Collection<GHUser> users, String method) throws IOException {
verifyMine();
for (GHUser user : users) {
new Requester(root).method(method).to(getApiTailUrl("collaborators/" + user.getLogin()));
}
@@ -799,7 +821,7 @@ public class GHRepository extends GHObject {
*/
public GHTree getTree(String sha) throws IOException {
String url = String.format("/repos/%s/%s/git/trees/%s", getOwnerName(), name, sha);
return root.retrieve().to(url, GHTree.class).wrap(root);
return root.retrieve().to(url, GHTree.class).wrap(this);
}
/**
@@ -814,7 +836,32 @@ public class GHRepository extends GHObject {
*/
public GHTree getTreeRecursive(String sha, int recursive) throws IOException {
String url = String.format("/repos/%s/%s/git/trees/%s?recursive=%d", getOwnerName(), name, sha, recursive);
return root.retrieve().to(url, GHTree.class).wrap(root);
return root.retrieve().to(url, GHTree.class).wrap(this);
}
/**
* Obtains the metadata &amp; the content of a blob.
*
* <p>
* This method retrieves the whole content in memory, so beware when you are dealing with large BLOB.
*
* @see <a href="https://developer.github.com/v3/git/blobs/#get-a-blob">Get a blob</a>
* @see #readBlob(String)
*/
public GHBlob getBlob(String blobSha) throws IOException {
String target = getApiTailUrl("git/blobs/" + blobSha);
return root.retrieve().to(target, GHBlob.class);
}
/**
* Reads the content of a blob as a stream for better efficiency.
*
* @see <a href="https://developer.github.com/v3/git/blobs/#get-a-blob">Get a blob</a>
* @see #getBlob(String)
*/
public InputStream readBlob(String blobSha) throws IOException {
String target = getApiTailUrl("git/blobs/" + blobSha);
return root.retrieve().withHeader("Accept","application/vnd.github.VERSION.raw").asStream(target);
}
/**
@@ -1092,11 +1139,6 @@ public class GHRepository extends GHObject {
// return root.retrieveWithAuth("/pulls/"+owner+'/'+name,JsonPullRequests.class).wrap(root);
// }
private void verifyMine() throws IOException {
if (!root.login.equals(getOwnerName()))
throw new IOException("Operation not applicable to a repository owned by someone else: " + getOwnerName());
}
/**
* Returns a set that represents the post-commit hook URLs.
* The returned set is live, and changes made to them are reflected to GitHub.

View File

@@ -57,6 +57,11 @@ public class GHRepositorySearchBuilder extends GHSearchBuilder<GHRepository> {
return q("stars:"+v);
}
public GHRepositorySearchBuilder order(GHDirection v) {
req.with("order",v);
return this;
}
public GHRepositorySearchBuilder sort(Sort sort) {
req.with("sort",sort);
return this;

View File

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

View File

@@ -1,5 +1,7 @@
package org.kohsuke.github;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
/**
@@ -10,6 +12,8 @@ import java.net.URL;
* @see GHTree
*/
public class GHTreeEntry {
/* package almost final */GHTree tree;
private String path, mode, type, sha, url;
private long size;
@@ -44,7 +48,7 @@ public class GHTreeEntry {
/**
* Gets the type such as:
* "blob"
* "blob", "tree", etc.
*
* @return The type
*/
@@ -68,4 +72,37 @@ public class GHTreeEntry {
public URL getUrl() {
return GitHub.parseURL(url);
}
/**
* If this tree entry represents a file, then return its information.
* Otherwise null.
*/
public GHBlob asBlob() throws IOException {
if (type.equals("blob"))
return tree.repo.getBlob(sha);
else
return null;
}
/**
* If this tree entry represents a file, then return its content.
* Otherwise null.
*/
public InputStream readAsBlob() throws IOException {
if (type.equals("blob"))
return tree.repo.readBlob(sha);
else
return null;
}
/**
* If this tree entry represents a directory, then return it.
* Otherwise null.
*/
public GHTree asTree() throws IOException {
if (type.equals("tree"))
return tree.repo.getTree(sha);
else
return null;
}
}

View File

@@ -214,4 +214,9 @@ public class GHUser extends GHPerson {
if (tail.length()>0 && !tail.startsWith("/")) tail='/'+tail;
return "/users/" + login + tail;
}
/*package*/ GHUser wrapUp(GitHub root) {
super.wrapUp(root);
return this;
}
}

View File

@@ -49,6 +49,11 @@ public class GHUserSearchBuilder extends GHSearchBuilder<GHUser> {
return q("followers:"+v);
}
public GHUserSearchBuilder order(GHDirection v) {
req.with("order",v);
return this;
}
public GHUserSearchBuilder sort(Sort sort) {
req.with("sort",sort);
return this;

View File

@@ -23,12 +23,10 @@
*/
package org.kohsuke.github;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;
import static java.util.logging.Level.FINE;
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
import static org.kohsuke.github.Previews.DRAX;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.VisibilityChecker.Std;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -49,17 +47,18 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import org.apache.commons.codec.Charsets;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.VisibilityChecker.Std;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import javax.annotation.Nonnull;
import java.util.logging.Logger;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
import static java.util.logging.Level.FINE;
import static org.kohsuke.github.Previews.DRAX;
/**
* Root of the GitHub API.
@@ -90,6 +89,10 @@ public class GitHub {
private HttpConnector connector = HttpConnector.DEFAULT;
private final Object headerRateLimitLock = new Object();
private GHRateLimit headerRateLimit = null;
private volatile GHRateLimit rateLimit = null;
/**
* Creates a client API root object.
*
@@ -254,6 +257,10 @@ public class GitHub {
return connector;
}
public String getApiUrl() {
return apiUrl;
}
/**
* Sets the custom connector used to make requests to GitHub.
*/
@@ -287,19 +294,63 @@ public class GitHub {
*/
public GHRateLimit getRateLimit() throws IOException {
try {
return retrieve().to("/rate_limit", JsonRateLimit.class).rate;
return rateLimit = retrieve().to("/rate_limit", JsonRateLimit.class).rate;
} catch (FileNotFoundException e) {
// GitHub Enterprise doesn't have the rate limit, so in that case
// return some big number that's not too big.
// see issue #78
GHRateLimit r = new GHRateLimit();
r.limit = r.remaining = 1000000;
long hours = 1000L * 60 * 60;
r.reset = new Date(System.currentTimeMillis() + 1 * hours );
return r;
long hour = 60L * 60L; // this is madness, storing the date as seconds in a Date object
r.reset = new Date(System.currentTimeMillis() / 1000L + hour);
return rateLimit = r;
}
}
/*package*/ void updateRateLimit(@Nonnull GHRateLimit observed) {
synchronized (headerRateLimitLock) {
if (headerRateLimit == null
|| headerRateLimit.getResetDate().getTime() < observed.getResetDate().getTime()
|| headerRateLimit.remaining > observed.remaining) {
headerRateLimit = observed;
LOGGER.log(FINE, "Rate limit now: {0}", headerRateLimit);
}
}
}
/**
* Returns the most recently observed rate limit data or {@code null} if either there is no rate limit
* (for example GitHub Enterprise) or if no requests have been made.
*
* @return the most recently observed rate limit data or {@code null}.
*/
@CheckForNull
public GHRateLimit lastRateLimit() {
synchronized (headerRateLimitLock) {
return headerRateLimit;
}
}
/**
* Gets the current rate limit while trying not to actually make any remote requests unless absolutely necessary.
*
* @return the current rate limit data.
* @throws IOException if we couldn't get the current rate limit data.
*/
@Nonnull
public GHRateLimit rateLimit() throws IOException {
synchronized (headerRateLimitLock) {
if (headerRateLimit != null) {
return headerRateLimit;
}
}
GHRateLimit rateLimit = this.rateLimit;
if (rateLimit == null || rateLimit.getResetDate().getTime() < System.currentTimeMillis()) {
rateLimit = getRateLimit();
}
return rateLimit;
}
/**
* Gets the {@link GHUser} that represents yourself.
*/
@@ -340,7 +391,7 @@ public class GitHub {
/**
* Interns the given {@link GHUser}.
*/
protected GHUser getUser(GHUser orig) throws IOException {
protected GHUser getUser(GHUser orig) {
GHUser u = users.get(orig.getLogin());
if (u==null) {
orig.root = this;
@@ -416,7 +467,6 @@ public class GitHub {
*
* @param key The license key provided from the API
* @return The license details
* @throws IOException
* @see GHLicense#getKey()
*/
@Preview @Deprecated
@@ -652,8 +702,18 @@ public class GitHub {
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
X-Content-Type-Options: nosniff
*/
return uc.getResponseCode() == HTTP_UNAUTHORIZED
&& uc.getHeaderField("X-GitHub-Media-Type") != null;
try {
return uc.getResponseCode() == HTTP_UNAUTHORIZED
&& uc.getHeaderField("X-GitHub-Media-Type") != null;
} finally {
// ensure that the connection opened by getResponseCode gets closed
try {
IOUtils.closeQuietly(uc.getInputStream());
} catch (IOException ignore) {
// ignore
}
IOUtils.closeQuietly(uc.getErrorStream());
}
} catch (IOException e) {
return false;
}

View File

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

View File

@@ -7,4 +7,5 @@ package org.kohsuke.github;
static final String LOKI = "application/vnd.github.loki-preview+json";
static final String DRAX = "application/vnd.github.drax-preview+json";
static final String SQUIRREL_GIRL = "application/vnd.github.squirrel-girl-preview";
static final String KORRA = "application/vnd.github.korra-preview";
}

View File

@@ -25,8 +25,6 @@ package org.kohsuke.github;
import com.fasterxml.jackson.databind.JsonMappingException;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.io.IOUtils;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -42,6 +40,7 @@ import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -53,12 +52,13 @@ import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import javax.annotation.WillClose;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import static java.util.Arrays.asList;
import static java.util.logging.Level.FINE;
import static org.kohsuke.github.GitHub.*;
import static java.util.logging.Level.*;
import static org.kohsuke.github.GitHub.MAPPER;
/**
* A builder pattern for making HTTP call and parsing its output.
@@ -81,6 +81,7 @@ class Requester {
* Current connection.
*/
private HttpURLConnection uc;
private boolean forceBody;
private static class Entry {
String key;
@@ -197,6 +198,16 @@ class Requester {
return this;
}
/**
* Small number of GitHub APIs use HTTP methods somewhat inconsistently, and use a body where it's not expected.
* Normally whether parameters go as query parameters or a body depends on the HTTP verb in use,
* but this method forces the parameters to be sent as a body.
*/
/*package*/ Requester inBody() {
forceBody = true;
return this;
}
public void to(String tailApiUrl) throws IOException {
to(tailApiUrl,null);
}
@@ -230,7 +241,7 @@ class Requester {
@SuppressFBWarnings("SBSC_USE_STRINGBUFFER_CONCATENATION")
private <T> T _to(String tailApiUrl, Class<T> type, T instance) throws IOException {
if (METHODS_WITHOUT_BODY.contains(method) && !args.isEmpty()) {
if (!isMethodWithBody() && !args.isEmpty()) {
boolean questionMarkFound = tailApiUrl.indexOf('?') != -1;
tailApiUrl += questionMarkFound ? '&' : '?';
for (Iterator<Entry> it = args.listIterator(); it.hasNext();) {
@@ -270,6 +281,8 @@ class Requester {
return result;
} catch (IOException e) {
handleApiError(e);
} finally {
noteRateLimit(tailApiUrl);
}
}
}
@@ -288,6 +301,8 @@ class Requester {
return uc.getResponseCode();
} catch (IOException e) {
handleApiError(e);
} finally {
noteRateLimit(tailApiUrl);
}
}
}
@@ -302,6 +317,59 @@ class Requester {
return wrapStream(uc.getInputStream());
} catch (IOException e) {
handleApiError(e);
} finally {
noteRateLimit(tailApiUrl);
}
}
}
private void noteRateLimit(String tailApiUrl) {
if ("/rate_limit".equals(tailApiUrl)) {
// the rate_limit API is "free"
return;
}
if (tailApiUrl.startsWith("/search")) {
// the search API uses a different rate limit
return;
}
String limit = uc.getHeaderField("X-RateLimit-Limit");
if (StringUtils.isBlank(limit)) {
// if we are missing a header, return fast
return;
}
String remaining = uc.getHeaderField("X-RateLimit-Remaining");
if (StringUtils.isBlank(remaining)) {
// if we are missing a header, return fast
return;
}
String reset = uc.getHeaderField("X-RateLimit-Reset");
if (StringUtils.isBlank(reset)) {
// if we are missing a header, return fast
return;
}
GHRateLimit observed = new GHRateLimit();
try {
observed.limit = Integer.parseInt(limit);
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(FINEST)) {
LOGGER.log(FINEST, "Malformed X-RateLimit-Limit header value " + limit, e);
}
return;
}
try {
observed.remaining = Integer.parseInt(remaining);
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(FINEST)) {
LOGGER.log(FINEST, "Malformed X-RateLimit-Remaining header value " + remaining, e);
}
return;
}
try {
observed.reset = new Date(Long.parseLong(reset)); // this is madness, storing the date as seconds
root.updateRateLimit(observed);
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(FINEST)) {
LOGGER.log(FINEST, "Malformed X-RateLimit-Reset header value " + reset, e);
}
}
}
@@ -340,11 +408,11 @@ class Requester {
}
private boolean isMethodWithBody() {
return !METHODS_WITHOUT_BODY.contains(method);
return forceBody || !METHODS_WITHOUT_BODY.contains(method);
}
/**
* Loads pagenated resources.
* Loads paginated resources.
*
* Every iterator call reports a new batch.
*/
@@ -371,7 +439,7 @@ class Requester {
}
try {
return new PagingIterator<T>(type, root.getApiURL(s.toString()));
return new PagingIterator<T>(type, tailApiUrl, root.getApiURL(s.toString()));
} catch (IOException e) {
throw new Error(e);
}
@@ -380,6 +448,7 @@ class Requester {
class PagingIterator<T> implements Iterator<T> {
private final Class<T> type;
private final String tailApiUrl;
/**
* The next batch to be returned from {@link #next()}.
@@ -391,9 +460,10 @@ class Requester {
*/
private URL url;
PagingIterator(Class<T> type, URL url) {
this.url = url;
PagingIterator(Class<T> type, String tailApiUrl, URL url) {
this.type = type;
this.tailApiUrl = tailApiUrl;
this.url = url;
}
public boolean hasNext() {
@@ -427,6 +497,8 @@ class Requester {
return;
} catch (IOException e) {
handleApiError(e);
} finally {
noteRateLimit(tailApiUrl);
}
}
} catch (IOException e) {
@@ -569,6 +641,24 @@ class Requester {
" handling exception " + e, e);
throw e;
}
InputStream es = wrapStream(uc.getErrorStream());
if (es != null) {
try {
String error = IOUtils.toString(es, "UTF-8");
if (e instanceof FileNotFoundException) {
// pass through 404 Not Found to allow the caller to handle it intelligently
e = (IOException) new FileNotFoundException(error).initCause(e);
} else if (e instanceof HttpException) {
HttpException http = (HttpException) e;
e = new HttpException(error, http.getResponseCode(), http.getResponseMessage(),
http.getUrl(), e);
} else {
e = (IOException) new IOException(error).initCause(e);
}
} finally {
IOUtils.closeQuietly(es);
}
}
if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) // 401 / Unauthorized == bad creds
throw e;
@@ -584,19 +674,7 @@ class Requester {
return;
}
InputStream es = wrapStream(uc.getErrorStream());
try {
if (es!=null) {
if (e instanceof FileNotFoundException) {
// pass through 404 Not Found to allow the caller to handle it intelligently
throw (IOException) new FileNotFoundException(IOUtils.toString(es, "UTF-8")).initCause(e);
} else
throw (IOException) new IOException(IOUtils.toString(es, "UTF-8")).initCause(e);
} else
throw e;
} finally {
IOUtils.closeQuietly(es);
}
throw e;
}
private static final List<String> METHODS_WITHOUT_BODY = asList("GET", "DELETE");

View File

@@ -1,10 +1,12 @@
package org.kohsuke.github;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.kohsuke.randname.RandomNameGenerator;
import java.io.File;
import java.io.IOException;
/**
* @author Kohsuke Kawaguchi
@@ -25,5 +27,18 @@ public abstract class AbstractGitHubApiTestBase extends Assert {
}
}
protected GHUser getUser() {
try {
return gitHub.getMyself();
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
protected void kohsuke() {
String login = getUser().getLogin();
Assume.assumeTrue(login.equals("kohsuke") || login.equals("kohsuke2"));
}
protected static final RandomNameGenerator rnd = new RandomNameGenerator();
}

View File

@@ -12,6 +12,7 @@ import org.kohsuke.github.GHCommit.File;
import org.kohsuke.github.GHOrganization.Permission;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.Map.Entry;
@@ -172,14 +173,6 @@ public class AppTest extends AbstractGitHubApiTestBase {
return repository;
}
private GHUser getUser() {
try {
return gitHub.getMyself();
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
@Test
public void testListIssues() throws IOException {
GHUser u = getUser();
@@ -362,6 +355,11 @@ public class AppTest extends AbstractGitHubApiTestBase {
assertEquals(48,f.getLinesChanged());
assertEquals("modified",f.getStatus());
assertEquals("changelog.html", f.getFileName());
// walk the tree
GHTree t = commit.getTree();
assertThat(IOUtils.toString(t.getEntry("todo.txt").readAsBlob()), containsString("executor rendering"));
assertNotNull(t.getEntry("war").asTree());
}
@Test
@@ -888,8 +886,38 @@ public class AppTest extends AbstractGitHubApiTestBase {
a.delete();
}
private void kohsuke() {
String login = getUser().getLogin();
Assume.assumeTrue(login.equals("kohsuke") || login.equals("kohsuke2"));
@Test
public void listOrgMemberships() throws Exception {
GHMyself me = gitHub.getMyself();
for (GHMembership m : me.listOrgMemberships()) {
assertThat(m.getUser(), is((GHUser)me));
assertNotNull(m.getState());
assertNotNull(m.getRole());
System.out.printf("%s %s %s\n",
m.getOrganization().getLogin(),
m.getState(),
m.getRole());
}
}
@Test
public void blob() throws Exception {
GHRepository r = gitHub.getRepository("kohsuke/github-api");
String sha1 = "a12243f2fc5b8c2ba47dd677d0b0c7583539584d";
assertBlobContent(r.readBlob(sha1));
GHBlob blob = r.getBlob(sha1);
assertBlobContent(blob.read());
assertThat(blob.getSha(),is("a12243f2fc5b8c2ba47dd677d0b0c7583539584d"));
assertThat(blob.getSize(),is(1104L));
}
private void assertBlobContent(InputStream is) throws Exception {
String content = new String(IOUtils.toByteArray(is),"UTF-8");
assertThat(content,containsString("Copyright (c) 2011- Kohsuke Kawaguchi and other contributors"));
assertThat(content,containsString("FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR"));
assertThat(content.length(),is(1104));
}
}

View File

@@ -3,6 +3,7 @@ package org.kohsuke.github;
import org.junit.Test;
import org.kohsuke.github.GHRepository.Contributor;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
@@ -40,6 +41,33 @@ public class RepositoryTest extends AbstractGitHubApiTestBase {
assertTrue(kohsuke);
}
@Test
public void getPermission() throws Exception {
kohsuke();
GHRepository r = gitHub.getRepository("github-api-test-org/test-permission");
assertEquals(GHPermissionType.ADMIN, r.getPermission("kohsuke"));
assertEquals(GHPermissionType.READ, r.getPermission("dude"));
r = gitHub.getOrganization("apache").getRepository("groovy");
try {
r.getPermission("jglick");
fail();
} catch (HttpException x) {
x.printStackTrace(); // good
assertEquals(403, x.getResponseCode());
}
if (false) {
// can't easily test this; there's no private repository visible to the test user
r = gitHub.getOrganization("cloudbees").getRepository("private-repo-not-writable-by-me");
try {
r.getPermission("jglick");
fail();
} catch (FileNotFoundException x) {
x.printStackTrace(); // good
}
}
}
private GHRepository getRepository() throws IOException {
return gitHub.getOrganization("github-api-test-org").getRepository("jenkins");
}

View File

@@ -65,8 +65,7 @@
"issues_url": "https://api.github.com/repos/baxterandthehackers/public-repo/issues{/number}",
"pulls_url": "https://api.github.com/repos/baxterandthehackers/public-repo/pulls{/number}",
"milestones_url": "https://api.github.com/repos/baxterandthehackers/public-repo/milestones{/number}",
"notifications_url": "https://api.github.com/repos/baxterandthehackers/public-repo/notifications{?since,all,
participating}",
"notifications_url": "https://api.github.com/repos/baxterandthehackers/public-repo/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/baxterandthehackers/public-repo/labels{/name}",
"releases_url": "https://api.github.com/repos/baxterandthehackers/public-repo/releases{/id}",
"deployments_url": "https://api.github.com/repos/baxterandthehackers/public-repo/deployments",

View File

@@ -2,8 +2,7 @@
"action": "created",
"milestone": {
"url": "https://api.github.com/repos/baxterandthehackers/public-repo/milestones/3",
"html_url": "https://github.com/baxterandthehackers/public-repo/milestones/Test%20milestone%20creation%20webhook
%20from%20command%20line2",
"html_url": "https://github.com/baxterandthehackers/public-repo/milestones/Test%20milestone%20creation%20webhook%20from%20command%20line2",
"labels_url": "https://api.github.com/repos/baxterandthehackers/public-repo/milestones/3/labels",
"id": 2055681,
"number": 3,
@@ -96,8 +95,7 @@
"issues_url": "https://api.github.com/repos/baxterandthehackers/public-repo/issues{/number}",
"pulls_url": "https://api.github.com/repos/baxterandthehackers/public-repo/pulls{/number}",
"milestones_url": "https://api.github.com/repos/baxterandthehackers/public-repo/milestones{/number}",
"notifications_url": "https://api.github.com/repos/baxterandthehackers/public-repo/notifications{?since,all,
participating}",
"notifications_url": "https://api.github.com/repos/baxterandthehackers/public-repo/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/baxterandthehackers/public-repo/labels{/name}",
"releases_url": "https://api.github.com/repos/baxterandthehackers/public-repo/releases{/id}",
"deployments_url": "https://api.github.com/repos/baxterandthehackers/public-repo/deployments",