Пример #1
0
  private static String describeLabels(ChangeData cd, List<SubmitRecord.Label> labels)
      throws OrmException {
    List<String> labelResults = new ArrayList<>();
    for (SubmitRecord.Label lbl : labels) {
      switch (lbl.status) {
        case OK:
        case MAY:
          break;

        case REJECT:
          labelResults.add("blocked by " + lbl.label);
          break;

        case NEED:
          labelResults.add("needs " + lbl.label);
          break;

        case IMPOSSIBLE:
          labelResults.add("needs " + lbl.label + " (check project access)");
          break;

        default:
          throw new IllegalStateException(
              String.format(
                  "Unsupported SubmitRecord.Label %s for %s in %s",
                  lbl, cd.change().currentPatchSetId(), cd.change().getProject()));
      }
    }
    return Joiner.on("; ").join(labelResults);
  }
Пример #2
0
  private void checkSubmitRulesAndState(ChangeSet cs)
      throws ResourceConflictException, OrmException {

    StringBuilder msgbuf = new StringBuilder();
    List<Change.Id> problemChanges = new ArrayList<>();
    for (Change.Id id : cs.ids()) {
      try {
        ChangeData cd = changeDataFactory.create(db, id);
        if (cd.change().getStatus() != Change.Status.NEW) {
          throw new ResourceConflictException(
              "Change " + cd.change().getChangeId() + " is in state " + cd.change().getStatus());
        } else {
          records.put(cd.change().getId(), checkSubmitRule(cd));
        }
      } catch (ResourceConflictException e) {
        msgbuf.append(e.getMessage() + "\n");
        problemChanges.add(id);
      }
    }
    String reason = msgbuf.toString();
    if (!reason.isEmpty()) {
      throw new ResourceConflictException(
          "The change could not be "
              + "submitted because it depends on change(s) "
              + problemChanges.toString()
              + ", which could not be submitted "
              + "because:\n"
              + reason);
    }
  }
  private static ChangeKind getChangeKindInternal(
      ChangeKindCache cache,
      ReviewDb db,
      Change change,
      PatchSet patch,
      ChangeData.Factory changeDataFactory,
      ProjectCache projectCache,
      GitRepositoryManager repoManager) {
    Repository repo = null;
    // TODO - dborowitz: add NEW_CHANGE type for default.
    ChangeKind kind = ChangeKind.REWORK;
    // Trivial case: if we're on the first patch, we don't need to open
    // the repository.
    if (patch.getId().get() > 1) {
      try {
        ProjectState projectState = projectCache.checkedGet(change.getProject());

        repo = repoManager.openRepository(change.getProject());

        ChangeData cd = changeDataFactory.create(db, change);
        Collection<PatchSet> patchSetCollection = cd.patches();
        PatchSet priorPs = patch;
        for (PatchSet ps : patchSetCollection) {
          if (ps.getId().get() < patch.getId().get()
              && (ps.getId().get() > priorPs.getId().get() || priorPs == patch)) {
            // We only want the previous patch set, so walk until the last one
            priorPs = ps;
          }
        }

        // If we still think the previous patch is the current patch,
        // we only have one patch set.  Return the default.
        // This can happen if a user creates a draft, uploads a second patch,
        // and deletes the draft.
        if (priorPs != patch) {
          kind =
              cache.getChangeKind(
                  projectState,
                  repo,
                  ObjectId.fromString(priorPs.getRevision().get()),
                  ObjectId.fromString(patch.getRevision().get()));
        }
      } catch (IOException | OrmException e) {
        // Do nothing; assume we have a complex change
        log.warn(
            "Unable to get change kind for patchSet "
                + patch.getPatchSetId()
                + "of change "
                + change.getChangeId(),
            e);
      } finally {
        if (repo != null) {
          repo.close();
        }
      }
    }
    return kind;
  }
Пример #4
0
 /**
  * @param ps patch set of the change to evaluate. If not set, the current patch set will be loaded
  *     from {@link #evaluate()} or {@link #getSubmitType}.
  * @return this
  */
 public SubmitRuleEvaluator setPatchSet(PatchSet ps) {
   checkArgument(
       ps.getId().getParentKey().equals(cd.getId()),
       "Patch set %s does not match change %s",
       ps.getId(),
       cd.getId());
   patchSet = ps;
   return this;
 }
Пример #5
0
 private void abandonAllOpenChanges(Project.NameKey destProject) throws NoSuchChangeException {
   try {
     for (ChangeData cd : internalChangeQuery.byProjectOpen(destProject)) {
       abandonOneChange(cd.change());
     }
   } catch (IOException | OrmException e) {
     logWarn("Cannot abandon changes for deleted project ", e);
   }
 }
Пример #6
0
 private SubmitType getSubmitType(ChangeData cd) {
   try {
     SubmitTypeRecord str = cd.submitTypeRecord();
     return str.isOk() ? str.type : null;
   } catch (OrmException e) {
     logError("Failed to get submit type for " + cd.getId(), e);
     return null;
   }
 }
