diff --git a/pom.xml b/pom.xml index c61b59b225..a3b27867e0 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ github-api - 1.51 + 1.52 GitHub API for Java http://github-api.kohsuke.org/ GitHub API for Java @@ -16,7 +16,7 @@ scm:git:git@github.com/kohsuke/${project.artifactId}.git scm:git:ssh://git@github.com/kohsuke/${project.artifactId}.git http://${project.artifactId}.kohsuke.org/ - github-api-1.51 + HEAD diff --git a/src/main/java/org/kohsuke/github/GHCommitQueryBuilder.java b/src/main/java/org/kohsuke/github/GHCommitQueryBuilder.java new file mode 100644 index 0000000000..6d0a7ae995 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHCommitQueryBuilder.java @@ -0,0 +1,105 @@ +package org.kohsuke.github; + +import java.util.Date; + +/** + * Builds up query for listing commits. + * + *

+ * Call various methods that set the filter criteria, then {@link #list()} method to actually list up the commit. + * + *

+ * GHRepository r = ...;
+ * for (GHCommit c : r.queryCommits().since(x).until(y).author("kohsuke")) {
+ *     ...
+ * }
+ * 
+ * + * @author Kohsuke Kawaguchi + * @see GHRepository#queryCommits() +*/ +public class GHCommitQueryBuilder { + private final Requester req; + private final GHRepository repo; + + /*package*/ GHCommitQueryBuilder(GHRepository repo) { + this.repo = repo; + this.req = repo.root.retrieve(); // requester to build up + } + + /** + * GItHub login or email address by which to filter by commit author. + */ + public GHCommitQueryBuilder author(String author) { + req.with("author",author); + return this; + } + + /** + * Only commits containing this file path will be returned. + */ + public GHCommitQueryBuilder path(String path) { + req.with("path",path); + return this; + } + + /** + * Specifies the SHA1 commit / tag / branch / etc to start listing commits from. + * + */ + public GHCommitQueryBuilder from(String ref) { + req.with("sha",ref); + return this; + } + + public GHCommitQueryBuilder pageSize(int pageSize) { + req.with("per_page",pageSize); + return this; + } + + /** + * Only commits after this date will be returned + */ + public GHCommitQueryBuilder since(Date dt) { + req.with("since",GitHub.printDate(dt)); + return this; + } + + /** + * Only commits after this date will be returned + */ + public GHCommitQueryBuilder since(long timestamp) { + return since(new Date(timestamp)); + } + + /** + * Only commits before this date will be returned + */ + public GHCommitQueryBuilder until(Date dt) { + req.with("until",GitHub.printDate(dt)); + return this; + } + + /** + * Only commits before this date will be returned + */ + public GHCommitQueryBuilder until(long timestamp) { + return until(new Date(timestamp)); + } + + /** + * Lists up the commits with the criteria built so far. + */ + public PagedIterable list() { + return new PagedIterable() { + public PagedIterator iterator() { + return new PagedIterator(req.asIterator(repo.getApiTailUrl("commits"), GHCommit[].class)) { + protected void wrapUp(GHCommit[] page) { + for (GHCommit c : page) + c.wrapUp(repo); + } + }; + } + }; + } +} diff --git a/src/main/java/org/kohsuke/github/GHEmail.java b/src/main/java/org/kohsuke/github/GHEmail.java new file mode 100644 index 0000000000..5f230c5945 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHEmail.java @@ -0,0 +1,69 @@ +/* + * The MIT License + * + * Copyright (c) 2010, Kohsuke Kawaguchi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.kohsuke.github; + + +/** + * Represents an email of GitHub. + * + * @author Kelly Campbell + */ +public class GHEmail { + + protected String email; + protected boolean primary; + protected boolean verified; + + public String getEmail() { + return email; + } + + public boolean isPrimary() { + return primary; + } + + public boolean isVerified() { + return verified; + } + + + @Override + public String toString() { + return "Email:"+email; + } + + @Override + public int hashCode() { + return email.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof GHEmail) { + GHEmail that = (GHEmail) obj; + return this.email.equals(that.email); + } + return false; + } +} diff --git a/src/main/java/org/kohsuke/github/GHEventPayload.java b/src/main/java/org/kohsuke/github/GHEventPayload.java index e805ef7eb4..704d7f8e81 100644 --- a/src/main/java/org/kohsuke/github/GHEventPayload.java +++ b/src/main/java/org/kohsuke/github/GHEventPayload.java @@ -51,6 +51,8 @@ public GHRepository getRepository() { @Override void wrapUp(GitHub root) { super.wrapUp(root); + if (pull_request==null) + throw new IllegalStateException("Expected pull_request payload, but got something else. Maybe we've got another type of event?"); if (repository!=null) { repository.wrap(root); pull_request.wrap(repository); diff --git a/src/main/java/org/kohsuke/github/GHMyself.java b/src/main/java/org/kohsuke/github/GHMyself.java index c770f0449e..85c75c0971 100644 --- a/src/main/java/org/kohsuke/github/GHMyself.java +++ b/src/main/java/org/kohsuke/github/GHMyself.java @@ -1,6 +1,7 @@ package org.kohsuke.github; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -16,18 +17,31 @@ * @author Kohsuke Kawaguchi */ public class GHMyself extends GHUser { + /** + * @deprecated + * Use {@link #getEmails2()} + */ + public List getEmails() throws IOException { + List src = getEmails2(); + List r = new ArrayList(src.size()); + for (GHEmail e : src) { + r.add(e.getEmail()); + } + return r; + } + /** * Returns the read-only list of e-mail addresses configured for you. * * This corresponds to the stuff you configure in https://github.com/settings/emails, * and not to be confused with {@link #getEmail()} that shows your public e-mail address * set in https://github.com/settings/profile - * + * * @return * Always non-null. */ - public List getEmails() throws IOException { - String[] addresses = root.retrieve().to("/user/emails", String[].class); + public List getEmails2() throws IOException { + GHEmail[] addresses = root.retrieve().to("/user/emails", GHEmail[].class); return Collections.unmodifiableList(Arrays.asList(addresses)); } diff --git a/src/main/java/org/kohsuke/github/GHOrganization.java b/src/main/java/org/kohsuke/github/GHOrganization.java index 8501ba3763..0680ca39ca 100644 --- a/src/main/java/org/kohsuke/github/GHOrganization.java +++ b/src/main/java/org/kohsuke/github/GHOrganization.java @@ -23,10 +23,15 @@ public class GHOrganization extends GHPerson { * Newly created repository. */ public GHRepository createRepository(String name, String description, String homepage, String team, boolean isPublic) throws IOException { - return createRepository(name,description,homepage,getTeams().get(team),isPublic); + GHTeam t = getTeams().get(team); + if (t==null) + throw new IllegalArgumentException("No such team: "+team); + return createRepository(name,description,homepage,t,isPublic); } public GHRepository createRepository(String name, String description, String homepage, GHTeam team, boolean isPublic) throws IOException { + if (team==null) + throw new IllegalArgumentException("Invalid team"); // such API doesn't exist, so fall back to HTML scraping return new Requester(root) .with("name", name).with("description", description).with("homepage", homepage) @@ -37,14 +42,41 @@ public GHRepository createRepository(String name, String description, String hom * Teams by their names. */ public Map getTeams() throws IOException { - GHTeam[] teams = root.retrieve().to("/orgs/" + login + "/teams", GHTeam[].class); Map r = new TreeMap(); - for (GHTeam t : teams) { - r.put(t.getName(),t.wrapUp(this)); + for (GHTeam t : listTeams()) { + r.put(t.getName(),t); } return r; } + /** + * List up all the teams. + */ + public PagedIterable listTeams() throws IOException { + return new PagedIterable() { + public PagedIterator iterator() { + return new PagedIterator(root.retrieve().asIterator(String.format("/orgs/%s/teams", login), GHTeam[].class)) { + @Override + protected void wrapUp(GHTeam[] page) { + for (GHTeam c : page) + c.wrapUp(GHOrganization.this); + } + }; + } + }; + } + + /** + * Finds a team that has the given name in its {@link GHTeam#getName()} + */ + public GHTeam getTeamByName(String name) throws IOException { + for (GHTeam t : listTeams()) { + if(t.getName().equals(name)) + return t; + } + return null; + } + /** * Checks if this organization has the specified user as a member. */ diff --git a/src/main/java/org/kohsuke/github/GHRelease.java b/src/main/java/org/kohsuke/github/GHRelease.java index b862477ea7..0c405b33e2 100644 --- a/src/main/java/org/kohsuke/github/GHRelease.java +++ b/src/main/java/org/kohsuke/github/GHRelease.java @@ -32,6 +32,8 @@ public class GHRelease { private boolean prerelease; private Date created_at; private Date published_at; + private String tarball_url; + private String zipball_url; public String getAssetsUrl() { return assets_url; @@ -153,6 +155,22 @@ 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; diff --git a/src/main/java/org/kohsuke/github/GHRepository.java b/src/main/java/org/kohsuke/github/GHRepository.java index 6a6b93ae73..7b12f08959 100644 --- a/src/main/java/org/kohsuke/github/GHRepository.java +++ b/src/main/java/org/kohsuke/github/GHRepository.java @@ -63,11 +63,11 @@ public class GHRepository { private boolean has_issues, has_wiki, fork, has_downloads; @JsonProperty("private") private boolean _private; - private int watchers,forks,open_issues,size; + private int watchers,forks,open_issues,size,network_count,subscribers_count; private String created_at, pushed_at; private Map milestones = new HashMap(); - private String master_branch,language; + private String default_branch,language; private Map commits = new HashMap(); private GHRepoPermission permissions; @@ -153,8 +153,9 @@ public List getIssues(GHIssueState state) throws IOException { public List getIssues(GHIssueState state, GHMilestone milestone) throws IOException { return Arrays.asList(GHIssue.wrap(root.retrieve() .to(String.format("/repos/%s/%s/issues?state=%s&milestone=%s", owner.login, name, - state.toString().toLowerCase(), milestone == null ? "none" : "" + milestone.getNumber()), - GHIssue[].class), this)); + state.toString().toLowerCase(), milestone == null ? "none" : "" + milestone.getNumber()), + GHIssue[].class + ), this)); } /** @@ -219,6 +220,14 @@ public int getOpenIssueCount() { return open_issues; } + public int getNetworkCount() { + return network_count; + } + + public int getSubscribersCount() { + return subscribers_count; + } + /** * * @return @@ -239,7 +248,7 @@ public Date getCreatedAt() { * This field is null until the user explicitly configures the master branch. */ public String getMasterBranch() { - return master_branch; + return default_branch; } public int getSize() { @@ -498,6 +507,13 @@ protected void wrapUp(GHCommit[] page) { }; } + /** + * Search commits by specifying filters through a builder pattern. + */ + public GHCommitQueryBuilder queryCommits() { + return new GHCommitQueryBuilder(this); + } + /** * Lists up all the commit comments in this repository. */ diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java index 167240d529..00f94bd04b 100644 --- a/src/main/java/org/kohsuke/github/GitHub.java +++ b/src/main/java/org/kohsuke/github/GitHub.java @@ -405,6 +405,10 @@ public boolean isCredentialValid() throws IOException { throw new IllegalStateException("Unable to parse the timestamp: "+timestamp); } + /*package*/ static String printDate(Date dt) { + return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(dt); + } + /*package*/ static final ObjectMapper MAPPER = new ObjectMapper(); private static final String[] TIME_FORMATS = {"yyyy/MM/dd HH:mm:ss ZZZZ","yyyy-MM-dd'T'HH:mm:ss'Z'"}; diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java index ef42a6e02f..e7e3e4674a 100644 --- a/src/main/java/org/kohsuke/github/Requester.java +++ b/src/main/java/org/kohsuke/github/Requester.java @@ -23,7 +23,7 @@ */ package org.kohsuke.github; -import org.apache.commons.io.IOUtils; +import static org.kohsuke.github.GitHub.MAPPER; import java.io.FileNotFoundException; import java.io.IOException; @@ -31,11 +31,13 @@ import java.io.InputStreamReader; import java.io.InterruptedIOException; import java.io.Reader; +import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.ProtocolException; import java.net.URL; +import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -47,7 +49,7 @@ import java.util.Set; import java.util.zip.GZIPInputStream; -import static org.kohsuke.github.GitHub.MAPPER; +import org.apache.commons.io.IOUtils; /** * A builder pattern for making HTTP call and parsing its output. @@ -208,9 +210,23 @@ private T _to(String tailApiUrl, Class type, T instance) throws IOExcepti * * Every iterator call reports a new batch. */ - /*package*/ Iterator asIterator(final String tailApiUrl, final Class type) { + /*package*/ Iterator asIterator(String _tailApiUrl, final Class type) { method("GET"); - if (!args.isEmpty()) throw new IllegalStateException(); + + if (!args.isEmpty()) { + boolean first=true; + try { + for (Entry a : args) { + _tailApiUrl += first ? '?' : '&'; + first = false; + _tailApiUrl += URLEncoder.encode(a.key,"UTF-8")+'='+URLEncoder.encode(a.value.toString(),"UTF-8"); + } + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); // UTF-8 is mandatory + } + } + + final String tailApiUrl = _tailApiUrl; return new Iterator() { /** diff --git a/src/test/java/Foo.java b/src/test/java/Foo.java index f5a88a34ae..e81431b112 100644 --- a/src/test/java/Foo.java +++ b/src/test/java/Foo.java @@ -1,12 +1,23 @@ +import org.kohsuke.github.GHOrganization; +import org.kohsuke.github.GHTeam; import org.kohsuke.github.GitHub; import java.util.Arrays; +import java.util.Map; /** * @author Kohsuke Kawaguchi */ public class Foo { public static void main(String[] args) throws Exception { - System.out.println(GitHub.connect().getOrganization("cloudbees").getRepository("grandcentral").isPrivate()); + GHOrganization org = GitHub.connect().getOrganization("jenkinsci"); + Map teams = org.getTeams(); + System.out.println(teams.size()); + + int sz = 0; + for (GHTeam t : org.listTeams()) { + sz++; + } + System.out.println(sz); } } diff --git a/src/test/java/org/kohsuke/AppTest.java b/src/test/java/org/kohsuke/AppTest.java index 2847c0f8ec..09a6bdec70 100644 --- a/src/test/java/org/kohsuke/AppTest.java +++ b/src/test/java/org/kohsuke/AppTest.java @@ -40,6 +40,14 @@ import org.kohsuke.github.GitHub; import org.kohsuke.github.PagedIterable; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Date; +import java.util.Map; +import java.util.Set; +import java.util.List; + /** * Unit test for simple App. */ @@ -252,13 +260,30 @@ public void testMembership() throws Exception { Set members = gitHub.getOrganization("jenkinsci").getRepository("violations-plugin").getCollaboratorNames(); System.out.println(members.contains("kohsuke")); } - + @Test public void testMemberOrgs() throws Exception { Set o = gitHub.getUser("kohsuke").getOrganizations(); System.out.println(o); } + @Test + public void testOrgTeams() throws Exception { + kohsuke(); + int sz=0; + for (GHTeam t : gitHub.getOrganization("jenkinsci").listTeams()) { + assertNotNull(t.getName()); + sz++; + } + assertTrue(sz>1000); + } + + public void testOrgTeamByName() throws Exception { + kohsuke(); + GHTeam e = gitHub.getOrganization("jenkinsci").getTeamByName("Everyone"); + assertNotNull(e); + } + @Test public void testCommit() throws Exception { GHCommit commit = gitHub.getUser("jenkinsci").getRepository("jenkins").getCommit("08c1c9970af4d609ae754fbe803e06186e3206f7"); @@ -279,8 +304,19 @@ public void testListCommits() throws Exception { System.out.println(c.getSHA1()); sha1.add(c.getSHA1()); } - assertEquals("fdfad6be4db6f96faea1f153fb447b479a7a9cb7",sha1.get(0)); - assertEquals(1,sha1.size()); + assertEquals("fdfad6be4db6f96faea1f153fb447b479a7a9cb7", sha1.get(0)); + assertEquals(1, sha1.size()); + } + + public void testQueryCommits() throws Exception { + List sha1 = new ArrayList(); + for (GHCommit c : gitHub.getUser("jenkinsci").getRepository("jenkins").queryCommits() + .since(new Date(1199174400000L)).until(1201852800000L).path("pom.xml").list()) { + System.out.println(c.getSHA1()); + sha1.add(c.getSHA1()); + } + assertEquals("1cccddb22e305397151b2b7b87b4b47d74ca337b",sha1.get(0)); + assertEquals(29,sha1.size()); } @Test