// 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.project; 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.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.RestReadView; import com.google.gerrit.reviewdb.server.ReviewDb; 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.inject.Inject; import com.google.inject.Provider; import java.io.IOException; 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; private final Provider<ReviewDb> db; @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; @Inject CheckMergeability( GitRepositoryManager gitManager, @GerritServerConfig Config cfg, Provider<ReviewDb> db) { this.gitManager = gitManager; this.strategy = MergeUtil.getMergeStrategy(cfg).getName(); this.submitType = cfg.getEnum("project", null, "submitType", SubmitType.MERGE_IF_NECESSARY); this.db = db; } @Override public MergeableInfo apply(BranchResource resource) throws IOException, BadRequestException, ResourceNotFoundException { 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 = MergeUtil.resolveCommit(git, rw, source); if (!resource.getControl().canReadCommit(db.get(), git, sourceCommit)) { throw new BadRequestException("do not have read permission for: " + source); } if (rw.isMergedInto(sourceCommit, targetCommit)) { result.mergeable = true; result.commitMerged = true; result.contentMerged = true; return 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 (IllegalArgumentException e) { throw new BadRequestException(e.getMessage()); } return result; } }