Пример #7
0
  private void updateChangeStatus(OpenBranch ob, List<ChangeData> submitted, IdentifiedUser caller)
      throws ResourceConflictException {
    List<Change.Id> problemChanges = new ArrayList<>(submitted.size());
    logDebug("Updating change status for {} changes", submitted.size());

    for (ChangeData cd : submitted) {
      Change.Id id = cd.getId();
      try {
        Change c = cd.change();
        CodeReviewCommit commit = commits.get(id);
        CommitMergeStatus s = commit != null ? commit.getStatusCode() : null;
        logDebug("Status of change {} ({}) on {}: {}", id, commit.name(), c.getDest(), s);
        checkState(s != null, "status not set for change %s; expected to previously fail fast", id);
        setApproval(cd, caller);

        ObjectId mergeResultRev =
            ob.mergeTip != null ? ob.mergeTip.getMergeResults().get(commit) : null;
        String txt = s.getMessage();

        // The change notes must be forcefully reloaded so that the SUBMIT
        // approval that we added earlier is visible
        commit.notes().reload();
        if (s == CommitMergeStatus.CLEAN_MERGE) {
          setMerged(c, message(c, txt + getByAccountName(commit)), mergeResultRev);
        } else if (s == CommitMergeStatus.CLEAN_REBASE || s == CommitMergeStatus.CLEAN_PICK) {
          setMerged(
              c,
              message(c, txt + " as " + commit.name() + getByAccountName(commit)),
              mergeResultRev);
        } else if (s == CommitMergeStatus.ALREADY_MERGED) {
          setMerged(c, null, mergeResultRev);
        } else {
          throw new IllegalStateException(
              "unexpected status "
                  + s
                  + " for change "
                  + c.getId()
                  + "; expected to previously fail fast");
        }
      } catch (OrmException | IOException err) {
        logWarn("Error updating change status for " + id, err);
        problemChanges.add(id);
      }
    }

    if (problemChanges.isEmpty()) {
      return;
    }
    StringBuilder msg = new StringBuilder("Error updating status of change");
    if (problemChanges.size() == 1) {
      msg.append(' ').append(problemChanges.iterator().next());
    } else {
      msg.append('s').append(Joiner.on(", ").join(problemChanges));
    }
    throw new ResourceConflictException(msg.toString());
  }
Пример #8
0
  private List<Term> evaluateImpl(
      String userRuleLocatorName,
      String userRuleWrapperName,
      String filterRuleLocatorName,
      String filterRuleWrapperName,
      CurrentUser user)
      throws RuleEvalException {
    PrologEnvironment env = getPrologEnvironment(user);
    try {
      Term sr = env.once("gerrit", userRuleLocatorName, new VariableTerm());
      if (fastEvalLabels) {
        env.once("gerrit", "assume_range_from_label");
      }

      List<Term> results = new ArrayList<>();
      try {
        for (Term[] template : env.all("gerrit", userRuleWrapperName, sr, new VariableTerm())) {
          results.add(template[1]);
        }
      } catch (ReductionLimitException err) {
        throw new RuleEvalException(
            String.format(
                "%s on change %d of %s", err.getMessage(), cd.getId().get(), getProjectName()));
      } catch (RuntimeException err) {
        throw new RuleEvalException(
            String.format(
                "Exception calling %s on change %d of %s", sr, cd.getId().get(), getProjectName()),
            err);
      } finally {
        reductionsConsumed = env.getReductions();
      }

      Term resultsTerm = toListTerm(results);
      if (!skipFilters) {
        resultsTerm =
            runSubmitFilters(resultsTerm, env, filterRuleLocatorName, filterRuleWrapperName);
      }
      List<Term> r;
      if (resultsTerm instanceof ListTerm) {
        r = Lists.newArrayList();
        for (Term t = resultsTerm; t instanceof ListTerm; ) {
          ListTerm l = (ListTerm) t;
          r.add(l.car().dereference());
          t = l.cdr().dereference();
        }
      } else {
        r = Collections.emptyList();
      }
      submitRule = sr;
      return r;
    } finally {
      env.close();
    }
  }
Пример #9
0
 private PrologEnvironment getPrologEnvironment(CurrentUser user) throws RuleEvalException {
   ProjectState projectState = control.getProjectControl().getProjectState();
   PrologEnvironment env;
   try {
     if (rule == null) {
       env = projectState.newPrologEnvironment();
     } else {
       env = projectState.newPrologEnvironment("stdin", new StringReader(rule));
     }
   } catch (CompileException err) {
     String msg;
     if (rule == null && control.getProjectControl().isOwner()) {
       msg = String.format("Cannot load rules.pl for %s: %s", getProjectName(), err.getMessage());
     } else if (rule != null) {
       msg = err.getMessage();
     } else {
       msg = String.format("Cannot load rules.pl for %s", getProjectName());
     }
     throw new RuleEvalException(msg, err);
   }
   env.set(StoredValues.REVIEW_DB, cd.db());
   env.set(StoredValues.CHANGE_DATA, cd);
   env.set(StoredValues.CHANGE_CONTROL, control);
   if (user != null) {
     env.set(StoredValues.CURRENT_USER, user);
   }
   return env;
 }
