Java tutorial
// Copyright (C) 2015 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 com.google.common.base.Preconditions.checkState; import com.google.gerrit.common.Nullable; import com.google.gerrit.extensions.restapi.MergeConflictException; import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.RevId; import com.google.gerrit.server.ChangeUtil; import com.google.gerrit.server.change.RebaseUtil.Base; import com.google.gerrit.server.git.BatchUpdate; import com.google.gerrit.server.git.BatchUpdate.ChangeContext; import com.google.gerrit.server.git.BatchUpdate.Context; import com.google.gerrit.server.git.BatchUpdate.RepoContext; import com.google.gerrit.server.git.MergeUtil; import com.google.gerrit.server.git.validators.CommitValidators; import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.InvalidChangeOperationException; import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.project.ProjectState; import com.google.gwtorm.server.OrmException; import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.merge.ThreeWayMerger; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import java.io.IOException; public class RebaseChangeOp extends BatchUpdate.Op { public interface Factory { RebaseChangeOp create(ChangeControl ctl, PatchSet originalPatchSet, @Nullable String baseCommitish); } private final PatchSetInserter.Factory patchSetInserterFactory; private final MergeUtil.Factory mergeUtilFactory; private final RebaseUtil rebaseUtil; private final ChangeControl ctl; private final PatchSet originalPatchSet; private String baseCommitish; private PersonIdent committerIdent; private boolean runHooks = true; private CommitValidators.Policy validate; private boolean forceContentMerge; private boolean copyApprovals = true; private RevCommit rebasedCommit; private PatchSet.Id rebasedPatchSetId; private PatchSetInserter patchSetInserter; private PatchSet rebasedPatchSet; @AssistedInject RebaseChangeOp(PatchSetInserter.Factory patchSetInserterFactory, MergeUtil.Factory mergeUtilFactory, RebaseUtil rebaseUtil, @Assisted ChangeControl ctl, @Assisted PatchSet originalPatchSet, @Assisted @Nullable String baseCommitish) { this.patchSetInserterFactory = patchSetInserterFactory; this.mergeUtilFactory = mergeUtilFactory; this.rebaseUtil = rebaseUtil; this.ctl = ctl; this.originalPatchSet = originalPatchSet; this.baseCommitish = baseCommitish; } public RebaseChangeOp setCommitterIdent(PersonIdent committerIdent) { this.committerIdent = committerIdent; return this; } public RebaseChangeOp setValidatePolicy(CommitValidators.Policy validate) { this.validate = validate; return this; } public RebaseChangeOp setRunHooks(boolean runHooks) { this.runHooks = runHooks; return this; } public RebaseChangeOp setForceContentMerge(boolean forceContentMerge) { this.forceContentMerge = forceContentMerge; return this; } public RebaseChangeOp setCopyApprovals(boolean copyApprovals) { this.copyApprovals = copyApprovals; return this; } @Override public void updateRepo(RepoContext ctx) throws MergeConflictException, InvalidChangeOperationException, RestApiException, IOException, OrmException, NoSuchChangeException { // Ok that originalPatchSet was not read in a transaction, since we just // need its revision. RevId oldRev = originalPatchSet.getRevision(); RevWalk rw = ctx.getRevWalk(); RevCommit original = rw.parseCommit(ObjectId.fromString(oldRev.get())); rw.parseBody(original); RevCommit baseCommit; if (baseCommitish != null) { baseCommit = rw.parseCommit(ctx.getRepository().resolve(baseCommitish)); } else { baseCommit = rw.parseCommit(rebaseUtil.findBaseRevision(originalPatchSet, ctl.getChange().getDest(), ctx.getRepository(), ctx.getRevWalk())); } rebasedCommit = rebaseCommit(ctx, original, baseCommit); RevId baseRevId = new RevId( (baseCommitish != null) ? baseCommitish : ObjectId.toString(baseCommit.getId())); Base base = rebaseUtil.parseBase(new RevisionResource(new ChangeResource(ctl), originalPatchSet), baseRevId.get()); rebasedPatchSetId = ChangeUtil.nextPatchSetId(ctx.getRepository(), ctl.getChange().currentPatchSetId()); patchSetInserter = patchSetInserterFactory.create(ctl.getRefControl(), rebasedPatchSetId, rebasedCommit) .setDraft(originalPatchSet.isDraft()).setSendMail(false).setRunHooks(runHooks) .setCopyApprovals(copyApprovals).setMessage("Patch Set " + rebasedPatchSetId.get() + ": Patch Set " + originalPatchSet.getId().get() + " was rebased"); if (base != null) { patchSetInserter.setGroups(base.patchSet().getGroups()); } if (validate != null) { patchSetInserter.setValidatePolicy(validate); } patchSetInserter.updateRepo(ctx); } @Override public boolean updateChange(ChangeContext ctx) throws ResourceConflictException, OrmException, IOException { boolean ret = patchSetInserter.updateChange(ctx); rebasedPatchSet = patchSetInserter.getPatchSet(); return ret; } @Override public void postUpdate(Context ctx) throws OrmException { patchSetInserter.postUpdate(ctx); } public RevCommit getRebasedCommit() { checkState(rebasedCommit != null, "getRebasedCommit() only valid after updateRepo"); return rebasedCommit; } public PatchSet.Id getPatchSetId() { checkState(rebasedPatchSetId != null, "getPatchSetId() only valid after updateRepo"); return rebasedPatchSetId; } public PatchSet getPatchSet() { checkState(rebasedPatchSet != null, "getPatchSet() only valid after executing update"); return rebasedPatchSet; } private MergeUtil newMergeUtil() { ProjectState project = ctl.getProjectControl().getProjectState(); return forceContentMerge ? mergeUtilFactory.create(project, true) : mergeUtilFactory.create(project); } /** * Rebase a commit. * * @param ctx repo context. * @param original the commit to rebase. * @param base base to rebase against. * @return the rebased commit. * @throws MergeConflictException the rebase failed due to a merge conflict. * @throws IOException the merge failed for another reason. */ private RevCommit rebaseCommit(RepoContext ctx, RevCommit original, ObjectId base) throws ResourceConflictException, MergeConflictException, IOException { RevCommit parentCommit = original.getParent(0); if (base.equals(parentCommit)) { throw new ResourceConflictException("Change is already up to date."); } ThreeWayMerger merger = newMergeUtil().newThreeWayMerger(ctx.getRepository(), ctx.getInserter()); merger.setBase(parentCommit); merger.merge(original, base); if (merger.getResultTreeId() == null) { throw new MergeConflictException("The change could not be rebased due to a conflict during merge."); } CommitBuilder cb = new CommitBuilder(); cb.setTreeId(merger.getResultTreeId()); cb.setParentId(base); cb.setAuthor(original.getAuthorIdent()); cb.setMessage(original.getFullMessage()); if (committerIdent != null) { cb.setCommitter(committerIdent); } else { cb.setCommitter(ctx.getUser().asIdentifiedUser().newCommitterIdent(ctx.getWhen(), ctx.getTimeZone())); } ObjectId objectId = ctx.getInserter().insert(cb); ctx.getInserter().flush(); return ctx.getRevWalk().parseCommit(objectId); } }