package argelbargel.jenkins.plugins.gitlab_branch_source; import argelbargel.jenkins.plugins.gitlab_branch_source.api.GitLabAPI; import argelbargel.jenkins.plugins.gitlab_branch_source.api.GitLabAPIException; import argelbargel.jenkins.plugins.gitlab_branch_source.api.GitLabMergeRequest; import argelbargel.jenkins.plugins.gitlab_branch_source.api.filters.GitLabMergeRequestFilter; import argelbargel.jenkins.plugins.gitlab_branch_source.events.GitLabSCMMergeRequestEvent; import argelbargel.jenkins.plugins.gitlab_branch_source.events.GitLabSCMPushEvent; import argelbargel.jenkins.plugins.gitlab_branch_source.events.GitLabSCMTagPushEvent; import argelbargel.jenkins.plugins.gitlab_branch_source.heads.GitLabSCMHead; import argelbargel.jenkins.plugins.gitlab_branch_source.heads.GitLabSCMMergeRequestHead; import argelbargel.jenkins.plugins.gitlab_branch_source.heads.GitLabSCMTagHead; import com.dabsquared.gitlabjenkins.gitlab.hook.model.MergeRequestObjectAttributes; import hudson.model.TaskListener; import jenkins.branch.MultiBranchProject; import jenkins.model.ParameterizedJobMixIn; import jenkins.plugins.git.AbstractGitSCMSource.SCMRevisionImpl; import jenkins.scm.api.*; import org.gitlab.api.models.GitlabBranch; import org.gitlab.api.models.GitlabTag; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import static argelbargel.jenkins.plugins.gitlab_branch_source.GitLabHelper.gitLabAPI; import static argelbargel.jenkins.plugins.gitlab_branch_source.heads.GitLabSCMHead.createBranch; import static argelbargel.jenkins.plugins.gitlab_branch_source.heads.GitLabSCMHead.createMergeRequest; import static argelbargel.jenkins.plugins.gitlab_branch_source.heads.GitLabSCMHead.createTag; import static argelbargel.jenkins.plugins.gitlab_branch_source.heads.GitLabSCMRefSpec.BRANCHES; import static argelbargel.jenkins.plugins.gitlab_branch_source.heads.GitLabSCMRefSpec.TAGS; import static hudson.model.TaskListener.NULL; import static java.util.Collections.emptyMap; import java.util.logging.Logger; import java.util.logging.Level; class SourceHeads { private static final Logger LOGGER = Logger.getLogger(SourceHeads.class.getName()); private static final SCMHeadObserver NOOP_OBSERVER = new SCMHeadObserver() { @Override public void observe(@Nonnull jenkins.scm.api.SCMHead head, @Nonnull SCMRevision revision) { /* NOOP */ } }; private static final SCMSourceCriteria ALL_CRITERIA = new SCMSourceCriteria() { @Override public boolean isHead(@Nonnull Probe probe, @Nonnull TaskListener taskListener) throws IOException { return true; } }; private static final String CAN_BE_MERGED = "can_be_merged"; private final GitLabSCMSource source; private transient Map<Integer, String> branchesWithMergeRequestsCache; SourceHeads(GitLabSCMSource source) { this.source = source; } void retrieve(@CheckForNull SCMSourceCriteria criteria, @Nonnull SCMHeadObserver observer, @CheckForNull SCMHeadEvent<?> event, @Nonnull TaskListener listener) throws IOException, InterruptedException { if (event instanceof GitLabSCMMergeRequestEvent) { retrieveMergeRequest(criteria, observer, (GitLabSCMMergeRequestEvent) event, listener); } else if (event instanceof GitLabSCMTagPushEvent) { retrieveTag(criteria, observer, (GitLabSCMTagPushEvent) event, listener); } else if (event instanceof GitLabSCMPushEvent) { retrieveBranch(criteria, observer, (GitLabSCMPushEvent) event, listener); } else { retrieveAll(criteria, observer, listener); } } @CheckForNull SCMRevision retrieve(@Nonnull SCMHead head, @Nonnull TaskListener listener) throws IOException, InterruptedException { log(listener, Messages.GitLabSCMSource_retrievingRevision(head.getName())); try { return new SCMRevisionImpl(head, retrieveRevision(head)); } catch (NoSuchElementException e) { return null; } } private String retrieveRevision(SCMHead head) throws GitLabAPIException { if (head instanceof GitLabSCMMergeRequestHead) { return retrieveMergeRequestRevision(((GitLabSCMMergeRequestHead) head).getId()); } else if (head instanceof GitLabSCMTagHead) { return retrieveTagRevision(head.getName()); } return retrieveBranchRevision(head.getName()); } private String retrieveMergeRequestRevision(String id) throws GitLabAPIException { return api().getMergeRequest(source.getProjectId(), id).getSha(); } private String retrieveTagRevision(String name) throws GitLabAPIException { return api().getTag(source.getProjectId(), name).getCommit().getId(); } private String retrieveBranchRevision(String name) throws GitLabAPIException { return api().getBranch(source.getProjectId(), name).getCommit().getId(); } private void retrieveMergeRequest(SCMSourceCriteria criteria, @Nonnull SCMHeadObserver observer, @Nonnull GitLabSCMMergeRequestEvent event, @Nonnull TaskListener listener) throws IOException, InterruptedException { MergeRequestObjectAttributes attributes = event.getPayload().getObjectAttributes(); String targetBranch = attributes.getTargetBranch(); if (!source.isExcluded(targetBranch)) { int mrId = attributes.getIid(); log(listener, Messages.GitLabSCMSource_retrievingMergeRequest(mrId)); try { GitLabMergeRequest mr = api().getMergeRequest(source.getProjectId(), mrId); observe(criteria, observer, mr, listener); } catch (NoSuchElementException e) { log(listener, Messages.GitLabSCMSource_removedMergeRequest(mrId)); branchesWithMergeRequests(listener).remove(mrId); } } } private void retrieveBranch(SCMSourceCriteria criteria, @Nonnull SCMHeadObserver observer, @Nonnull GitLabSCMPushEvent event, @Nonnull TaskListener listener) throws IOException, InterruptedException { retrieveBranch(criteria, observer, BRANCHES.remoteName(event.getPayload().getRef()), listener); } private void retrieveBranch(SCMSourceCriteria criteria, @Nonnull SCMHeadObserver observer, String branchName, @Nonnull TaskListener listener) throws IOException, InterruptedException { if (!source.isExcluded(branchName)) { log(listener, Messages.GitLabSCMSource_retrievingBranch(branchName)); try { GitlabBranch branch = api().getBranch(source.getProjectId(), branchName); observe(criteria, observer, branch, listener); } catch (NoSuchElementException e) { log(listener, Messages.GitLabSCMSource_removedHead(branchName)); } } } private void retrieveTag(SCMSourceCriteria criteria, @Nonnull SCMHeadObserver observer, @Nonnull GitLabSCMTagPushEvent event, @Nonnull TaskListener listener) throws IOException, InterruptedException { retrieveTag(criteria, observer, TAGS.remoteName(event.getPayload().getRef()), listener); } private void retrieveTag(SCMSourceCriteria criteria, @Nonnull SCMHeadObserver observer, String tagName, @Nonnull TaskListener listener) throws IOException, InterruptedException { log(listener, Messages.GitLabSCMSource_retrievingTag(tagName)); try { GitlabTag tag = api().getTag(source.getProjectId(), tagName); tag.getCommit().getCommittedDate().getTime(); observe(criteria, observer, tag, listener); } catch (NoSuchElementException e) { log(listener, Messages.GitLabSCMSource_removedHead(tagName)); } } private void retrieveAll(@CheckForNull SCMSourceCriteria criteria, @Nonnull SCMHeadObserver observer, @Nonnull TaskListener listener) throws IOException, InterruptedException { // TODO: could/should we optimize based on SCMHeadObserver#getIncludes()? retrieveMergeRequests(criteria, observer, listener); retrieveBranches(criteria, observer, listener); retrieveTags(criteria, observer, listener); } private void retrieveMergeRequests(@CheckForNull SCMSourceCriteria criteria, @Nonnull SCMHeadObserver observer, @Nonnull TaskListener listener) throws IOException, InterruptedException { branchesWithMergeRequestsCache = new HashMap<>(); if (source.getProject().isMergeRequestsEnabled() && source.getSourceSettings().shouldMonitorMergeRequests()) { log(listener, Messages.GitLabSCMSource_retrievingMergeRequests()); GitLabMergeRequestFilter filter = source.getSourceSettings().createMergeRequestFilter(listener); for (GitLabMergeRequest mr : filter.filter(api().getMergeRequests(source.getProjectId()))) { checkInterrupt(); if (!source.isExcluded(mr.getTargetBranch())) { observe(criteria, observer, mr, listener); } } } } private void retrieveBranches(@CheckForNull SCMSourceCriteria criteria, @Nonnull SCMHeadObserver observer, @Nonnull TaskListener listener) throws InterruptedException, IOException { if (source.getSourceSettings().getBranchMonitorStrategy().getMonitored()) { log(listener, Messages.GitLabSCMSource_retrievingBranches()); for (GitlabBranch branch : api().getBranches(source.getProjectId())) { checkInterrupt(); if (!source.isExcluded(branch.getName())) { observe(criteria, observer, branch, listener); } } } } private void retrieveTags(@CheckForNull SCMSourceCriteria criteria, @Nonnull SCMHeadObserver observer, @Nonnull TaskListener listener) throws IOException, InterruptedException { if (source.getSourceSettings().getTagMonitorStrategy().getMonitored()) { log(listener, Messages.GitLabSCMSource_retrievingTags()); for (GitlabTag tag : api().getTags(source.getProjectId())) { checkInterrupt(); observe(criteria, observer, tag, listener); } } } private void observe(SCMSourceCriteria criteria, @Nonnull SCMHeadObserver observer, GitlabBranch branch, TaskListener listener) throws IOException, InterruptedException { log(listener, Messages.GitLabSCMSource_monitoringBranch(branch.getName())); boolean hasMergeRequest = branchesWithMergeRequests(NULL).containsValue(branch.getName()); if (hasMergeRequest && !source.getSourceSettings().getBranchMonitorStrategy().getBuildBranchesWithMergeRequests()) { log(listener, Messages.GitLabSCMSource_willNotBuildBranchWithMergeRequest(branch.getName())); } observe(criteria, observer, createBranch(source.getProjectId(), branch.getName(), branch.getCommit().getId(), hasMergeRequest), listener); } private void observe(SCMSourceCriteria criteria, @Nonnull SCMHeadObserver observer, GitlabTag tag, TaskListener listener) throws IOException, InterruptedException { log(listener, Messages.GitLabSCMSource_monitoringTag(tag.getName())); observe(criteria, observer, createTag(source.getProjectId(), tag.getName(), tag.getCommit().getId(), tag.getCommit().getCommittedDate().getTime()), listener); } private void observe(SCMSourceCriteria criteria, @Nonnull SCMHeadObserver observer, GitLabMergeRequest mergeRequest, @Nonnull TaskListener listener) throws IOException, InterruptedException { log(listener, Messages.GitLabSCMSource_monitoringMergeRequest(mergeRequest.getIid())); String targetBranch = mergeRequest.getTargetBranch(); GitLabSCMMergeRequestHead head = createMergeRequest( mergeRequest.getIid(), mergeRequest.getTitle(), mergeRequest.getIid(), createBranch(mergeRequest.getSourceProjectId(), mergeRequest.getSourceBranch(), mergeRequest.getSha()), createBranch(mergeRequest.getTargetProjectId(), targetBranch, retrieveBranchRevision(targetBranch)), Objects.equals(mergeRequest.getMergeStatus(), CAN_BE_MERGED)); if (source.getSourceSettings().buildUnmerged(head)) { observe(criteria, observer, head, listener); } if (source.getSourceSettings().buildMerged(head)) { if (!head.isMergeable() && buildOnlyMergeableRequests(head)) { log(listener, Messages.GitLabSCMSource_willNotBuildUnmergeableRequest(mergeRequest.getIid(), mergeRequest.getTargetBranch(), mergeRequest.getMergeStatus())); } observe(criteria, observer, head.merged(), listener); } if (!source.getSourceSettings().getBranchMonitorStrategy().getBuildBranchesWithMergeRequests() && head.fromOrigin()) { branchesWithMergeRequests(listener).put(mergeRequest.getIid(), mergeRequest.getSourceBranch()); } } private void observe(SCMSourceCriteria criteria, @Nonnull SCMHeadObserver observer, GitLabSCMHead head, TaskListener listener) throws IOException, InterruptedException { if (criteria == null || matches(criteria, head, listener)) { observer.observe(head, head.getRevision()); } } private boolean matches(SCMSourceCriteria criteria, GitLabSCMHead head, TaskListener listener) { SCMSourceCriteria.Probe probe = source.createProbe(head, head.getRevision()); try { if (criteria.isHead(probe, listener)) { log(listener, head.getName() + " (" + head.getRevision().getHash() + ") meets criteria"); return true; } else { log(listener, head.getName() + " (" + head.getRevision().getHash() + ") does not meet criteria"); } } catch (IOException e) { log(listener, "error checking criteria: " + e.getMessage()); } return false; } private GitLabAPI api() throws GitLabAPIException { return gitLabAPI(source.getSourceSettings()); } private void log(@Nonnull TaskListener listener, String message) { listener.getLogger().format(message + "\n"); } private Map<Integer, String> branchesWithMergeRequests(TaskListener listener) throws IOException, InterruptedException { if (source.getSourceSettings().getBranchMonitorStrategy().getBuildBranchesWithMergeRequests()) { return emptyMap(); } if (branchesWithMergeRequestsCache == null) { retrieveMergeRequests(ALL_CRITERIA, NOOP_OBSERVER, listener); } return branchesWithMergeRequestsCache; } @SuppressWarnings("SimplifiableIfStatement") private boolean buildOnlyMergeableRequests(SCMHead head) { if (head instanceof GitLabSCMMergeRequestHead) { return source.getSourceSettings().determineMergeRequestStrategyValue( ((GitLabSCMMergeRequestHead) head), source.getSourceSettings().getOriginMonitorStrategy().getBuildOnlyMergeableMerged(), source.getSourceSettings().getForksMonitorStrategy().getBuildOnlyMergeableMerged()); } return true; } private void checkInterrupt() throws InterruptedException { if (Thread.interrupted()) { throw new InterruptedException(); } } }