Пример #10
0
  private Term runSubmitFilters(
      Term results,
      PrologEnvironment env,
      String filterRuleLocatorName,
      String filterRuleWrapperName)
      throws RuleEvalException {
    ProjectState projectState = control.getProjectControl().getProjectState();
    PrologEnvironment childEnv = env;
    for (ProjectState parentState : projectState.parents()) {
      PrologEnvironment parentEnv;
      try {
        parentEnv = parentState.newPrologEnvironment();
      } catch (CompileException err) {
        throw new RuleEvalException(
            "Cannot consult rules.pl for " + parentState.getProject().getName(), err);
      }

      parentEnv.copyStoredValues(childEnv);
      Term filterRule = parentEnv.once("gerrit", filterRuleLocatorName, new VariableTerm());
      try {
        if (fastEvalLabels) {
          env.once("gerrit", "assume_range_from_label");
        }

        Term[] template =
            parentEnv.once(
                "gerrit", filterRuleWrapperName, filterRule, results, new VariableTerm());
        results = template[2];
      } catch (ReductionLimitException err) {
        throw new RuleEvalException(
            String.format(
                "%s on change %d of %s",
                err.getMessage(), cd.getId().get(), parentState.getProject().getName()));
      } catch (RuntimeException err) {
        throw new RuleEvalException(
            String.format(
                "Exception calling %s on change %d of %s",
                filterRule, cd.getId().get(), parentState.getProject().getName()),
            err);
      } finally {
        reductionsConsumed += env.getReductions();
      }
      childEnv = parentEnv;
    }
    return results;
  }
Пример #11
0
 private List<SubmitRecord> invalidResult(Term rule, Term record, String reason) {
   return ruleError(
       String.format(
           "Submit rule %s for change %s of %s output " + "invalid result: %s%s",
           rule,
           cd.getId(),
           getProjectName(),
           record,
           (reason == null ? "" : ". Reason: " + reason)));
 }
Пример #12
0
 private MergeTip preMerge(
     SubmitStrategy strategy, List<ChangeData> submitted, CodeReviewCommit branchTip)
     throws IntegrationException, OrmException {
   logDebug(
       "Running submit strategy {} for {} commits {}",
       strategy.getClass().getSimpleName(),
       submitted.size(),
       submitted);
   List<CodeReviewCommit> toMerge = new ArrayList<>(submitted.size());
   for (ChangeData cd : submitted) {
     CodeReviewCommit commit = commits.get(cd.change().getId());
     checkState(
         commit != null, "commit for %s not found by validateChangeList", cd.change().getId());
     toMerge.add(commit);
   }
   MergeTip mergeTip = strategy.run(branchTip, toMerge);
   logDebug("Produced {} new commits", strategy.getNewCommits().size());
   commits.putAll(strategy.getNewCommits());
   return mergeTip;
 }
Пример #13
0
  private void checkMergeStrategyResults(ChangeSet cs, Collection<BranchBatch> batches)
      throws ResourceConflictException {
    for (ChangeData cd : flattenBatches(batches)) {
      Change.Id id = cd.getId();
      CodeReviewCommit commit = commits.get(id);
      CommitMergeStatus s = commit != null ? commit.getStatusCode() : null;
      if (s == null) {
        problems.put(id, "internal error: change not processed by merge strategy");
        continue;
      }
      switch (s) {
        case CLEAN_MERGE:
        case CLEAN_REBASE:
        case CLEAN_PICK:
        case ALREADY_MERGED:
          break; // Merge strategy accepted this change.

        case PATH_CONFLICT:
        case REBASE_MERGE_CONFLICT:
        case MANUAL_RECURSIVE_MERGE:
        case CANNOT_CHERRY_PICK_ROOT:
        case NOT_FAST_FORWARD:
          // TODO(dborowitz): Reformat these messages to be more appropriate for
          // short problem descriptions.
          problems.put(id, CharMatcher.is('\n').collapseFrom(s.getMessage(), ' '));
          break;

        case MISSING_DEPENDENCY:
          problems.put(id, "depends on change that was not submitted");
          break;

        default:
          problems.put(id, "unspecified merge failure: " + s);
          break;
      }
    }
    failFast(cs);
  }
Пример #14
0
 private Multimap<ObjectId, PatchSet.Id> getRevisions(OpenRepo or, Collection<ChangeData> cds)
     throws IntegrationException {
   try {
     List<String> refNames = new ArrayList<>(cds.size());
     for (ChangeData cd : cds) {
       Change c = cd.change();
       if (c != null) {
         refNames.add(c.currentPatchSetId().toRefName());
       }
     }
     Multimap<ObjectId, PatchSet.Id> revisions = HashMultimap.create(cds.size(), 1);
     for (Map.Entry<String, Ref> e :
         or.repo
             .getRefDatabase()
             .exactRef(refNames.toArray(new String[refNames.size()]))
             .entrySet()) {
       revisions.put(e.getValue().getObjectId(), PatchSet.Id.fromRef(e.getKey()));
     }
     return revisions;
   } catch (IOException | OrmException e) {
     throw new IntegrationException("Failed to validate changes", e);
   }
 }
