/**
   * @param project
   * @param commitSHA1
   * @return
   */
  private Run getBuildBySHA1(Job project, String commitSHA1, boolean triggeredByMergeRequest) {
    List<Run> builds = project.getBuilds();
    for (Run build : builds) {
      BuildData data = build.getAction(BuildData.class);
      MergeRecord mergeRecord = build.getAction(MergeRecord.class);
      if (mergeRecord == null) {
        // Determine if build was triggered by a Merge Request event
        ParametersAction params = build.getAction(ParametersAction.class);

        if (params == null) continue;

        StringParameterValue sourceBranch =
            (StringParameterValue) params.getParameter("gitlabSourceBranch");
        StringParameterValue targetBranch =
            (StringParameterValue) params.getParameter("gitlabTargetBranch");
        boolean isMergeRequestBuild =
            (sourceBranch != null && !sourceBranch.value.equals(targetBranch.value));

        if (!triggeredByMergeRequest) {
          if (isMergeRequestBuild)
            // skip Merge Request builds
            continue;

          if (data.getLastBuiltRevision().getSha1String().contains(commitSHA1)) {
            return build;
          }
        } else {
          if (!isMergeRequestBuild)
            // skip Push builds
            continue;

          if (hasBeenBuilt(data, ObjectId.fromString(commitSHA1), build)) {
            return build;
          }
        }

      } else {
        Build b = data.lastBuild;
        boolean isMergeBuild =
            mergeRecord != null && !mergeRecord.getSha1().equals(b.getMarked().getSha1String());
        if (b != null
            && b.getMarked() != null
            && b.getMarked().getSha1String().equals(commitSHA1)) {
          if (triggeredByMergeRequest == isMergeBuild) {
            LOGGER.log(
                Level.FINE,
                build.getNumber()
                    + " Build found matching "
                    + commitSHA1
                    + " "
                    + (isMergeBuild ? "merge" : "normal")
                    + " build");
            return build;
          }
        }
      }
    }
    return null;
  }
  /**
   * visit project chain, from current through parents.
   *
   * @param visitor
   * @param run
   */
  public static void traverseChain(HudsonVisitor visitor, Run run) {
    if (run == null) return;

    traverse(visitor, run);

    RepositoryAction repositoryAction = run.getAction(RepositoryAction.class);

    if (repositoryAction != null) {
      if (repositoryAction instanceof ProjectRepositoryAction) {
        final ProjectRepositoryAction projectRepositoryAction =
            (ProjectRepositoryAction) repositoryAction;

        AbstractProject item =
            (AbstractProject)
                Hudson.getInstance().getItem(projectRepositoryAction.getProjectName());

        Optional<Run> r =
            Iterables.tryFind(
                item.getBuilds(),
                new Predicate<Run>() {
                  public boolean apply(Run run) {
                    return run.getNumber() == projectRepositoryAction.getBuildNumber();
                  }
                });

        if (r.isPresent()) traverseChain(visitor, r.get());
      }
    }
  }
  /**
   * visit a run
   *
   * @param visitor
   * @param run
   */
  public static void traverse(HudsonVisitor visitor, Run run) {
    if (run instanceof MavenModuleSetBuild) {
      MavenModuleSetBuild item = (MavenModuleSetBuild) run;

      visitor.visitModuleSet(item);

      Map<MavenModule, List<MavenBuild>> modulesMap = item.getModuleBuilds();

      for (List<MavenBuild> builds : modulesMap.values()) {
        for (MavenBuild build : builds) {

          log.trace("Visit mavenBuild {}", build);

          visitor.visitBuild(build);

          MavenArtifactRecord artifacts = build.getAction(MavenArtifactRecord.class);
          if (artifacts != null) {
            visitMavenArtifactRecord(visitor, build, artifacts);
          }
        }
      }
    } else {

      RepositoryArtifactRecords records = run.getAction(RepositoryArtifactRecords.class);
      if (records != null) {
        for (RepositoryArtifactRecord record : records.recordList) {
          visitRepositoryRecord(visitor, run, record);
        }
      }
    }
  }
 /**
  * @param project
  * @param branch
  * @return latest build of the branch specified that is not part of a merge request
  */
 @SuppressWarnings("rawtypes")
 private Run getBuildByBranch(Job project, String branch) {
   RunList<?> builds = project.getBuilds();
   for (Run build : builds) {
     BuildData data = build.getAction(BuildData.class);
     if (data != null && data.lastBuild != null) {
       MergeRecord merge = build.getAction(MergeRecord.class);
       boolean isMergeBuild =
           merge != null && !merge.getSha1().equals(data.lastBuild.getMarked().getSha1String());
       if (data.lastBuild.getRevision() != null && !isMergeBuild) {
         for (Branch b : data.lastBuild.getRevision().getBranches()) {
           if (b.getName().endsWith("/" + branch)) return build;
         }
       }
     }
   }
   return null;
 }
 private void populateWithParameters(Run build, String externalizableId) {
   ParametersAction action = build.getAction(ParametersAction.class);
   if (action != null) {
     List<ParameterValue> parameters = action.getParameters();
     if (parameters != null) {
       for (ParameterValue parameter : parameters) {
         if (parameter instanceof StringParameterValue) {
           put(
               "parameter-" + parameter.getName(),
               ((StringParameterValue) parameter).value,
               externalizableId);
         }
       }
     }
   }
 }
  /**
   * Waits for the given configurations to finish, retrying any that qualify to be rerun.
   *
   * @param execution Provided by the plugin.
   * @param patterns List of regular expression patterns used to scan the log to determine if a
   *     build should be rerun.
   * @param retries Mutable map that tracks the number of times a specific configuration has been
   *     retried.
   * @param configurations The configurations that have already been scheduled to run that should be
   *     waited for to finish.
   * @return The worst result of all the runs. If a build was rerun, only the result of the rerun is
   *     considered.
   * @throws InterruptedException
   * @throws IOException
   */
  private Result waitForMatrixRuns(
      MatrixBuild.MatrixBuildExecution execution,
      List<Pattern> patterns,
      Map<MatrixConfiguration, Integer> retries,
      LinkedList<MatrixConfiguration> configurations)
      throws InterruptedException, IOException {
    BuildListener listener = execution.getListener();
    PrintStream logger = listener.getLogger();

    Map<String, String> whyBlockedMap =
        new HashMap<
            String,
            String>(); // keep track of why builds are blocked so we can print unique messages when
    // they change.
    Result finalResult = Result.SUCCESS;
    int iteration = 0;
    boolean continueRetrying = true;
    while (!configurations.isEmpty()) {
      ++iteration;
      MatrixConfiguration configuration = configurations.removeFirst();
      if (isBuilding(execution, configuration, whyBlockedMap)) {
        if (iteration >= configurations.size()) {
          // Every time we loop through all the configurations, sleep for a bit.
          // This is to prevent polling too often while everything is still building.
          iteration = 0;
          Thread.sleep(1000);
        }
        configurations.add(configuration);
        continue;
      }

      Run parentBuild = execution.getBuild();
      MatrixRun matrixRun = configuration.getBuildByNumber(parentBuild.getNumber());
      Result runResult = matrixRun.getResult();
      if (continueRetrying
          && runResult.isWorseOrEqualTo(getWorseThanOrEqualTo())
          && runResult.isBetterOrEqualTo(getBetterThanOrEqualTo())) {
        if (matchesPattern(matrixRun, patterns)) {
          int retriedCount = retries.get(configuration);
          if (retriedCount < getMaxRetries()) {
            ++retriedCount;
            retries.put(configuration, retriedCount);
            // rerun
            String logMessage =
                String.format(
                    "%s was %s. Matched pattern to rerun. Rerunning (%d).",
                    matrixRun, runResult, retriedCount);
            listener.error(logMessage);

            HealedAction action = parentBuild.getAction(HealedAction.class);
            if (action == null) {
              //noinspection SynchronizationOnLocalVariableOrMethodParameter
              synchronized (parentBuild.getActions()) {
                action = parentBuild.getAction(HealedAction.class);
                if (action == null) {
                  action = new HealedAction(matrixRun.getCharset());
                  parentBuild.addAction(action);
                }
              }
            }
            action.addAutoHealedJob(matrixRun);

            MatrixConfiguration parent = matrixRun.getParent();
            if (parent != null) {
              // I'm paranoid about NPEs
              parent.removeRun(matrixRun);
              matrixRun.delete();
            } else {
              LOGGER.severe(
                  "couldn't remove old run, parent was null. This is a Jenkins core bug.");
            }
            scheduleConfigurationBuild(
                execution, configuration, new SelfHealingCause(parentBuild, retriedCount));
            configurations.add(configuration);
            continue;
          } else {
            String logMessage =
                String.format(
                    "%s was %s. Matched pattern to rerun, but the max number of retries (%d) has been met.",
                    matrixRun, runResult, getMaxRetries());
            listener.error(logMessage);
            if (getStopRetryingAfterOneFails()) {
              listener.error("Not retrying any more builds.");
              continueRetrying = false;
            }
          }
        } else {
          String logMessage =
              String.format(
                  "%s was %s. It did not match the pattern to rerun. Accepting result.",
                  matrixRun, runResult);
          logger.println(logMessage);
        }
      }
      notifyEndRun(matrixRun, execution.getAggregators(), execution.getListener());
      finalResult = finalResult.combine(runResult);
    }
    return finalResult;
  }