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(); }
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()); }
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; }
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(); }
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); }
private void initPatchSet() throws OrmException { if (patchSet == null) { patchSet = cd.currentPatchSet(); } }