| // Copyright (C) 2020 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.edit; | |
| import com.google.gerrit.entities.Change; | |
| import com.google.gerrit.entities.PatchSet; | |
| import com.google.gerrit.server.project.InvalidChangeOperationException; | |
| import java.io.IOException; | |
| import org.eclipse.jgit.lib.Repository; | |
| import org.eclipse.jgit.revwalk.RevCommit; | |
| import org.eclipse.jgit.revwalk.RevWalk; | |
| /** | |
| * Target of the modification of a commit. | |
| * | |
| * <p>This is currently used in the context of change edits which involves both direct actions on | |
| * change edits (e.g. creating a change edit; modifying a file of a change edit) as well as indirect | |
| * creation/modification of them (e.g. via applying a suggested fix of a robot comment.) | |
| * | |
| * <p>Depending on the situation and exact action, either an existing {@link ChangeEdit} (-> {@link | |
| * EditCommit} or a specific patchset commit (-> {@link PatchsetCommit}) is the target of a | |
| * modification. | |
| */ | |
| public interface ModificationTarget { | |
| void ensureNewEditMayBeBasedOnTarget(Change change) throws InvalidChangeOperationException; | |
| void ensureTargetMayBeModifiedDespiteExistingEdit(ChangeEdit changeEdit) | |
| throws InvalidChangeOperationException; | |
| /** Commit to modify. */ | |
| RevCommit getCommit(Repository repository) throws IOException; | |
| /** | |
| * Patchset within whose context the modification happens. This also applies to change edits as | |
| * each change edit is based on a specific patchset. | |
| */ | |
| PatchSet getBasePatchset(); | |
| /** A specific patchset commit is the target of the modification. */ | |
| class PatchsetCommit implements ModificationTarget { | |
| private final PatchSet patchSet; | |
| PatchsetCommit(PatchSet patchSet) { | |
| this.patchSet = patchSet; | |
| } | |
| @Override | |
| public void ensureTargetMayBeModifiedDespiteExistingEdit(ChangeEdit changeEdit) | |
| throws InvalidChangeOperationException { | |
| if (!isBasedOn(changeEdit, patchSet)) { | |
| throw new InvalidChangeOperationException( | |
| String.format( | |
| "Only the patch set %s on which the existing change edit is based may be modified " | |
| + "(specified patch set: %s)", | |
| changeEdit.getBasePatchSet().id(), patchSet.id())); | |
| } | |
| } | |
| private static boolean isBasedOn(ChangeEdit changeEdit, PatchSet patchSet) { | |
| PatchSet editBasePatchSet = changeEdit.getBasePatchSet(); | |
| return editBasePatchSet.id().equals(patchSet.id()); | |
| } | |
| @Override | |
| public void ensureNewEditMayBeBasedOnTarget(Change change) | |
| throws InvalidChangeOperationException { | |
| PatchSet.Id patchSetId = patchSet.id(); | |
| PatchSet.Id currentPatchSetId = change.currentPatchSetId(); | |
| if (!patchSetId.equals(currentPatchSetId)) { | |
| throw new InvalidChangeOperationException( | |
| String.format( | |
| "A change edit may only be created for the current patch set %s (and not for %s)", | |
| currentPatchSetId, patchSetId)); | |
| } | |
| } | |
| @Override | |
| public RevCommit getCommit(Repository repository) throws IOException { | |
| try (RevWalk revWalk = new RevWalk(repository)) { | |
| return revWalk.parseCommit(patchSet.commitId()); | |
| } | |
| } | |
| @Override | |
| public PatchSet getBasePatchset() { | |
| return patchSet; | |
| } | |
| } | |
| /** An existing {@link ChangeEdit} commit is the target of the modification. */ | |
| class EditCommit implements ModificationTarget { | |
| private final ChangeEdit changeEdit; | |
| EditCommit(ChangeEdit changeEdit) { | |
| this.changeEdit = changeEdit; | |
| } | |
| @Override | |
| public void ensureNewEditMayBeBasedOnTarget(Change change) { | |
| // The current code will never create a new edit if one already exists. It would be a | |
| // programmer error if this changes in the future (without adjusting the storage of change | |
| // edits). | |
| throw new IllegalStateException( | |
| String.format( | |
| "Change %d already has a change edit for the calling user. A new change edit can't" | |
| + " be created.", | |
| changeEdit.getChange().getChangeId())); | |
| } | |
| @Override | |
| public void ensureTargetMayBeModifiedDespiteExistingEdit(ChangeEdit changeEdit) { | |
| // The target is the change edit and hence can be modified. | |
| } | |
| @Override | |
| public RevCommit getCommit(Repository repository) throws IOException { | |
| return changeEdit.getEditCommit(); | |
| } | |
| @Override | |
| public PatchSet getBasePatchset() { | |
| return changeEdit.getBasePatchSet(); | |
| } | |
| } | |
| } |