diff --git a/pom.xml b/pom.xml
index 48d7ce7b8e..3ab470b664 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
github-api
- 1.47
+ 1.48
GitHub API for Java
http://github-api.kohsuke.org/
GitHub API for Java
@@ -16,6 +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.48
diff --git a/src/main/java/org/kohsuke/github/GHContent.java b/src/main/java/org/kohsuke/github/GHContent.java
new file mode 100644
index 0000000000..adc1285ed9
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GHContent.java
@@ -0,0 +1,171 @@
+package org.kohsuke.github;
+
+import java.io.IOException;
+
+import javax.xml.bind.DatatypeConverter;
+
+/**
+ * A Content of a repository.
+ *
+ * @author Alexandre COLLIGNON
+ */
+public final class GHContent {
+ private GHRepository owner;
+
+ private String type;
+ private String encoding;
+ private long size;
+ private String sha;
+ private String name;
+ private String path;
+ private String content;
+ 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
+
+ public GHRepository getOwner() {
+ return owner;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getEncoding() {
+ return encoding;
+ }
+
+ public long getSize() {
+ return size;
+ }
+
+ public String getSha() {
+ return sha;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * Retrieve the decoded content that is stored at this location.
+ *
+ * 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.
+ **/
+ public String getContent() throws IOException {
+ return new String(DatatypeConverter.parseBase64Binary(getEncodedContent()));
+ }
+
+ /**
+ * Retrieve the raw content that is stored at this location.
+ *
+ * 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.
+ **/
+ public String getEncodedContent() throws IOException {
+ 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;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public String getGitUrl() {
+ return git_url;
+ }
+
+ public String getHtmlUrl() {
+ return html_url;
+ }
+
+ public boolean isFile() {
+ return "file".equals(type);
+ }
+
+ public boolean isDirectory() {
+ return "dir".equals(type);
+ }
+
+ public GHContentUpdateResponse update(String newContent, String commitMessage) throws IOException {
+ return update(newContent, commitMessage, null);
+ }
+
+ public GHContentUpdateResponse update(String newContent, String commitMessage, String branch) throws IOException {
+ String encodedContent = DatatypeConverter.printBase64Binary(newContent.getBytes());
+
+ Requester requester = new Requester(owner.root)
+ .with("path", path)
+ .with("message", commitMessage)
+ .with("sha", sha)
+ .with("content", encodedContent)
+ .method("PUT");
+
+ if (branch != null) {
+ requester.with("branch", branch);
+ }
+
+ GHContentUpdateResponse response = requester.to(getApiRoute(), GHContentUpdateResponse.class);
+
+ response.getContent().wrap(owner);
+ response.getCommit().wrapUp(owner);
+
+ this.content = encodedContent;
+ return response;
+ }
+
+ public GHContentUpdateResponse delete(String message) throws IOException {
+ return delete(message, null);
+ }
+
+ public GHContentUpdateResponse delete(String commitMessage, String branch) throws IOException {
+ Requester requester = new Requester(owner.root)
+ .with("path", path)
+ .with("message", commitMessage)
+ .with("sha", sha)
+ .method("DELETE");
+
+ if (branch != null) {
+ requester.with("branch", branch);
+ }
+
+ GHContentUpdateResponse response = requester.to(getApiRoute(), GHContentUpdateResponse.class);
+
+ response.getCommit().wrapUp(owner);
+ return response;
+ }
+
+ private String getApiRoute() {
+ return "/repos/" + owner.getOwnerName() + "/" + owner.getName() + "/contents/" + path;
+ }
+
+ GHContent wrap(GHRepository owner) {
+ this.owner = owner;
+ return this;
+ }
+
+ public static GHContent[] wrap(GHContent[] contents, GHRepository repository) {
+ for (GHContent unwrappedContent : contents) {
+ unwrappedContent.wrap(repository);
+ }
+ return contents;
+ }
+}
diff --git a/src/main/java/org/kohsuke/github/GHContentUpdateResponse.java b/src/main/java/org/kohsuke/github/GHContentUpdateResponse.java
new file mode 100644
index 0000000000..faa0545ad6
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GHContentUpdateResponse.java
@@ -0,0 +1,18 @@
+package org.kohsuke.github;
+
+/**
+ * The response that is returned when updating
+ * repository content.
+**/
+public final class GHContentUpdateResponse {
+ private GHContent content;
+ private GHCommit commit;
+
+ public GHContent getContent() {
+ return content;
+ }
+
+ public GHCommit getCommit() {
+ return commit;
+ }
+}
diff --git a/src/main/java/org/kohsuke/github/GHKey.java b/src/main/java/org/kohsuke/github/GHKey.java
index e9692453d4..91632cf175 100644
--- a/src/main/java/org/kohsuke/github/GHKey.java
+++ b/src/main/java/org/kohsuke/github/GHKey.java
@@ -10,9 +10,9 @@
public class GHKey {
/*package almost final*/ GitHub root;
- private String url, key, title;
- private boolean verified;
- private int id;
+ protected String url, key, title;
+ protected boolean verified;
+ protected int id;
public int getId() {
return id;
diff --git a/src/main/java/org/kohsuke/github/GHMyself.java b/src/main/java/org/kohsuke/github/GHMyself.java
index 7a612007ac..70e732ac9e 100644
--- a/src/main/java/org/kohsuke/github/GHMyself.java
+++ b/src/main/java/org/kohsuke/github/GHMyself.java
@@ -34,6 +34,9 @@ public List getEmails() throws IOException {
/**
* Returns the read-only list of all the pulic keys of the current user.
*
+ * NOTE: When using OAuth authenticaiton, the READ/WRITE User scope is
+ * required by the GitHub APIs, otherwise you will get a 404 NOT FOUND.
+ *
* @return
* Always non-null.
*/
@@ -41,6 +44,21 @@ public List getPublicKeys() throws IOException {
return Collections.unmodifiableList(Arrays.asList(root.retrieve().to("/user/keys", GHKey[].class)));
}
+ /**
+ * Returns the read-only list of all the pulic verified keys of the current user.
+ *
+ * Differently from the getPublicKeys() method, the retrieval of the user's
+ * verified public keys does not require any READ/WRITE OAuth Scope to the
+ * user's profile.
+ *
+ * @return
+ * Always non-null.
+ */
+ public List getPublicVerifiedKeys() throws IOException {
+ return Collections.unmodifiableList(Arrays.asList(root.retrieve().to(
+ "/users/" + getLogin() + "/keys", GHVerifiedKey[].class)));
+ }
+
/**
* Gets the organization that this user belongs to.
*/
diff --git a/src/main/java/org/kohsuke/github/GHRepository.java b/src/main/java/org/kohsuke/github/GHRepository.java
index f1a3100a46..c1146f8e4b 100644
--- a/src/main/java/org/kohsuke/github/GHRepository.java
+++ b/src/main/java/org/kohsuke/github/GHRepository.java
@@ -42,6 +42,7 @@
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
+import javax.xml.bind.DatatypeConverter;
import static java.util.Arrays.*;
@@ -733,7 +734,66 @@ public GHMilestone getMilestone(int number) throws IOException {
}
return m;
}
-
+
+ public GHContent getFileContent(String path) throws IOException {
+ return getFileContent(path, null);
+ }
+
+ 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);
+
+ if (ref != null)
+ target = target + "?ref=" + ref;
+
+ return requester.to(target, GHContent.class).wrap(this);
+ }
+
+ public List getDirectoryContent(String path) throws IOException {
+ return getDirectoryContent(path, null);
+ }
+
+ public List getDirectoryContent(String path, String ref) throws IOException {
+ Requester requester = root.retrieve();
+ String target = String.format("/repos/%s/%s/contents/%s", owner.login, name, path);
+
+ if (ref != null)
+ target = target + "?ref=" + ref;
+
+ GHContent[] files = requester.to(target, GHContent[].class);
+
+ GHContent.wrap(files, this);
+
+ return Arrays.asList(files);
+ }
+
+ public GHContent getReadme() throws Exception {
+ return getFileContent("readme");
+ }
+
+ public GHContentUpdateResponse createContent(String content, String commitMessage, String path) throws IOException {
+ return createContent(content, commitMessage, path, null);
+ }
+
+ public GHContentUpdateResponse createContent(String content, 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()))
+ .method("PUT");
+
+ if (branch != null) {
+ requester.with("branch", branch);
+ }
+
+ GHContentUpdateResponse response = requester.to(getApiTailUrl("contents/" + path), GHContentUpdateResponse.class);
+
+ response.getContent().wrap(this);
+ response.getCommit().wrapUp(this);
+
+ return response;
+ }
+
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);
diff --git a/src/main/java/org/kohsuke/github/GHVerifiedKey.java b/src/main/java/org/kohsuke/github/GHVerifiedKey.java
new file mode 100644
index 0000000000..d81597d748
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GHVerifiedKey.java
@@ -0,0 +1,13 @@
+package org.kohsuke.github;
+
+public class GHVerifiedKey extends GHKey {
+
+ public GHVerifiedKey() {
+ this.verified = true;
+ }
+
+ @Override
+ public String getTitle() {
+ return (title == null ? "key-" + id : title);
+ }
+}
diff --git a/src/test/java/org/kohsuke/AppTest.java b/src/test/java/org/kohsuke/AppTest.java
index 64908e7a87..0b8177657c 100644
--- a/src/test/java/org/kohsuke/AppTest.java
+++ b/src/test/java/org/kohsuke/AppTest.java
@@ -46,6 +46,9 @@ public void setUp() throws Exception {
}
public void testRepoCRUD() throws Exception {
+ GHRepository existing = gitHub.getMyself().getRepository("github-api-test");
+ if (existing!=null)
+ existing.delete();
GHRepository r = gitHub.createRepository("github-api-test", "a test repository", "http://github-api.kohsuke.org/", true);
r.enableIssueTracker(false);
r.enableDownloads(false);
diff --git a/src/test/java/org/kohsuke/github/GHContentIntegrationTest.java b/src/test/java/org/kohsuke/github/GHContentIntegrationTest.java
new file mode 100644
index 0000000000..d375f85403
--- /dev/null
+++ b/src/test/java/org/kohsuke/github/GHContentIntegrationTest.java
@@ -0,0 +1,67 @@
+package org.kohsuke.github;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Integration test for {@link GHContent}.
+ */
+public class GHContentIntegrationTest extends TestCase {
+
+ private GitHub gitHub;
+ private GHRepository repo;
+ private String createdFilename;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ gitHub = GitHub.connect();
+ repo = gitHub.getRepository("acollign/github-api-test").fork();
+ createdFilename = UUID.randomUUID().toString();
+ }
+
+ public void testGetFileContent() throws Exception {
+ GHContent content = repo.getFileContent("ghcontent-ro/a-file-with-content");
+
+ assertTrue(content.isFile());
+ assertEquals("thanks for reading me\n", content.getContent());
+ }
+
+ public void testGetEmptyFileContent() throws Exception {
+ GHContent content = repo.getFileContent("ghcontent-ro/an-empty-file");
+
+ assertTrue(content.isFile());
+ assertEquals("", content.getContent());
+ }
+
+ public void testGetDirectoryContent() throws Exception {
+ List entries = repo.getDirectoryContent("ghcontent-ro/a-dir-with-3-entries");
+
+ assertTrue(entries.size() == 3);
+ }
+
+ public void testCRUDContent() throws Exception {
+ GHContentUpdateResponse created = repo.createContent("this is an awesome file I created\n", "Creating a file for integration tests.", createdFilename);
+ GHContent createdContent = created.getContent();
+
+ assertNotNull(created.getCommit());
+ assertNotNull(created.getContent());
+ assertNotNull(createdContent.getContent());
+ assertEquals("this is an awesome file I created\n", createdContent.getContent());
+
+ GHContentUpdateResponse updatedContentResponse = createdContent.update("this is some new content\n", "Updated file for integration tests.");
+ GHContent updatedContent = updatedContentResponse.getContent();
+
+ assertNotNull(updatedContentResponse.getCommit());
+ assertNotNull(updatedContentResponse.getContent());
+ assertEquals("this is some new content\n", updatedContent.getContent());
+
+ GHContentUpdateResponse deleteResponse = updatedContent.delete("Enough of this foolishness!");
+
+ assertNotNull(deleteResponse.getCommit());
+ assertNull(deleteResponse.getContent());
+ }
+}