LCOV - code coverage report
Current view: top level - server/restapi/project - CheckMergeability.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 44 50 88.0 %
Date: 2022-11-19 15:00:39 Functions: 4 4 100.0 %

          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             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750