Пример #15
0
  /**
   * Evaluate the submit rules.
   *
   * @return List of {@link SubmitRecord} objects returned from the evaluated rules, including any
   *     errors.
   */
  public List<SubmitRecord> evaluate() {
    Change c = control.getChange();
    if (!allowClosed && c.getStatus().isClosed()) {
      SubmitRecord rec = new SubmitRecord();
      rec.status = SubmitRecord.Status.CLOSED;
      return Collections.singletonList(rec);
    }
    if (!allowDraft) {
      if (c.getStatus() == Change.Status.DRAFT) {
        return cannotSubmitDraft();
      }
      try {
        initPatchSet();
      } catch (OrmException e) {
        return ruleError("Error looking up patch set " + control.getChange().currentPatchSetId());
      }
      if (patchSet.isDraft()) {
        return cannotSubmitDraft();
      }
    }

    List<Term> results;
    try {
      results =
          evaluateImpl(
              "locate_submit_rule",
              "can_submit",
              "locate_submit_filter",
              "filter_submit_results",
              control.getUser());
    } catch (RuleEvalException e) {
      return ruleError(e.getMessage(), e);
    }

    if (results.isEmpty()) {
      // This should never occur. A well written submit rule will always produce
      // at least one result informing the caller of the labels that are
      // required for this change to be submittable. Each label will indicate
      // whether or not that is actually possible given the permissions.
      return ruleError(
          String.format(
              "Submit rule '%s' for change %s of %s has " + "no solution.",
              getSubmitRuleName(), cd.getId(), getProjectName()));
    }

    return resultsToSubmitRecord(getSubmitRule(), results);
  }
Пример #16
0
 private List<SubmitRecord> cannotSubmitDraft() {
   try {
     if (!control.isDraftVisible(cd.db(), cd)) {
       return createRuleError("Patch set " + patchSet.getId() + " not found");
     }
     initPatchSet();
     if (patchSet.isDraft()) {
       return createRuleError("Cannot submit draft patch sets");
     } else {
       return createRuleError("Cannot submit draft changes");
     }
   } catch (OrmException err) {
     String msg = "Cannot check visibility of patch set " + patchSet.getId();
     log.error(msg, err);
     return createRuleError(msg);
   }
 }
Пример #17
0
 private void checkSubmitRulesAndState(ChangeSet cs) {
   for (ChangeData cd : cs.changes()) {
     try {
       if (cd.change().getStatus() != Change.Status.NEW) {
         problems.put(
             cd.getId(),
             "Change " + cd.getId() + " is " + cd.change().getStatus().toString().toLowerCase());
       } else {
         checkSubmitRule(cd);
       }
     } catch (ResourceConflictException e) {
       problems.put(cd.getId(), e.getMessage());
     } catch (OrmException e) {
       String msg = "Error checking submit rules for change";
       log.warn(msg + " " + cd.getId(), e);
       problems.put(cd.getId(), msg);
     }
   }
 }
Пример #18
0
  public static void checkSubmitRule(ChangeData cd) throws ResourceConflictException, OrmException {
    PatchSet patchSet = cd.currentPatchSet();
    if (patchSet == null) {
      throw new ResourceConflictException("missing current patch set for change " + cd.getId());
    }
    List<SubmitRecord> results = cd.getSubmitRecords();
    if (results == null) {
      results = new SubmitRuleEvaluator(cd).evaluate();
      cd.setSubmitRecords(results);
    }
    if (findOkRecord(results).isPresent()) {
      // Rules supplied a valid solution.
      return;
    } else if (results.isEmpty()) {
      throw new IllegalStateException(
          String.format(
              "SubmitRuleEvaluator.evaluate for change %s " + "returned empty list for %s in %s",
              cd.getId(), patchSet.getId(), cd.change().getProject().get()));
    }

    for (SubmitRecord record : results) {
      switch (record.status) {
        case CLOSED:
          throw new ResourceConflictException("change is closed");

        case RULE_ERROR:
          throw new ResourceConflictException("submit rule error: " + record.errorMessage);

        case NOT_READY:
          throw new ResourceConflictException(describeLabels(cd, record.labels));

        default:
          throw new IllegalStateException(
              String.format(
                  "Unsupported SubmitRecord %s for %s in %s",
                  record, patchSet.getId().getId(), cd.change().getProject().get()));
      }
    }
    throw new IllegalStateException();
  }
