Пример #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);
    }
  }
Пример #3
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);
   }
 }
Пример #4
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());
  }
Пример #5
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);
     }
   }
 }
Пример #6
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;
 }
Пример #7
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();
  }
Пример #8
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());
  }
Пример #9
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);
   }
 }
Пример #10
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);
      }
    }
  }
Пример #11
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();
  }
Пример #12
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");
    }
  }
Пример #13
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);
  }