diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/AbstractGitHubSCMHeadProcessErrorStrategy.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/AbstractGitHubSCMHeadProcessErrorStrategy.java new file mode 100644 index 000000000..179f185a8 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/AbstractGitHubSCMHeadProcessErrorStrategy.java @@ -0,0 +1,13 @@ +package org.jenkinsci.plugins.github_branch_source; + +import jenkins.scm.api.SCMHead; + +public abstract class AbstractGitHubSCMHeadProcessErrorStrategy { + public abstract boolean shouldIgnore(SCMHead head, Throwable t); + + /** {@inheritDoc} */ + public abstract boolean equals(Object o); + + /** {@inheritDoc} */ + public abstract int hashCode(); +} diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMHeadProcessIgnoreErrorTrait.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMHeadProcessIgnoreErrorTrait.java new file mode 100644 index 000000000..d562f7f78 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMHeadProcessIgnoreErrorTrait.java @@ -0,0 +1,123 @@ +package org.jenkinsci.plugins.github_branch_source; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; +import java.util.Objects; +import jenkins.scm.api.SCMHead; +import jenkins.scm.api.SCMHeadCategory; +import jenkins.scm.api.SCMSource; +import jenkins.scm.api.trait.SCMBuilder; +import jenkins.scm.api.trait.SCMSourceContext; +import jenkins.scm.api.trait.SCMSourceTrait; +import jenkins.scm.api.trait.SCMSourceTraitDescriptor; +import org.kohsuke.stapler.DataBoundConstructor; + +public class GitHubSCMHeadProcessIgnoreErrorTrait extends SCMSourceTrait { + private boolean ignoreBranch; + private boolean ignorePR; + private boolean ignoreTag; + + @DataBoundConstructor + public GitHubSCMHeadProcessIgnoreErrorTrait( + boolean ignoreBranch, boolean ignorePR, boolean ignoreTag) { + this.ignoreBranch = ignoreBranch; + this.ignorePR = ignorePR; + this.ignoreTag = ignoreTag; + } + + public boolean isIgnoreBranch() { + return ignoreBranch; + } + + public boolean isIgnorePR() { + return ignorePR; + } + + public boolean isIgnoreTag() { + return ignoreTag; + } + + @Override + protected void decorateContext(SCMSourceContext context) { + GitHubSCMSourceContext githubContext = (GitHubSCMSourceContext) context; + githubContext.withHeadProcessErrorStrategy( + new GitHubSCMHeadProcessIgnoreErrorStrategy(ignoreBranch, ignorePR, ignoreTag)); + } + + @Override + public boolean includeCategory(@NonNull SCMHeadCategory category) { + return category.isUncategorized(); + } + + @Extension + public static class DescriptorImpl extends SCMSourceTraitDescriptor { + + @NonNull + @Override + public String getDisplayName() { + return "Ignore Indexing Errors"; + } + + @Override + public Class getBuilderClass() { + return GitHubSCMBuilder.class; + } + + @Override + public Class getContextClass() { + return GitHubSCMSourceContext.class; + } + + @Override + public Class getSourceClass() { + return GitHubSCMSource.class; + } + } + + private static final class GitHubSCMHeadProcessIgnoreErrorStrategy + extends AbstractGitHubSCMHeadProcessErrorStrategy { + + private boolean ignoreBranch; + private boolean ignorePR; + private boolean ignoreTag; + + public GitHubSCMHeadProcessIgnoreErrorStrategy( + boolean ignoreBranch, boolean ignorePR, boolean ignoreTag) { + this.ignoreBranch = ignoreBranch; + this.ignorePR = ignorePR; + this.ignoreTag = ignoreTag; + } + + @Override + public boolean shouldIgnore(SCMHead head, Throwable t) { + if (head instanceof BranchSCMHead) { + return ignoreBranch; + } + + if (head instanceof PullRequestSCMHead) { + return ignorePR; + } + + if (head instanceof GitHubTagSCMHead) { + return ignoreTag; + } + + return true; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GitHubSCMHeadProcessIgnoreErrorStrategy that = (GitHubSCMHeadProcessIgnoreErrorStrategy) o; + return ignoreBranch == that.ignoreBranch + && ignorePR == that.ignorePR + && ignoreTag == that.ignoreTag; + } + + @Override + public int hashCode() { + return Objects.hash(ignoreBranch, ignorePR, ignoreTag); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMProbe.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMProbe.java index 3f0a1d11b..c4127e490 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMProbe.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMProbe.java @@ -163,6 +163,11 @@ public SCMProbeStat stat(@NonNull String path) throws IOException { } } catch (FileNotFoundException fnf) { // means that does not exist and this is handled below this try/catch block. + // } catch (IOException e) { + // Throwable t = e.getCause(); + // if (!(t instanceof MismatchedInputException)) { + // throw e; + // } } return SCMProbeStat.fromType(SCMFile.Type.NONEXISTENT); } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java index ba5bb4ab1..c0be27918 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java @@ -1043,6 +1043,7 @@ public GHPermissionType fetch(String username) && this.shouldRetrieve(observer, event, BranchSCMHead.class)) { listener.getLogger().format("%n Checking branches...%n"); int count = 0; + int errorCount = 0; for (final GHBranch branch : request.getBranches()) { count++; String branchName = branch.getName(); @@ -1053,27 +1054,43 @@ public GHPermissionType fetch(String username) HyperlinkNote.encodeTo( resolvedRepositoryUrl + "/tree/" + branchName, branchName)); BranchSCMHead head = new BranchSCMHead(branchName); - if (request.process( - head, - new SCMRevisionImpl(head, branch.getSHA1()), - new SCMSourceRequest.ProbeLambda() { - @NonNull - @Override - public SCMSourceCriteria.Probe create( - @NonNull BranchSCMHead head, @Nullable SCMRevisionImpl revisionInfo) - throws IOException, InterruptedException { - return new GitHubSCMProbe( - apiUri, credentials, ghRepository, head, revisionInfo); - } - }, - new CriteriaWitness(listener))) { - listener - .getLogger() - .format("%n %d branches were processed (query completed)%n", count); - break; + try { + if (request.process( + head, + new SCMRevisionImpl(head, branch.getSHA1()), + new SCMSourceRequest.ProbeLambda() { + @NonNull + @Override + public SCMSourceCriteria.Probe create( + @NonNull BranchSCMHead head, @Nullable SCMRevisionImpl revisionInfo) + throws IOException, InterruptedException { + return new GitHubSCMProbe( + apiUri, credentials, ghRepository, head, revisionInfo); + } + }, + new CriteriaWitness(listener))) { + listener + .getLogger() + .format("%n %d branches were processed (query completed)%n", count); + break; + } + } catch (IOException e) { + if (request.headProcessErrorStrategies().stream() + .anyMatch(item -> item.shouldIgnore(head, e))) { + listener.getLogger().format("%n Error while processing branch %s%n", branchName); + Functions.printStackTrace(e, listener.getLogger()); + errorCount++; + } else { + throw e; + } } } listener.getLogger().format("%n %d branches were processed%n", count); + if (errorCount > 0) { + listener + .getLogger() + .format("%n %d branches encountered errors and were orphaned.%n", errorCount); + } } if (request.isFetchPRs() && !request.isComplete() @@ -1099,10 +1116,19 @@ public SCMSourceCriteria.Probe create( try { retrievePullRequest( apiUri, credentials, ghRepository, pr, strategies, request, listener); - } catch (FileNotFoundException e) { - listener.getLogger().format("%n Error while processing pull request %d%n", number); - Functions.printStackTrace(e, listener.getLogger()); - errorCount++; + } catch (IOException e) { + PullRequestSCMHead head = new PullRequestSCMHead(pr, "PR", false); + if (e instanceof FileNotFoundException + || request.headProcessErrorStrategies().stream() + .anyMatch(item -> item.shouldIgnore(head, e))) { + listener + .getLogger() + .format("%n Error while processing pull request %d%n", number); + Functions.printStackTrace(e, listener.getLogger()); + errorCount++; + } else { + throw e; + } } count++; } @@ -1110,7 +1136,8 @@ public SCMSourceCriteria.Probe create( if (errorCount > 0) { listener .getLogger() - .format("%n %d pull requests encountered errors and were orphaned.%n", count); + .format( + "%n %d pull requests encountered errors and were orphaned.%n", errorCount); } } if (request.isFetchTags() @@ -1118,6 +1145,7 @@ public SCMSourceCriteria.Probe create( && this.shouldRetrieve(observer, event, GitHubTagSCMHead.class)) { listener.getLogger().format("%n Checking tags...%n"); int count = 0; + int errorCount = 0; for (final GHRef tag : request.getTags()) { String tagName = tag.getRef(); if (!tagName.startsWith(Constants.R_TAGS)) { @@ -1154,27 +1182,40 @@ public SCMSourceCriteria.Probe create( } } GitHubTagSCMHead head = new GitHubTagSCMHead(tagName, tagDate); - if (request.process( - head, - new GitTagSCMRevision(head, sha), - new SCMSourceRequest.ProbeLambda() { - @NonNull - @Override - public SCMSourceCriteria.Probe create( - @NonNull GitHubTagSCMHead head, @Nullable GitTagSCMRevision revisionInfo) - throws IOException, InterruptedException { - return new GitHubSCMProbe( - apiUri, credentials, ghRepository, head, revisionInfo); - } - }, - new CriteriaWitness(listener))) { - listener - .getLogger() - .format("%n %d tags were processed (query completed)%n", count); - break; + try { + if (request.process( + head, + new GitTagSCMRevision(head, sha), + new SCMSourceRequest.ProbeLambda() { + @NonNull + @Override + public SCMSourceCriteria.Probe create( + @NonNull GitHubTagSCMHead head, @Nullable GitTagSCMRevision revisionInfo) + throws IOException, InterruptedException { + return new GitHubSCMProbe( + apiUri, credentials, ghRepository, head, revisionInfo); + } + }, + new CriteriaWitness(listener))) { + listener + .getLogger() + .format("%n %d tags were processed (query completed)%n", count); + break; + } + } catch (IOException e) { + if (e instanceof FileNotFoundException || true) { + listener.getLogger().format("%n Error while processing tag %s%n", tagName); + Functions.printStackTrace(e, listener.getLogger()); + errorCount++; + } } } listener.getLogger().format("%n %d tags were processed%n", count); + if (errorCount > 0) { + listener + .getLogger() + .format("%n %d tags encountered errors and were orphaned.%n", errorCount); + } } } listener.getLogger().format("%nFinished examining %s%n%n", fullName); @@ -1258,7 +1299,6 @@ private static void retrievePullRequest( Connector.release(gitHub); } } - if (request.process( new PullRequestSCMHead(pr, branchName, strategy == ChangeRequestCheckoutStrategy.MERGE), null, diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceContext.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceContext.java index 0ed5599a4..114ec1ea6 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceContext.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceContext.java @@ -58,6 +58,7 @@ public class GitHubSCMSourceContext * requests. */ private boolean wantForkPRs; + /** Set of {@link ChangeRequestCheckoutStrategy} to create for each origin pull request. */ @NonNull private Set originPRStrategies = @@ -75,6 +76,9 @@ public class GitHubSCMSourceContext */ private final List notificationStrategies = new ArrayList<>(); + private final List headProcessErrorStrategies = + new ArrayList<>(); + /** * Constructor. * @@ -172,6 +176,10 @@ public final List notificationStrategies() { } return Collections.unmodifiableList(notificationStrategies); } + + public final List headProcessErrorStrategies() { + return Collections.unmodifiableList(headProcessErrorStrategies); + } /** * Returns {@code true} if notifications should be disabled. * @@ -297,6 +305,25 @@ public final GitHubSCMSourceContext withNotificationStrategy( return this; } + public final GitHubSCMSourceContext withHeadProcessErrorStrategies( + List strategies) { + headProcessErrorStrategies.clear(); + for (AbstractGitHubSCMHeadProcessErrorStrategy strategy : strategies) { + if (!headProcessErrorStrategies.contains(strategy)) { + headProcessErrorStrategies.add(strategy); + } + } + return this; + } + + public final GitHubSCMSourceContext withHeadProcessErrorStrategy( + AbstractGitHubSCMHeadProcessErrorStrategy strategy) { + if (!headProcessErrorStrategies.contains(strategy)) { + headProcessErrorStrategies.add(strategy); + } + return this; + } + /** * Defines the notification mode to use in this context. * diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceRequest.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceRequest.java index 7e2b1bae8..e5beaca0a 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceRequest.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceRequest.java @@ -29,13 +29,7 @@ import hudson.model.TaskListener; import java.io.Closeable; import java.io.IOException; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.util.*; import jenkins.scm.api.SCMHead; import jenkins.scm.api.SCMHeadOrigin; import jenkins.scm.api.SCMSource; @@ -67,6 +61,8 @@ public class GitHubSCMSourceRequest extends SCMSourceRequest { @NonNull private final Set originPRStrategies; /** The {@link ChangeRequestCheckoutStrategy} to create for each fork pull request. */ @NonNull private final Set forkPRStrategies; + + private final List headProcessErrorStrategies; /** * The set of pull request numbers that the request is scoped to or {@code null} if the request is * not limited. @@ -122,6 +118,7 @@ public class GitHubSCMSourceRequest extends SCMSourceRequest { fetchForkPRs && !context.forkPRStrategies().isEmpty() ? Collections.unmodifiableSet(EnumSet.copyOf(context.forkPRStrategies())) : Collections.emptySet(); + headProcessErrorStrategies = context.headProcessErrorStrategies(); Set includes = context.observer().getIncludes(); if (includes != null) { Set pullRequestNumbers = new HashSet<>(includes.size()); @@ -480,4 +477,8 @@ public GitHubPermissionsSource getPermissionsSource() { public void setPermissionsSource(@CheckForNull GitHubPermissionsSource permissionsSource) { this.permissionsSource = permissionsSource; } + + public List headProcessErrorStrategies() { + return headProcessErrorStrategies; + } } diff --git a/src/main/resources/org/jenkinsci/plugins/github_branch_source/GitHubSCMHeadProcessIgnoreErrorTrait/config.jelly b/src/main/resources/org/jenkinsci/plugins/github_branch_source/GitHubSCMHeadProcessIgnoreErrorTrait/config.jelly new file mode 100644 index 000000000..573b675fd --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/github_branch_source/GitHubSCMHeadProcessIgnoreErrorTrait/config.jelly @@ -0,0 +1,12 @@ + + + + + + + + + + + +