Пример #19
0
  private void setApproval(ChangeData cd, IdentifiedUser user) throws OrmException, IOException {
    Timestamp timestamp = TimeUtil.nowTs();
    ChangeControl control = cd.changeControl();
    PatchSet.Id psId = cd.currentPatchSet().getId();
    PatchSet.Id psIdNewRev = commits.get(cd.change().getId()).change().currentPatchSetId();

    logDebug("Add approval for " + cd + " from user " + user);
    ChangeUpdate update = updateFactory.create(control, timestamp);
    update.putReviewer(user.getAccountId(), REVIEWER);
    List<SubmitRecord> record = records.get(cd.change().getId());
    if (record != null) {
      update.merge(record);
    }
    db.changes().beginTransaction(cd.change().getId());
    try {
      BatchMetaDataUpdate batch = approve(control, psId, user, update, timestamp);
      batch.write(update, new CommitBuilder());

      // If the submit strategy created a new revision (rebase, cherry-pick)
      // approve that as well
      if (!psIdNewRev.equals(psId)) {
        update.setPatchSetId(psId);
        update.commit();
        // Create a new ChangeUpdate instance because we need to store meta data
        // on another patch set (psIdNewRev).
        update = updateFactory.create(control, timestamp);
        batch = approve(control, psIdNewRev, user, update, timestamp);
        // Write update commit after all normalized label commits.
        batch.write(update, new CommitBuilder());
      }
      db.commit();
    } finally {
      db.rollback();
    }
    update.commit();
    indexer.index(db, cd.change());
  }
Пример #20
0
  private void updateChangeStatus(
      List<ChangeData> submitted, Branch.NameKey destBranch, boolean dryRun, IdentifiedUser caller)
      throws NoSuchChangeException, IntegrationException, ResourceConflictException, OrmException {
    if (!dryRun) {
      logDebug("Updating change status for {} changes", submitted.size());
    } else {
      logDebug("Checking change state for {} changes in a dry run", submitted.size());
    }
    MergeTip mergeTip = mergeTips.get(destBranch);
    for (ChangeData cd : submitted) {
      Change c = cd.change();
      CodeReviewCommit commit = commits.get(c.getId());
      CommitMergeStatus s = commit != null ? commit.getStatusCode() : null;
      if (s == null) {
        // Shouldn't ever happen, but leave the change alone. We'll pick
        // it up on the next pass.
        //
        logDebug(
            "Submitted change {} did not appear in set of new commits"
                + " produced by merge strategy",
            c.getId());
        continue;
      }

      if (!dryRun) {
        try {
          setApproval(cd, caller);
        } catch (IOException e) {
          throw new OrmException(e);
        }
      }

      String txt = s.getMessage();
      logDebug("Status of change {} ({}) on {}: {}", c.getId(), commit.name(), c.getDest(), s);
      // If mergeTip is null merge failed and mergeResultRev will not be read.
      ObjectId mergeResultRev = mergeTip != null ? mergeTip.getMergeResults().get(commit) : null;
      // The change notes must be forcefully reloaded so that the SUBMIT
      // approval that we added earlier is visible
      commit.notes().reload();
      try {
        ChangeMessage msg;
        switch (s) {
          case CLEAN_MERGE:
            if (!dryRun) {
              setMerged(c, message(c, txt + getByAccountName(commit)), mergeResultRev);
            }
            break;

          case CLEAN_REBASE:
          case CLEAN_PICK:
            if (!dryRun) {
              setMerged(
                  c,
                  message(c, txt + " as " + commit.name() + getByAccountName(commit)),
                  mergeResultRev);
            }
            break;

          case ALREADY_MERGED:
            if (!dryRun) {
              setMerged(c, null, mergeResultRev);
            }
            break;

          case PATH_CONFLICT:
          case REBASE_MERGE_CONFLICT:
          case MANUAL_RECURSIVE_MERGE:
          case CANNOT_CHERRY_PICK_ROOT:
          case NOT_FAST_FORWARD:
          case INVALID_PROJECT_CONFIGURATION:
          case INVALID_PROJECT_CONFIGURATION_PLUGIN_VALUE_NOT_PERMITTED:
          case INVALID_PROJECT_CONFIGURATION_PLUGIN_VALUE_NOT_EDITABLE:
          case INVALID_PROJECT_CONFIGURATION_PARENT_PROJECT_NOT_FOUND:
          case INVALID_PROJECT_CONFIGURATION_ROOT_PROJECT_CANNOT_HAVE_PARENT:
          case SETTING_PARENT_PROJECT_ONLY_ALLOWED_BY_ADMIN:
            setNew(commit.notes(), message(c, txt));
            throw new ResourceConflictException(
                "Cannot merge " + commit.name() + "\n" + s.getMessage());

          case MISSING_DEPENDENCY:
            logDebug("Change {} is missing dependency", c.getId());
            throw new IntegrationException("Cannot merge " + commit.name() + "\n" + s.getMessage());

          case REVISION_GONE:
            logDebug("Commit not found for change {}", c.getId());
            msg =
                new ChangeMessage(
                    new ChangeMessage.Key(c.getId(), ChangeUtil.messageUUID(db)),
                    null,
                    TimeUtil.nowTs(),
                    c.currentPatchSetId());
            msg.setMessage("Failed to read commit for this patch set");
            setNew(commit.notes(), msg);
            throw new IntegrationException(msg.getMessage());

          default:
            msg = message(c, "Unspecified merge failure: " + s.name());
            setNew(commit.notes(), msg);
            throw new IntegrationException(msg.getMessage());
        }
      } catch (OrmException | IOException err) {
        logWarn("Error updating change status for " + c.getId(), err);
      }
    }
  }
