package org.jenkinsci.plugins.ghprb; import com.google.common.annotations.VisibleForTesting; import hudson.Launcher; import hudson.Util; import hudson.model.*; import hudson.model.queue.QueueTaskFuture; import hudson.plugins.git.util.BuildData; import org.apache.commons.lang.StringUtils; import org.jenkinsci.plugins.ghprb.extensions.GhprbBuildStep; import org.jenkinsci.plugins.ghprb.extensions.GhprbCommentAppender; import org.jenkinsci.plugins.ghprb.extensions.GhprbCommitStatus; import org.jenkinsci.plugins.ghprb.extensions.GhprbCommitStatusException; import org.jenkinsci.plugins.ghprb.extensions.GhprbExtension; import org.kohsuke.github.GHCommitState; import org.kohsuke.github.GHIssueState; import org.kohsuke.github.GHPullRequest; import org.kohsuke.github.GHUser; import java.io.IOException; import java.io.PrintStream; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; /** * @author janinko */ public class GhprbBuilds { private static final Logger logger = Logger.getLogger(GhprbBuilds.class.getName()); private final GhprbTrigger trigger; private final GhprbRepository repo; public GhprbBuilds(GhprbTrigger trigger, GhprbRepository repo) { this.trigger = trigger; this.repo = repo; } public void build(GhprbPullRequest pr, GHUser triggerSender, String commentBody) { URL url = null; GHUser prAuthor = null; try { url = pr.getUrl(); prAuthor = pr.getPullRequestAuthor(); } catch (IOException e) { logger.log(Level.SEVERE, "Unable to get PR author or PR URL", e); } GhprbCause cause = new GhprbCause(pr.getHead(), pr.getId(), pr.isMergeable(), pr.getTarget(), pr.getSource(), pr.getAuthorEmail(), pr.getTitle(), url, triggerSender, commentBody, pr.getCommitAuthor(), prAuthor, pr.getDescription(), pr.getAuthorRepoGitUrl(), repo.getName(), trigger.getGitHubApiAuth().getCredentialsId()); for (GhprbExtension ext : Ghprb.getJobExtensions(trigger, GhprbCommitStatus.class)) { if (ext instanceof GhprbCommitStatus) { try { ((GhprbCommitStatus) ext).onBuildTriggered(trigger.getActualProject(), pr.getHead(), pr.isMergeable(), pr.getId(), repo.getGitHubRepo()); } catch (GhprbCommitStatusException e) { repo.commentOnFailure(null, null, e); } } } QueueTaskFuture<?> build = trigger.scheduleBuild(cause, repo); if (build == null) { logger.log(Level.SEVERE, "Job did not start"); } } public void onStarted(Run<?, ?> build, TaskListener listener) { PrintStream logger = listener.getLogger(); GhprbCause c = Ghprb.getCause(build); if (c == null) { return; } GhprbTrigger trigger = Ghprb.extractTrigger(build); GhprbPullRequest pullRequest = trigger.getRepository().getPullRequest(c.getPullID()); pullRequest.setBuild(build); try { GHPullRequest pr = pullRequest.getPullRequest(true); int counter = 0; // If the PR is being resolved by GitHub then getMergeable will return null Boolean isMergeable = pr.getMergeable(); boolean isMerged = pr.isMerged(); while (isMergeable == null && !isMerged && counter++ < 60) { Thread.sleep(1000); isMergeable = pr.getMergeable(); isMerged = pr.isMerged(); } if (isMerged) { logger.println("PR has already been merged, builds using the merged sha1 will fail!!!"); } else if (isMergeable == null) { logger.println("PR merge status couldn't be retrieved, maybe GitHub hasn't settled yet"); } else if (isMergeable != c.isMerged()) { logger.println("!!! PR mergeability status has changed !!! "); if (isMergeable) { logger.println("PR now has NO merge conflicts"); } else if (!isMergeable) { logger.println("PR now has merge conflicts!"); } } } catch (Exception e) { logger.print("Unable to query GitHub for status of PullRequest"); e.printStackTrace(logger); } for (GhprbExtension ext : Ghprb.getJobExtensions(trigger, GhprbCommitStatus.class)) { if (ext instanceof GhprbCommitStatus) { try { ((GhprbCommitStatus) ext).onBuildStart(build, listener, repo.getGitHubRepo()); } catch (GhprbCommitStatusException e) { repo.commentOnFailure(build, listener, e); } } } try { String template = trigger.getBuildDescTemplate(); if (StringUtils.isEmpty(template)) { template = "<a title=\"$title\" href=\"$url\">PR #$pullId</a>: $abbrTitle"; } Map<String, String> vars = getVariables(c); template = Util.replaceMacro(template, vars); template = Ghprb.replaceMacros(build, listener, template); build.setDescription(template); } catch (IOException ex) { logger.print("Can't update build description"); ex.printStackTrace(logger); } } public Map<String, String> getVariables(GhprbCause c) { Map<String, String> vars = new HashMap<String, String>(); vars.put("title", c.getTitle()); if (c.getUrl() != null) { vars.put("url", c.getUrl().toString()); } else { vars.put("url", ""); } vars.put("pullId", Integer.toString(c.getPullID())); vars.put("abbrTitle", c.getAbbreviatedTitle()); return vars; } public void onCompleted(Run<?, ?> build, TaskListener listener) { GhprbCause c = Ghprb.getCause(build); if (c == null) { return; } // remove the BuildData action that we may have added earlier to avoid // having two of them, and because the one we added isn't correct // @see GhprbTrigger for (BuildData data : build.getActions(BuildData.class)) { if (data.getLastBuiltRevision() != null && !data.getLastBuiltRevision().getSha1String().equals(c.getCommit())) { build.getActions().remove(data); break; } } if (build.getResult() == Result.ABORTED) { GhprbBuildStep abortAction = build.getAction(GhprbBuildStep.class); if (abortAction != null) { return; } } for (GhprbExtension ext : Ghprb.getJobExtensions(trigger, GhprbCommitStatus.class)) { if (ext instanceof GhprbCommitStatus) { try { ((GhprbCommitStatus) ext).onBuildComplete(build, listener, repo.getGitHubRepo()); } catch (GhprbCommitStatusException e) { repo.commentOnFailure(build, listener, e); } } } GHCommitState state; state = Ghprb.getState(build); commentOnBuildResult(build, listener, state, c); // close failed pull request automatically if (state == GHCommitState.FAILURE && trigger.getAutoCloseFailedPullRequests()) { closeFailedRequest(listener, c); } } private void closeFailedRequest(TaskListener listener, GhprbCause c) { try { GHPullRequest pr = repo.getActualPullRequest(c.getPullID()); if (pr.getState().equals(GHIssueState.OPEN)) { repo.closePullRequest(c.getPullID()); } } catch (IOException ex) { listener.getLogger().println("Can't close pull request"); ex.printStackTrace(listener.getLogger()); } } @VisibleForTesting void commentOnBuildResult(Run<?, ?> build, TaskListener listener, GHCommitState state, GhprbCause c) { StringBuilder msg = new StringBuilder(); for (GhprbExtension ext : Ghprb.getJobExtensions(trigger, GhprbCommentAppender.class)) { if (ext instanceof GhprbCommentAppender) { String cmt = ((GhprbCommentAppender) ext).postBuildComment(build, listener); if ("--none--".equals(cmt)) { return; } msg.append(cmt); } } if (msg.length() > 0) { listener.getLogger().println(msg); repo.addComment(c.getPullID(), msg.toString(), build, listener); } } public void onEnvironmentSetup(@SuppressWarnings("rawtypes") Run build, Launcher launcher, TaskListener listener) { GhprbCause c = Ghprb.getCause(build); if (c == null) { return; } logger.log(Level.FINE, "Job: " + build.getFullDisplayName() + " Attempting to send GitHub commit status"); for (GhprbExtension ext : Ghprb.getJobExtensions(trigger, GhprbCommitStatus.class)) { if (ext instanceof GhprbCommitStatus) { try { ((GhprbCommitStatus) ext).onEnvironmentSetup(build, listener, repo.getGitHubRepo()); } catch (GhprbCommitStatusException e) { repo.commentOnFailure(build, listener, e); } } } } }