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); }
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; }
/** * @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; }
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); } }
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; } }
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()); }
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(); } }
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; }
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; }
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))); }
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; }
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); }
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); } }
/** * 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); }
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); } }
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); } } }
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 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); } } }
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(); }
/** * 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); } }
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"); } }
private void initPatchSet() throws OrmException { if (patchSet == null) { patchSet = cd.currentPatchSet(); } }
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; }
private static void reloadChanges(ChangeSet cs) throws OrmException { // Reload changes in case index was stale. for (ChangeData cd : cs.changes()) { cd.reloadChange(); } }
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); }
public SubmitRuleEvaluator(ChangeData cd) throws OrmException { this.cd = cd; this.control = cd.changeControl(); }