Пример #21
0
  public static List<SubmitRecord> checkSubmitRule(ChangeData cd)
      throws ResourceConflictException, OrmException {
    PatchSet patchSet = cd.currentPatchSet();
    if (patchSet == null) {
      throw new ResourceConflictException("missing current patch set for change " + cd.getId());
    }
    List<SubmitRecord> results = new SubmitRuleEvaluator(cd).setPatchSet(patchSet).evaluate();
    Optional<SubmitRecord> ok = findOkRecord(results);
    if (ok.isPresent()) {
      // Rules supplied a valid solution.
      return ImmutableList.of(ok.get());
    } else if (results.isEmpty()) {
      throw new IllegalStateException(
          String.format(
              "SubmitRuleEvaluator.evaluate for change %s " + "returned empty list for %s in %s",
              cd.getId(), patchSet.getId(), cd.change().getProject().get()));
    }

    for (SubmitRecord record : results) {
      switch (record.status) {
        case CLOSED:
          throw new ResourceConflictException(String.format("change %s is closed", cd.getId()));

        case RULE_ERROR:
          throw new ResourceConflictException(
              String.format("rule error for change %s: %s", cd.getId(), record.errorMessage));

        case NOT_READY:
          StringBuilder msg = new StringBuilder();
          msg.append(cd.getId() + ":");
          for (SubmitRecord.Label lbl : record.labels) {
            switch (lbl.status) {
              case OK:
              case MAY:
                continue;

              case REJECT:
                msg.append(" blocked by ").append(lbl.label);
                msg.append(";");
                continue;

              case NEED:
                msg.append(" needs ").append(lbl.label);
                msg.append(";");
                continue;

              case IMPOSSIBLE:
                msg.append(" needs ").append(lbl.label).append(" (check project access)");
                msg.append(";");
                continue;

              default:
                throw new IllegalStateException(
                    String.format(
                        "Unsupported SubmitRecord.Label %s for %s in %s in %s",
                        lbl.toString(),
                        patchSet.getId(),
                        cd.getId(),
                        cd.change().getProject().get()));
            }
          }
          throw new ResourceConflictException(msg.toString());

        default:
          throw new IllegalStateException(
              String.format(
                  "Unsupported SubmitRecord %s for %s in %s",
                  record, patchSet.getId().getId(), cd.change().getProject().get()));
      }
    }
    throw new IllegalStateException();
  }
Пример #22
0
  /**
   * Evaluate the submit type rules to get the submit type.
   *
   * @return record from the evaluated rules.
   */
  public SubmitTypeRecord getSubmitType() {
    try {
      initPatchSet();
    } catch (OrmException e) {
      return typeError("Error looking up patch set " + control.getChange().currentPatchSetId());
    }

    try {
      if (control.getChange().getStatus() == Change.Status.DRAFT
          && !control.isDraftVisible(cd.db(), cd)) {
        return SubmitTypeRecord.error("Patch set " + patchSet.getId() + " not found");
      }
      if (patchSet.isDraft() && !control.isDraftVisible(cd.db(), cd)) {
        return SubmitTypeRecord.error("Patch set " + patchSet.getId() + " not found");
      }
    } catch (OrmException err) {
      String msg = "Cannot read patch set " + patchSet.getId();
      log.error(msg, err);
      return SubmitTypeRecord.error(msg);
    }

    List<Term> results;
    try {
      results =
          evaluateImpl(
              "locate_submit_type",
              "get_submit_type",
              "locate_submit_type_filter",
              "filter_submit_type_results",
              // Do not include current user in submit type evaluation. This is used
              // for mergeability checks, which are stored persistently and so must
              // have a consistent view of the submit type.
              null);
    } catch (RuleEvalException e) {
      return typeError(e.getMessage(), e);
    }

    if (results.isEmpty()) {
      // Should never occur for a well written rule
      return typeError(
          "Submit rule '"
              + getSubmitRuleName()
              + "' for change "
              + cd.getId()
              + " of "
              + getProjectName()
              + " has no solution.");
    }

    Term typeTerm = results.get(0);
    if (!(typeTerm instanceof SymbolTerm)) {
      return typeError(
          "Submit rule '"
              + getSubmitRuleName()
              + "' for change "
              + cd.getId()
              + " of "
              + getProjectName()
              + " did not return a symbol.");
    }

    String typeName = ((SymbolTerm) typeTerm).name();
    try {
      return SubmitTypeRecord.OK(SubmitType.valueOf(typeName.toUpperCase()));
    } catch (IllegalArgumentException e) {
      return typeError(
          "Submit type rule "
              + getSubmitRule()
              + " for change "
              + cd.getId()
              + " of "
              + getProjectName()
              + " output invalid result: "
              + typeName);
    }
  }
