summaryrefslogtreecommitdiffstats
path: root/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
diff options
context:
space:
mode:
Diffstat (limited to 'gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java')
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java535
1 files changed, 0 insertions, 535 deletions
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
deleted file mode 100644
index 84ba88e071..0000000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
+++ /dev/null
@@ -1,535 +0,0 @@
-// Copyright (C) 2012 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.change;
-
-import static java.util.stream.Collectors.joining;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Strings;
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Sets;
-import com.google.gerrit.common.data.ParameterizedString;
-import com.google.gerrit.extensions.api.changes.SubmitInput;
-import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.PatchSetUtil;
-import com.google.gerrit.server.ProjectUtil;
-import com.google.gerrit.server.account.AccountsCollection;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.ChangeSet;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.MergeOp;
-import com.google.gerrit.server.git.MergeSuperSet;
-import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.permissions.ChangePermission;
-import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.query.change.InternalChangeQuery;
-import com.google.gerrit.server.update.UpdateException;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.OrmRuntimeException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import java.util.Set;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@Singleton
-public class Submit
- implements RestModifyView<RevisionResource, SubmitInput>, UiAction<RevisionResource> {
- private static final Logger log = LoggerFactory.getLogger(Submit.class);
-
- private static final String DEFAULT_TOOLTIP = "Submit patch set ${patchSet} into ${branch}";
- private static final String DEFAULT_TOOLTIP_ANCESTORS =
- "Submit patch set ${patchSet} and ancestors (${submitSize} changes "
- + "altogether) into ${branch}";
- private static final String DEFAULT_TOPIC_TOOLTIP =
- "Submit all ${topicSize} changes of the same topic "
- + "(${submitSize} changes including ancestors and other "
- + "changes related by topic)";
- private static final String BLOCKED_SUBMIT_TOOLTIP =
- "This change depends on other changes which are not ready";
- private static final String BLOCKED_HIDDEN_SUBMIT_TOOLTIP =
- "This change depends on other hidden changes which are not ready";
- private static final String BLOCKED_WORK_IN_PROGRESS = "This change is marked work in progress";
- private static final String CLICK_FAILURE_TOOLTIP = "Clicking the button would fail";
- private static final String CHANGE_UNMERGEABLE = "Problems with integrating this change";
- private static final String CHANGES_NOT_MERGEABLE = "Problems with change(s): ";
-
- public static class Output {
- transient Change change;
-
- private Output(Change c) {
- change = c;
- }
- }
-
- /**
- * Subclass of {@link SubmitInput} with special bits that may be flipped for testing purposes
- * only.
- */
- @VisibleForTesting
- public static class TestSubmitInput extends SubmitInput {
- public boolean failAfterRefUpdates;
-
- /**
- * For each change being submitted, an element is removed from this queue and, if the value is
- * true, a bogus ref update is added to the batch, in order to generate a lock failure during
- * execution.
- */
- public Queue<Boolean> generateLockFailures;
- }
-
- private final Provider<ReviewDb> dbProvider;
- private final GitRepositoryManager repoManager;
- private final PermissionBackend permissionBackend;
- private final ChangeData.Factory changeDataFactory;
- private final ChangeMessagesUtil cmUtil;
- private final ChangeNotes.Factory changeNotesFactory;
- private final Provider<MergeOp> mergeOpProvider;
- private final Provider<MergeSuperSet> mergeSuperSet;
- private final AccountsCollection accounts;
- private final String label;
- private final String labelWithParents;
- private final ParameterizedString titlePattern;
- private final ParameterizedString titlePatternWithAncestors;
- private final String submitTopicLabel;
- private final ParameterizedString submitTopicTooltip;
- private final boolean submitWholeTopic;
- private final Provider<InternalChangeQuery> queryProvider;
- private final PatchSetUtil psUtil;
-
- @Inject
- Submit(
- Provider<ReviewDb> dbProvider,
- GitRepositoryManager repoManager,
- PermissionBackend permissionBackend,
- ChangeData.Factory changeDataFactory,
- ChangeMessagesUtil cmUtil,
- ChangeNotes.Factory changeNotesFactory,
- Provider<MergeOp> mergeOpProvider,
- Provider<MergeSuperSet> mergeSuperSet,
- AccountsCollection accounts,
- @GerritServerConfig Config cfg,
- Provider<InternalChangeQuery> queryProvider,
- PatchSetUtil psUtil) {
- this.dbProvider = dbProvider;
- this.repoManager = repoManager;
- this.permissionBackend = permissionBackend;
- this.changeDataFactory = changeDataFactory;
- this.cmUtil = cmUtil;
- this.changeNotesFactory = changeNotesFactory;
- this.mergeOpProvider = mergeOpProvider;
- this.mergeSuperSet = mergeSuperSet;
- this.accounts = accounts;
- this.label =
- MoreObjects.firstNonNull(
- Strings.emptyToNull(cfg.getString("change", null, "submitLabel")), "Submit");
- this.labelWithParents =
- MoreObjects.firstNonNull(
- Strings.emptyToNull(cfg.getString("change", null, "submitLabelWithParents")),
- "Submit including parents");
- this.titlePattern =
- new ParameterizedString(
- MoreObjects.firstNonNull(
- cfg.getString("change", null, "submitTooltip"), DEFAULT_TOOLTIP));
- this.titlePatternWithAncestors =
- new ParameterizedString(
- MoreObjects.firstNonNull(
- cfg.getString("change", null, "submitTooltipAncestors"),
- DEFAULT_TOOLTIP_ANCESTORS));
- submitWholeTopic = wholeTopicEnabled(cfg);
- this.submitTopicLabel =
- MoreObjects.firstNonNull(
- Strings.emptyToNull(cfg.getString("change", null, "submitTopicLabel")),
- "Submit whole topic");
- this.submitTopicTooltip =
- new ParameterizedString(
- MoreObjects.firstNonNull(
- cfg.getString("change", null, "submitTopicTooltip"), DEFAULT_TOPIC_TOOLTIP));
- this.queryProvider = queryProvider;
- this.psUtil = psUtil;
- }
-
- @Override
- public Output apply(RevisionResource rsrc, SubmitInput input)
- throws RestApiException, RepositoryNotFoundException, IOException, OrmException,
- PermissionBackendException, UpdateException, ConfigInvalidException {
- input.onBehalfOf = Strings.emptyToNull(input.onBehalfOf);
- IdentifiedUser submitter;
- if (input.onBehalfOf != null) {
- submitter = onBehalfOf(rsrc, input);
- } else {
- rsrc.permissions().check(ChangePermission.SUBMIT);
- submitter = rsrc.getUser().asIdentifiedUser();
- }
-
- return new Output(mergeChange(rsrc, submitter, input));
- }
-
- public Change mergeChange(RevisionResource rsrc, IdentifiedUser submitter, SubmitInput input)
- throws OrmException, RestApiException, IOException, UpdateException, ConfigInvalidException,
- PermissionBackendException {
- Change change = rsrc.getChange();
- if (!change.getStatus().isOpen()) {
- throw new ResourceConflictException("change is " + ChangeUtil.status(change));
- } else if (!ProjectUtil.branchExists(repoManager, change.getDest())) {
- throw new ResourceConflictException(
- String.format("destination branch \"%s\" not found.", change.getDest().get()));
- } else if (!rsrc.getPatchSet().getId().equals(change.currentPatchSetId())) {
- // TODO Allow submitting non-current revision by changing the current.
- throw new ResourceConflictException(
- String.format(
- "revision %s is not current revision", rsrc.getPatchSet().getRevision().get()));
- }
-
- try (MergeOp op = mergeOpProvider.get()) {
- ReviewDb db = dbProvider.get();
- op.merge(db, change, submitter, true, input, false);
- try {
- change =
- changeNotesFactory.createChecked(db, change.getProject(), change.getId()).getChange();
- } catch (NoSuchChangeException e) {
- throw new ResourceConflictException("change is deleted");
- }
- }
-
- switch (change.getStatus()) {
- case MERGED:
- return change;
- case NEW:
- ChangeMessage msg = getConflictMessage(rsrc);
- if (msg != null) {
- throw new ResourceConflictException(msg.getMessage());
- }
- // $FALL-THROUGH$
- case ABANDONED:
- default:
- throw new ResourceConflictException("change is " + ChangeUtil.status(change));
- }
- }
-
- /**
- * @param cd the change the user is currently looking at
- * @param cs set of changes to be submitted at once
- * @param user the user who is checking to submit
- * @return a reason why any of the changes is not submittable or null
- */
- private String problemsForSubmittingChangeset(ChangeData cd, ChangeSet cs, CurrentUser user) {
- try {
- if (cs.furtherHiddenChanges()) {
- return BLOCKED_HIDDEN_SUBMIT_TOOLTIP;
- }
- for (ChangeData c : cs.changes()) {
- Set<ChangePermission> can =
- permissionBackend
- .user(user)
- .database(dbProvider)
- .change(c)
- .test(EnumSet.of(ChangePermission.READ, ChangePermission.SUBMIT));
- if (!can.contains(ChangePermission.READ)) {
- return BLOCKED_HIDDEN_SUBMIT_TOOLTIP;
- }
- if (!can.contains(ChangePermission.SUBMIT)) {
- return BLOCKED_SUBMIT_TOOLTIP;
- }
- if (c.change().isWorkInProgress()) {
- return BLOCKED_WORK_IN_PROGRESS;
- }
- MergeOp.checkSubmitRule(c, false);
- }
-
- Collection<ChangeData> unmergeable = unmergeableChanges(cs);
- if (unmergeable == null) {
- return CLICK_FAILURE_TOOLTIP;
- } else if (!unmergeable.isEmpty()) {
- for (ChangeData c : unmergeable) {
- if (c.change().getKey().equals(cd.change().getKey())) {
- return CHANGE_UNMERGEABLE;
- }
- }
- return CHANGES_NOT_MERGEABLE
- + unmergeable.stream().map(c -> c.getId().toString()).collect(joining(", "));
- }
- } catch (ResourceConflictException e) {
- return BLOCKED_SUBMIT_TOOLTIP;
- } catch (PermissionBackendException | OrmException | IOException e) {
- log.error("Error checking if change is submittable", e);
- throw new OrmRuntimeException("Could not determine problems for the change", e);
- }
- return null;
- }
-
- @Override
- public UiAction.Description getDescription(RevisionResource resource) {
- Change change = resource.getChange();
- if (!change.getStatus().isOpen()
- || !resource.isCurrent()
- || !resource.permissions().testOrFalse(ChangePermission.SUBMIT)) {
- return null; // submit not visible
- }
-
- ReviewDb db = dbProvider.get();
- ChangeData cd = changeDataFactory.create(db, resource.getNotes());
- try {
- MergeOp.checkSubmitRule(cd, false);
- } catch (ResourceConflictException e) {
- return null; // submit not visible
- } catch (OrmException e) {
- log.error("Error checking if change is submittable", e);
- throw new OrmRuntimeException("Could not determine problems for the change", e);
- }
-
- ChangeSet cs;
- try {
- cs = mergeSuperSet.get().completeChangeSet(db, cd.change(), resource.getUser());
- } catch (OrmException | IOException | PermissionBackendException e) {
- throw new OrmRuntimeException(
- "Could not determine complete set of changes to be submitted", e);
- }
-
- String topic = change.getTopic();
- int topicSize = 0;
- if (!Strings.isNullOrEmpty(topic)) {
- topicSize = getChangesByTopic(topic).size();
- }
- boolean treatWithTopic = submitWholeTopic && !Strings.isNullOrEmpty(topic) && topicSize > 1;
-
- String submitProblems = problemsForSubmittingChangeset(cd, cs, resource.getUser());
-
- Boolean enabled;
- try {
- // Recheck mergeability rather than using value stored in the index,
- // which may be stale.
- // TODO(dborowitz): This is ugly; consider providing a way to not read
- // stored fields from the index in the first place.
- // cd.setMergeable(null);
- // That was done in unmergeableChanges which was called by
- // problemsForSubmittingChangeset, so now it is safe to read from
- // the cache, as it yields the same result.
- enabled = cd.isMergeable();
- } catch (OrmException e) {
- throw new OrmRuntimeException("Could not determine mergeability", e);
- }
-
- if (submitProblems != null) {
- return new UiAction.Description()
- .setLabel(treatWithTopic ? submitTopicLabel : (cs.size() > 1) ? labelWithParents : label)
- .setTitle(submitProblems)
- .setVisible(true)
- .setEnabled(false);
- }
-
- if (treatWithTopic) {
- Map<String, String> params =
- ImmutableMap.of(
- "topicSize", String.valueOf(topicSize),
- "submitSize", String.valueOf(cs.size()));
- return new UiAction.Description()
- .setLabel(submitTopicLabel)
- .setTitle(Strings.emptyToNull(submitTopicTooltip.replace(params)))
- .setVisible(true)
- .setEnabled(Boolean.TRUE.equals(enabled));
- }
- RevId revId = resource.getPatchSet().getRevision();
- Map<String, String> params =
- ImmutableMap.of(
- "patchSet", String.valueOf(resource.getPatchSet().getPatchSetId()),
- "branch", change.getDest().getShortName(),
- "commit", ObjectId.fromString(revId.get()).abbreviate(7).name(),
- "submitSize", String.valueOf(cs.size()));
- ParameterizedString tp = cs.size() > 1 ? titlePatternWithAncestors : titlePattern;
- return new UiAction.Description()
- .setLabel(cs.size() > 1 ? labelWithParents : label)
- .setTitle(Strings.emptyToNull(tp.replace(params)))
- .setVisible(true)
- .setEnabled(Boolean.TRUE.equals(enabled));
- }
-
- /**
- * If the merge was attempted and it failed the system usually writes a comment as a ChangeMessage
- * and sets status to NEW. Find the relevant message and return it.
- */
- public ChangeMessage getConflictMessage(RevisionResource rsrc) throws OrmException {
- return FluentIterable.from(
- cmUtil.byPatchSet(dbProvider.get(), rsrc.getNotes(), rsrc.getPatchSet().getId()))
- .filter(cm -> cm.getAuthor() == null)
- .last()
- .orNull();
- }
-
- public Collection<ChangeData> unmergeableChanges(ChangeSet cs) throws OrmException, IOException {
- Set<ChangeData> mergeabilityMap = new HashSet<>();
- for (ChangeData change : cs.changes()) {
- mergeabilityMap.add(change);
- }
-
- ListMultimap<Branch.NameKey, ChangeData> cbb = cs.changesByBranch();
- for (Branch.NameKey branch : cbb.keySet()) {
- Collection<ChangeData> targetBranch = cbb.get(branch);
- HashMap<Change.Id, RevCommit> commits = findCommits(targetBranch, branch.getParentKey());
-
- Set<ObjectId> allParents = Sets.newHashSetWithExpectedSize(cs.size());
- for (RevCommit commit : commits.values()) {
- for (RevCommit parent : commit.getParents()) {
- allParents.add(parent.getId());
- }
- }
-
- for (ChangeData change : targetBranch) {
- RevCommit commit = commits.get(change.getId());
- boolean isMergeCommit = commit.getParentCount() > 1;
- boolean isLastInChain = !allParents.contains(commit.getId());
-
- // Recheck mergeability rather than using value stored in the index,
- // which may be stale.
- // TODO(dborowitz): This is ugly; consider providing a way to not read
- // stored fields from the index in the first place.
- change.setMergeable(null);
- Boolean mergeable = change.isMergeable();
- if (mergeable == null) {
- // Skip whole check, cannot determine if mergeable
- return null;
- }
- if (mergeable) {
- mergeabilityMap.remove(change);
- }
-
- if (isLastInChain && isMergeCommit && mergeable) {
- for (ChangeData c : targetBranch) {
- mergeabilityMap.remove(c);
- }
- break;
- }
- }
- }
- return mergeabilityMap;
- }
-
- private HashMap<Change.Id, RevCommit> findCommits(
- Collection<ChangeData> changes, Project.NameKey project) throws IOException, OrmException {
- HashMap<Change.Id, RevCommit> commits = new HashMap<>();
- try (Repository repo = repoManager.openRepository(project);
- RevWalk walk = new RevWalk(repo)) {
- for (ChangeData change : changes) {
- RevCommit commit =
- walk.parseCommit(
- ObjectId.fromString(
- psUtil.current(dbProvider.get(), change.notes()).getRevision().get()));
- commits.put(change.getId(), commit);
- }
- }
- return commits;
- }
-
- private IdentifiedUser onBehalfOf(RevisionResource rsrc, SubmitInput in)
- throws AuthException, UnprocessableEntityException, OrmException, PermissionBackendException,
- IOException, ConfigInvalidException {
- PermissionBackend.ForChange perm = rsrc.permissions().database(dbProvider);
- perm.check(ChangePermission.SUBMIT);
- perm.check(ChangePermission.SUBMIT_AS);
-
- CurrentUser caller = rsrc.getUser();
- IdentifiedUser submitter = accounts.parseOnBehalfOf(caller, in.onBehalfOf);
- try {
- perm.user(submitter).check(ChangePermission.READ);
- } catch (AuthException e) {
- throw new UnprocessableEntityException(
- String.format("on_behalf_of account %s cannot see change", submitter.getAccountId()));
- }
- return submitter;
- }
-
- public static boolean wholeTopicEnabled(Config config) {
- return config.getBoolean("change", null, "submitWholeTopic", false);
- }
-
- private List<ChangeData> getChangesByTopic(String topic) {
- try {
- return queryProvider.get().byTopicOpen(topic);
- } catch (OrmException e) {
- throw new OrmRuntimeException(e);
- }
- }
-
- public static class CurrentRevision implements RestModifyView<ChangeResource, SubmitInput> {
- private final Provider<ReviewDb> dbProvider;
- private final Submit submit;
- private final ChangeJson.Factory json;
- private final PatchSetUtil psUtil;
-
- @Inject
- CurrentRevision(
- Provider<ReviewDb> dbProvider,
- Submit submit,
- ChangeJson.Factory json,
- PatchSetUtil psUtil) {
- this.dbProvider = dbProvider;
- this.submit = submit;
- this.json = json;
- this.psUtil = psUtil;
- }
-
- @Override
- public ChangeInfo apply(ChangeResource rsrc, SubmitInput input)
- throws RestApiException, RepositoryNotFoundException, IOException, OrmException,
- PermissionBackendException, UpdateException, ConfigInvalidException {
- PatchSet ps = psUtil.current(dbProvider.get(), rsrc.getNotes());
- if (ps == null) {
- throw new ResourceConflictException("current revision is missing");
- }
-
- Output out = submit.apply(new RevisionResource(rsrc, ps), input);
- return json.noOptions().format(out.change);
- }
- }
-}