| // Copyright (C) 2016 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.restapi.project; | |
| import com.google.gerrit.exceptions.InvalidMergeStrategyException; | |
| import com.google.gerrit.extensions.client.SubmitType; | |
| import com.google.gerrit.extensions.common.MergeableInfo; | |
| import com.google.gerrit.extensions.restapi.BadRequestException; | |
| import com.google.gerrit.extensions.restapi.ResourceConflictException; | |
| import com.google.gerrit.extensions.restapi.ResourceNotFoundException; | |
| import com.google.gerrit.extensions.restapi.Response; | |
| import com.google.gerrit.extensions.restapi.RestReadView; | |
| import com.google.gerrit.server.config.GerritServerConfig; | |
| import com.google.gerrit.server.git.GitRepositoryManager; | |
| import com.google.gerrit.server.git.InMemoryInserter; | |
| import com.google.gerrit.server.git.MergeUtil; | |
| import com.google.gerrit.server.project.BranchResource; | |
| import com.google.inject.Inject; | |
| import java.io.IOException; | |
| import org.eclipse.jgit.errors.NoMergeBaseException; | |
| import org.eclipse.jgit.lib.Config; | |
| import org.eclipse.jgit.lib.ObjectInserter; | |
| import org.eclipse.jgit.lib.Ref; | |
| import org.eclipse.jgit.lib.Repository; | |
| import org.eclipse.jgit.merge.Merger; | |
| import org.eclipse.jgit.merge.ResolveMerger; | |
| import org.eclipse.jgit.revwalk.RevCommit; | |
| import org.eclipse.jgit.revwalk.RevWalk; | |
| import org.kohsuke.args4j.Option; | |
| /** Check the mergeability at current branch for a git object references expression. */ | |
| public class CheckMergeability implements RestReadView<BranchResource> { | |
| private String source; | |
| private String strategy; | |
| private SubmitType submitType; | |
| @Option( | |
| name = "--source", | |
| metaVar = "COMMIT", | |
| usage = | |
| "the source reference to merge, which could be any git object " | |
| + "references expression, refer to " | |
| + "org.eclipse.jgit.lib.Repository#resolve(String)", | |
| required = true) | |
| public void setSource(String source) { | |
| this.source = source; | |
| } | |
| @Option( | |
| name = "--strategy", | |
| metaVar = "STRATEGY", | |
| usage = "name of the merge strategy, refer to org.eclipse.jgit.merge.MergeStrategy") | |
| public void setStrategy(String strategy) { | |
| this.strategy = strategy; | |
| } | |
| private final GitRepositoryManager gitManager; | |
| private final CommitsCollection commits; | |
| @Inject | |
| CheckMergeability( | |
| GitRepositoryManager gitManager, CommitsCollection commits, @GerritServerConfig Config cfg) { | |
| this.gitManager = gitManager; | |
| this.commits = commits; | |
| this.strategy = MergeUtil.getMergeStrategy(cfg).getName(); | |
| this.submitType = cfg.getEnum("project", null, "submitType", SubmitType.MERGE_IF_NECESSARY); | |
| } | |
| @Override | |
| public Response<MergeableInfo> apply(BranchResource resource) | |
| throws IOException, BadRequestException, ResourceNotFoundException, | |
| ResourceConflictException { | |
| if (!(submitType.equals(SubmitType.MERGE_ALWAYS) | |
| || submitType.equals(SubmitType.MERGE_IF_NECESSARY))) { | |
| throw new BadRequestException("Submit type: " + submitType + " is not supported"); | |
| } | |
| MergeableInfo result = new MergeableInfo(); | |
| result.submitType = submitType; | |
| result.strategy = strategy; | |
| try (Repository git = gitManager.openRepository(resource.getNameKey()); | |
| RevWalk rw = new RevWalk(git); | |
| ObjectInserter inserter = new InMemoryInserter(git)) { | |
| Merger m = MergeUtil.newMerger(inserter, git.getConfig(), strategy); | |
| Ref destRef = git.getRefDatabase().exactRef(resource.getRef()); | |
| if (destRef == null) { | |
| throw new ResourceNotFoundException(resource.getRef()); | |
| } | |
| RevCommit targetCommit = rw.parseCommit(destRef.getObjectId()); | |
| RevCommit sourceCommit = null; | |
| try { | |
| sourceCommit = MergeUtil.resolveCommit(git, rw, source); | |
| if (!commits.canRead(resource.getProjectState(), git, sourceCommit)) { | |
| throw new BadRequestException("do not have read permission for: " + source); | |
| } | |
| } catch (BadRequestException e) { | |
| // Throw a unified exception for permission denied and unresolvable commits. | |
| throw new BadRequestException( | |
| "Error resolving: '" | |
| + source | |
| + "'. Do not have read permission, or failed to resolve to a commit.", | |
| e); | |
| } | |
| if (rw.isMergedInto(sourceCommit, targetCommit)) { | |
| result.mergeable = true; | |
| result.commitMerged = true; | |
| result.contentMerged = true; | |
| return Response.ok(result); | |
| } | |
| if (m.merge(false, targetCommit, sourceCommit)) { | |
| result.mergeable = true; | |
| result.commitMerged = false; | |
| result.contentMerged = m.getResultTreeId().equals(targetCommit.getTree()); | |
| } else { | |
| result.mergeable = false; | |
| if (m instanceof ResolveMerger) { | |
| result.conflicts = ((ResolveMerger) m).getUnmergedPaths(); | |
| } | |
| } | |
| } catch (InvalidMergeStrategyException e) { | |
| throw new BadRequestException(e.getMessage()); | |
| } catch (NoMergeBaseException e) { | |
| // TODO(ekempin) Rather return MergeableInfo with mergeable = false. But then we need a new | |
| // field in MergeableInfo to carry the message to the client and the frontend needs to be | |
| // adapted to show the message to the user. | |
| throw new ResourceConflictException( | |
| String.format("Change cannot be merged: %s", e.getMessage()), e); | |
| } | |
| return Response.ok(result); | |
| } | |
| } |