Пример #23
0
  private Set<Change.Id> parseChangeId(String idstr) throws UnloggedFailure, OrmException {
    Set<Change.Id> matched = new HashSet<>(4);
    boolean isCommit = idstr.matches("^([0-9a-fA-F]{4," + RevId.LEN + "})$");

    // By newer style changeKey?
    //
    boolean changeKeyParses = idstr.matches("^I[0-9a-f]*$");
    if (changeKeyParses) {
      for (ChangeData cd : queryProvider.get().byKeyPrefix(idstr)) {
        matchChange(matched, cd.change());
      }
    }

    // By commit?
    //
    if (isCommit) {
      RevId id = new RevId(idstr);
      ResultSet<PatchSet> patches;
      if (id.isComplete()) {
        patches = db.patchSets().byRevision(id);
      } else {
        patches = db.patchSets().byRevisionRange(id, id.max());
      }

      for (PatchSet ps : patches) {
        matchChange(matched, ps.getId().getParentKey());
      }
    }

    // By older style changeId?
    //
    boolean changeIdParses = false;
    if (idstr.matches("^[1-9][0-9]*$")) {
      Change.Id id;
      try {
        id = Change.Id.parse(idstr);
        changeIdParses = true;
      } catch (IllegalArgumentException e) {
        id = null;
        changeIdParses = false;
      }

      if (changeIdParses) {
        matchChange(matched, id);
      }
    }

    if (!changeKeyParses && !isCommit && !changeIdParses) {
      throw error("\"" + idstr + "\" is not a valid change");
    }

    switch (matched.size()) {
      case 0:
        throw error("\"" + idstr + "\" no such change");

      case 1:
        return matched;

      default:
        throw error("\"" + idstr + "\" matches multiple changes");
    }
  }
Пример #24
0
 private void initPatchSet() throws OrmException {
   if (patchSet == null) {
     patchSet = cd.currentPatchSet();
   }
 }
Пример #25
0
  private ListMultimap<SubmitType, ChangeData> validateChangeList(Collection<ChangeData> submitted)
      throws IntegrationException {
    logDebug("Validating {} changes", submitted.size());
    ListMultimap<SubmitType, ChangeData> toSubmit = ArrayListMultimap.create();

    Map<String, Ref> allRefs;
    try {
      allRefs = repo.getRefDatabase().getRefs(ALL);
    } catch (IOException e) {
      throw new IntegrationException(e.getMessage(), e);
    }

    Set<ObjectId> tips = new HashSet<>();
    for (Ref r : allRefs.values()) {
      tips.add(r.getObjectId());
    }

    for (ChangeData cd : submitted) {
      ChangeControl ctl;
      Change chg;
      try {
        ctl = cd.changeControl();
        // Reload change in case index was stale.
        chg = cd.reloadChange();
      } catch (OrmException e) {
        throw new IntegrationException("Failed to validate changes", e);
      }
      Change.Id changeId = cd.getId();
      if (chg.getStatus() != Change.Status.NEW) {
        logDebug("Change {} is not new: {}", changeId, chg.getStatus());
        continue;
      }
      if (chg.currentPatchSetId() == null) {
        logError("Missing current patch set on change " + changeId);
        commits.put(changeId, CodeReviewCommit.noPatchSet(ctl));
        continue;
      }

      PatchSet ps;
      Branch.NameKey destBranch = chg.getDest();
      try {
        ps = cd.currentPatchSet();
      } catch (OrmException e) {
        throw new IntegrationException("Cannot query the database", e);
      }
      if (ps == null || ps.getRevision() == null || ps.getRevision().get() == null) {
        logError("Missing patch set or revision on change " + changeId);
        commits.put(changeId, CodeReviewCommit.noPatchSet(ctl));
        continue;
      }

      String idstr = ps.getRevision().get();
      ObjectId id;
      try {
        id = ObjectId.fromString(idstr);
      } catch (IllegalArgumentException iae) {
        logError("Invalid revision on patch set " + ps.getId());
        commits.put(changeId, CodeReviewCommit.noPatchSet(ctl));
        continue;
      }

      if (!tips.contains(id)) {
        // TODO Technically the proper way to do this test is to use a
        // RevWalk on "$id --not --all" and test for an empty set. But
        // that is way slower than looking for a ref directly pointing
        // at the desired tip. We should always have a ref available.
        //
        // TODO this is actually an error, the branch is gone but we
        // want to merge the issue. We can't safely do that if the
        // tip is not reachable.
        //
        logError(
            "Revision " + idstr + " of patch set " + ps.getId() + " is not contained in any ref");
        commits.put(changeId, CodeReviewCommit.revisionGone(ctl));
        continue;
      }

      CodeReviewCommit commit;
      try {
        commit = rw.parseCommit(id);
      } catch (IOException e) {
        logError("Invalid commit " + idstr + " on patch set " + ps.getId(), e);
        commits.put(changeId, CodeReviewCommit.revisionGone(ctl));
        continue;
      }

      // TODO(dborowitz): Consider putting ChangeData in CodeReviewCommit.
      commit.setControl(ctl);
      commit.setPatchsetId(ps.getId());
      commits.put(changeId, commit);

      MergeValidators mergeValidators = mergeValidatorsFactory.create();
      try {
        mergeValidators.validatePreMerge(repo, commit, destProject, destBranch, ps.getId());
      } catch (MergeValidationException mve) {
        logDebug(
            "Revision {} of patch set {} failed validation: {}",
            idstr,
            ps.getId(),
            mve.getStatus());
        commit.setStatusCode(mve.getStatus());
        continue;
      }

      SubmitType submitType;
      submitType = getSubmitType(commit.getControl(), ps);
      if (submitType == null) {
        logError("No submit type for revision " + idstr + " of patch set " + ps.getId());
        commit.setStatusCode(CommitMergeStatus.NO_SUBMIT_TYPE);
        continue;
      }

      commit.add(canMergeFlag);
      toSubmit.put(submitType, cd);
    }
    logDebug("Submitting on this run: {}", toSubmit);
    return toSubmit;
  }
