Line data Source code
1 : // Copyright (C) 2016 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.restapi.project; 16 : 17 : import com.google.gerrit.exceptions.InvalidMergeStrategyException; 18 : import com.google.gerrit.extensions.client.SubmitType; 19 : import com.google.gerrit.extensions.common.MergeableInfo; 20 : import com.google.gerrit.extensions.restapi.BadRequestException; 21 : import com.google.gerrit.extensions.restapi.ResourceConflictException; 22 : import com.google.gerrit.extensions.restapi.ResourceNotFoundException; 23 : import com.google.gerrit.extensions.restapi.Response; 24 : import com.google.gerrit.extensions.restapi.RestReadView; 25 : import com.google.gerrit.server.config.GerritServerConfig; 26 : import com.google.gerrit.server.git.GitRepositoryManager; 27 : import com.google.gerrit.server.git.InMemoryInserter; 28 : import com.google.gerrit.server.git.MergeUtil; 29 : import com.google.gerrit.server.project.BranchResource; 30 : import com.google.inject.Inject; 31 : import java.io.IOException; 32 : import org.eclipse.jgit.errors.NoMergeBaseException; 33 : import org.eclipse.jgit.lib.Config; 34 : import org.eclipse.jgit.lib.ObjectInserter; 35 : import org.eclipse.jgit.lib.Ref; 36 : import org.eclipse.jgit.lib.Repository; 37 : import org.eclipse.jgit.merge.Merger; 38 : import org.eclipse.jgit.merge.ResolveMerger; 39 : import org.eclipse.jgit.revwalk.RevCommit; 40 : import org.eclipse.jgit.revwalk.RevWalk; 41 : import org.kohsuke.args4j.Option; 42 : 43 : /** Check the mergeability at current branch for a git object references expression. */ 44 : public class CheckMergeability implements RestReadView<BranchResource> { 45 : private String source; 46 : private String strategy; 47 : private SubmitType submitType; 48 : 49 : @Option( 50 : name = "--source", 51 : metaVar = "COMMIT", 52 : usage = 53 : "the source reference to merge, which could be any git object " 54 : + "references expression, refer to " 55 : + "org.eclipse.jgit.lib.Repository#resolve(String)", 56 : required = true) 57 : public void setSource(String source) { 58 1 : this.source = source; 59 1 : } 60 : 61 : @Option( 62 : name = "--strategy", 63 : metaVar = "STRATEGY", 64 : usage = "name of the merge strategy, refer to org.eclipse.jgit.merge.MergeStrategy") 65 : public void setStrategy(String strategy) { 66 1 : this.strategy = strategy; 67 1 : } 68 : 69 : private final GitRepositoryManager gitManager; 70 : private final CommitsCollection commits; 71 : 72 : @Inject 73 : CheckMergeability( 74 16 : GitRepositoryManager gitManager, CommitsCollection commits, @GerritServerConfig Config cfg) { 75 16 : this.gitManager = gitManager; 76 16 : this.commits = commits; 77 16 : this.strategy = MergeUtil.getMergeStrategy(cfg).getName(); 78 16 : this.submitType = cfg.getEnum("project", null, "submitType", SubmitType.MERGE_IF_NECESSARY); 79 16 : } 80 : 81 : @Override 82 : public Response<MergeableInfo> apply(BranchResource resource) 83 : throws IOException, BadRequestException, ResourceNotFoundException, 84 : ResourceConflictException { 85 1 : if (!(submitType.equals(SubmitType.MERGE_ALWAYS) 86 1 : || submitType.equals(SubmitType.MERGE_IF_NECESSARY))) { 87 0 : throw new BadRequestException("Submit type: " + submitType + " is not supported"); 88 : } 89 : 90 1 : MergeableInfo result = new MergeableInfo(); 91 1 : result.submitType = submitType; 92 1 : result.strategy = strategy; 93 1 : try (Repository git = gitManager.openRepository(resource.getNameKey()); 94 1 : RevWalk rw = new RevWalk(git); 95 1 : ObjectInserter inserter = new InMemoryInserter(git)) { 96 1 : Merger m = MergeUtil.newMerger(inserter, git.getConfig(), strategy); 97 : 98 1 : Ref destRef = git.getRefDatabase().exactRef(resource.getRef()); 99 1 : if (destRef == null) { 100 0 : throw new ResourceNotFoundException(resource.getRef()); 101 : } 102 : 103 1 : RevCommit targetCommit = rw.parseCommit(destRef.getObjectId()); 104 : 105 1 : RevCommit sourceCommit = null; 106 : try { 107 1 : sourceCommit = MergeUtil.resolveCommit(git, rw, source); 108 1 : if (!commits.canRead(resource.getProjectState(), git, sourceCommit)) { 109 0 : throw new BadRequestException("do not have read permission for: " + source); 110 : } 111 1 : } catch (BadRequestException e) { 112 : // Throw a unified exception for permission denied and unresolvable commits. 113 1 : throw new BadRequestException( 114 : "Error resolving: '" 115 : + source 116 : + "'. Do not have read permission, or failed to resolve to a commit.", 117 : e); 118 1 : } 119 : 120 1 : if (rw.isMergedInto(sourceCommit, targetCommit)) { 121 1 : result.mergeable = true; 122 1 : result.commitMerged = true; 123 1 : result.contentMerged = true; 124 1 : return Response.ok(result); 125 : } 126 : 127 1 : if (m.merge(false, targetCommit, sourceCommit)) { 128 1 : result.mergeable = true; 129 1 : result.commitMerged = false; 130 1 : result.contentMerged = m.getResultTreeId().equals(targetCommit.getTree()); 131 : } else { 132 1 : result.mergeable = false; 133 1 : if (m instanceof ResolveMerger) { 134 1 : result.conflicts = ((ResolveMerger) m).getUnmergedPaths(); 135 : } 136 : } 137 1 : } catch (InvalidMergeStrategyException e) { 138 1 : throw new BadRequestException(e.getMessage()); 139 0 : } catch (NoMergeBaseException e) { 140 : // TODO(ekempin) Rather return MergeableInfo with mergeable = false. But then we need a new 141 : // field in MergeableInfo to carry the message to the client and the frontend needs to be 142 : // adapted to show the message to the user. 143 0 : throw new ResourceConflictException( 144 0 : String.format("Change cannot be merged: %s", e.getMessage()), e); 145 1 : } 146 1 : return Response.ok(result); 147 : } 148 : }