package org.kohsuke.github; import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.net.URL; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import static org.kohsuke.github.internal.Previews.ANTIOPE; import static org.kohsuke.github.internal.Previews.GROOT; /** * A commit in a repository. * * @author Kohsuke Kawaguchi * @see GHRepository#getCommit(String) GHRepository#getCommit(String) * @see GHCommitComment#getCommit() GHCommitComment#getCommit() */ @SuppressFBWarnings(value = { "NP_UNWRITTEN_FIELD", "UWF_UNWRITTEN_FIELD" }, justification = "JSON API") public class GHCommit { private GHRepository owner; private ShortInfo commit; /** * Short summary of this commit. */ @SuppressFBWarnings( value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD", "UWF_UNWRITTEN_FIELD" }, justification = "JSON API") public static class ShortInfo { private GHAuthor author; private GHAuthor committer; private String message; private int comment_count; private GHVerification verification; static class Tree { String sha; } private Tree tree; /** * Gets author. * * @return the author */ @WithBridgeMethods(value = GHAuthor.class, castRequired = true) public GitUser getAuthor() { return author; } /** * Gets authored date. * * @return the authored date */ public Date getAuthoredDate() { return author.getDate(); } /** * Gets committer. * * @return the committer */ @WithBridgeMethods(value = GHAuthor.class, castRequired = true) public GitUser getCommitter() { return committer; } /** * Gets commit date. * * @return the commit date */ public Date getCommitDate() { return committer.getDate(); } /** * Gets message. * * @return Commit message. */ public String getMessage() { return message; } /** * Gets comment count. * * @return the comment count */ public int getCommentCount() { return comment_count; } /** * Gets Verification Status. * * @return the Verification status */ public GHVerification getVerification() { return verification; } } /** * The type GHAuthor. * * @deprecated Use {@link GitUser} instead. */ public static class GHAuthor extends GitUser { } /** * The type Stats. */ public static class Stats { int total, additions, deletions; } /** * A file that was modified. */ @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "It's being initilized by JSON deserialization") public static class File { String status; int changes, additions, deletions; String raw_url, blob_url, sha, patch; String filename, previous_filename; /** * Gets lines changed. * * @return Number of lines added + removed. */ public int getLinesChanged() { return changes; } /** * Gets lines added. * * @return Number of lines added. */ public int getLinesAdded() { return additions; } /** * Gets lines deleted. * * @return Number of lines removed. */ public int getLinesDeleted() { return deletions; } /** * Gets status. * * @return "modified", "added", or "removed" */ public String getStatus() { return status; } /** * Gets file name. * * @return Full path in the repository. */ @SuppressFBWarnings(value = "NM_CONFUSING", justification = "It's a part of the library's API and cannot be renamed") public String getFileName() { return filename; } /** * Gets previous filename. * * @return Previous path, in case file has moved. */ public String getPreviousFilename() { return previous_filename; } /** * Gets patch. * * @return The actual change. */ public String getPatch() { return patch; } /** * Gets raw url. * * @return URL like * 'https://raw.github.com/jenkinsci/jenkins/4eb17c197dfdcf8ef7ff87eb160f24f6a20b7f0e/core/pom.xml' that * resolves to the actual content of the file. */ public URL getRawUrl() { return GitHubClient.parseURL(raw_url); } /** * Gets blob url. * * @return URL like * 'https://github.com/jenkinsci/jenkins/blob/1182e2ebb1734d0653142bd422ad33c21437f7cf/core/pom.xml' * that resolves to the HTML page that describes this file. */ public URL getBlobUrl() { return GitHubClient.parseURL(blob_url); } /** * Gets sha. * * @return [0 -9a-f]{40} SHA1 checksum. */ public String getSha() { return sha; } } /** * The type Parent. */ public static class Parent { @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") String url; String sha; } static class User { // TODO: what if someone who doesn't have an account on GitHub makes a commit? @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") String url, avatar_url, gravatar_id; @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") int id; String login; } String url, html_url, sha; List files; Stats stats; List parents; User author, committer; /** * Gets commit short info. * * @return the commit short info * @throws IOException * the io exception */ public ShortInfo getCommitShortInfo() throws IOException { if (commit == null) populate(); return commit; } /** * Gets owner. * * @return the repository that contains the commit. */ public GHRepository getOwner() { return owner; } /** * Gets lines changed. * * @return the number of lines added + removed. * @throws IOException * if the field was not populated and refresh fails */ public int getLinesChanged() throws IOException { populate(); return stats.total; } /** * Gets lines added. * * @return Number of lines added. * @throws IOException * if the field was not populated and refresh fails */ public int getLinesAdded() throws IOException { populate(); return stats.additions; } /** * Gets lines deleted. * * @return Number of lines removed. * @throws IOException * if the field was not populated and refresh fails */ public int getLinesDeleted() throws IOException { populate(); return stats.deletions; } /** * Use this method to walk the tree. * * @return a GHTree to walk * @throws IOException * on error */ public GHTree getTree() throws IOException { return owner.getTree(getCommitShortInfo().tree.sha); } /** * Gets html url. * * @return URL of this commit like * "https://github.com/kohsuke/sandbox-ant/commit/8ae38db0ea5837313ab5f39d43a6f73de3bd9000" */ public URL getHtmlUrl() { return GitHubClient.parseURL(html_url); } /** * Gets sha 1. * * @return [0 -9a-f]{40} SHA1 checksum. */ public String getSHA1() { return sha; } /** * List of files changed/added/removed in this commit. * * @return Can be empty but never null. * @throws IOException * on error */ public List getFiles() throws IOException { populate(); return files != null ? Collections.unmodifiableList(files) : Collections.emptyList(); } /** * Gets parent sha 1 s. * * @return SHA1 of parent commit objects. */ public List getParentSHA1s() { if (parents == null) return Collections.emptyList(); return new AbstractList() { @Override public String get(int index) { return parents.get(index).sha; } @Override public int size() { return parents.size(); } }; } /** * Resolves the parent commit objects and return them. * * @return parent commit objects * @throws IOException * on error */ public List getParents() throws IOException { List r = new ArrayList(); for (String sha1 : getParentSHA1s()) r.add(owner.getCommit(sha1)); return r; } /** * Gets author. * * @return the author * @throws IOException * the io exception */ public GHUser getAuthor() throws IOException { return resolveUser(author); } /** * Gets the date the change was authored on. * * @return the date the change was authored on. * @throws IOException * if the information was not already fetched and an attempt at fetching the information failed. */ public Date getAuthoredDate() throws IOException { return getCommitShortInfo().getAuthoredDate(); } /** * Gets committer. * * @return the committer * @throws IOException * the io exception */ public GHUser getCommitter() throws IOException { return resolveUser(committer); } /** * Gets the date the change was committed on. * * @return the date the change was committed on. * @throws IOException * if the information was not already fetched and an attempt at fetching the information failed. */ public Date getCommitDate() throws IOException { return getCommitShortInfo().getCommitDate(); } private GHUser resolveUser(User author) throws IOException { if (author == null || author.login == null) return null; return owner.root.getUser(author.login); } /** * Retrieves a list of pull requests which contain this commit. * * @return {@link PagedIterable} with the pull requests which contain this commit */ @Preview(GROOT) @Deprecated public PagedIterable listPullRequests() { return owner.root.createRequest() .withPreview(GROOT) .withUrlPath(String.format("/repos/%s/%s/commits/%s/pulls", owner.getOwnerName(), owner.getName(), sha)) .toIterable(GHPullRequest[].class, item -> item.wrapUp(owner)); } /** * Retrieves a list of branches where this commit is the head commit. * * @return {@link PagedIterable} with the branches where the commit is the head commit * @throws IOException * the io exception */ @Preview(GROOT) @Deprecated public PagedIterable listBranchesWhereHead() throws IOException { return owner.root.createRequest() .withPreview(GROOT) .withUrlPath(String.format("/repos/%s/%s/commits/%s/branches-where-head", owner.getOwnerName(), owner.getName(), sha)) .toIterable(GHBranch[].class, item -> item.wrap(owner)); } /** * List comments paged iterable. * * @return {@link PagedIterable} with all the commit comments in this repository. */ public PagedIterable listComments() { return owner.root.createRequest() .withUrlPath( String.format("/repos/%s/%s/commits/%s/comments", owner.getOwnerName(), owner.getName(), sha)) .toIterable(GHCommitComment[].class, item -> item.wrap(owner)); } /** * Creates a commit comment. *

* I'm not sure how path/line/position parameters interact with each other. * * @param body * body of the comment * @param path * path of file being commented on * @param line * target line for comment * @param position * position on line * @return created GHCommitComment * @throws IOException * if comment is not created */ public GHCommitComment createComment(String body, String path, Integer line, Integer position) throws IOException { GHCommitComment r = owner.root.createRequest() .method("POST") .with("body", body) .with("path", path) .with("line", line) .with("position", position) .withUrlPath( String.format("/repos/%s/%s/commits/%s/comments", owner.getOwnerName(), owner.getName(), sha)) .fetch(GHCommitComment.class); return r.wrap(owner); } /** * Create comment gh commit comment. * * @param body * the body * @return the gh commit comment * @throws IOException * the io exception */ public GHCommitComment createComment(String body) throws IOException { return createComment(body, null, null, null); } /** * List statuses paged iterable. * * @return status of this commit, newer ones first. * @throws IOException * if statuses cannot be read */ public PagedIterable listStatuses() throws IOException { return owner.listCommitStatuses(sha); } /** * Gets last status. * * @return the last status of this commit, which is what gets shown in the UI. * @throws IOException * on error */ public GHCommitStatus getLastStatus() throws IOException { return owner.getLastCommitStatus(sha); } /** * Gets check-runs for given sha. * * @return check runs for given sha. * @throws IOException * on error */ @Preview(ANTIOPE) @Deprecated public PagedIterable getCheckRuns() throws IOException { return owner.getCheckRuns(sha); } /** * Some of the fields are not always filled in when this object is retrieved as a part of another API call. * * @throws IOException * on error */ void populate() throws IOException { if (files == null && stats == null) owner.root.createRequest().withUrlPath(owner.getApiTailUrl("commits/" + sha)).fetchInto(this); } GHCommit wrapUp(GHRepository owner) { this.owner = owner; return this; } }