Пример #26
0
 private static void reloadChanges(ChangeSet cs) throws OrmException {
   // Reload changes in case index was stale.
   for (ChangeData cd : cs.changes()) {
     cd.reloadChange();
   }
 }
Пример #27
0
  private BranchBatch validateChangeList(OpenRepo or, Collection<ChangeData> submitted)
      throws IntegrationException {
    logDebug("Validating {} changes", submitted.size());
    List<ChangeData> toSubmit = new ArrayList<>(submitted.size());
    Multimap<ObjectId, PatchSet.Id> revisions = getRevisions(or, submitted);

    SubmitType submitType = null;
    ChangeData choseSubmitTypeFrom = null;
    for (ChangeData cd : submitted) {
      Change.Id changeId = cd.getId();
      ChangeControl ctl;
      Change chg;
      try {
        ctl = cd.changeControl();
        chg = cd.change();
      } catch (OrmException e) {
        logProblem(changeId, e);
        continue;
      }
      if (chg.currentPatchSetId() == null) {
        String msg = "Missing current patch set on change";
        logError(msg + " " + changeId);
        problems.put(changeId, msg);
        continue;
      }

      PatchSet ps;
      Branch.NameKey destBranch = chg.getDest();
      try {
        ps = cd.currentPatchSet();
      } catch (OrmException e) {
        logProblem(changeId, e);
        continue;
      }
      if (ps == null || ps.getRevision() == null || ps.getRevision().get() == null) {
        logProblem(changeId, "Missing patch set or revision on change");
        continue;
      }

      String idstr = ps.getRevision().get();
      ObjectId id;
      try {
        id = ObjectId.fromString(idstr);
      } catch (IllegalArgumentException e) {
        logProblem(changeId, e);
        continue;
      }

      if (!revisions.containsEntry(id, ps.getId())) {
        // TODO this is actually an error, the branch is gone but we
        // want to merge the issue. We can't safely do that if the
        // tip is not reachable.
        //
        logProblem(
            changeId,
            "Revision "
                + idstr
                + " of patch set "
                + ps.getPatchSetId()
                + " does not match "
                + ps.getId().toRefName()
                + " for change");
        continue;
      }

      CodeReviewCommit commit;
      try {
        commit = or.rw.parseCommit(id);
      } catch (IOException e) {
        logProblem(changeId, e);
        continue;
      }

      // TODO(dborowitz): Consider putting ChangeData in CodeReviewCommit.
      commit.setControl(ctl);
      commit.setPatchsetId(ps.getId());
      commits.put(changeId, commit);

      MergeValidators mergeValidators = mergeValidatorsFactory.create();
      try {
        mergeValidators.validatePreMerge(or.repo, commit, or.project, destBranch, ps.getId());
      } catch (MergeValidationException mve) {
        problems.put(changeId, mve.getMessage());
        continue;
      }

      SubmitType st = getSubmitType(cd);
      if (st == null) {
        logProblem(changeId, "No submit type for change");
        continue;
      }
      if (submitType == null) {
        submitType = st;
        choseSubmitTypeFrom = cd;
      } else if (st != submitType) {
        problems.put(
            changeId,
            String.format(
                "Change has submit type %s, but previously chose submit type %s "
                    + "from change %s in the same batch",
                st, submitType, choseSubmitTypeFrom.getId()));
        continue;
      }
      commit.add(or.canMergeFlag);
      toSubmit.add(cd);
    }
    logDebug("Submitting on this run: {}", toSubmit);
    return new AutoValue_MergeOp_BranchBatch(submitType, toSubmit);
  }
Пример #28
0
 public SubmitRuleEvaluator(ChangeData cd) throws OrmException {
   this.cd = cd;
   this.control = cd.changeControl();
 }