LCOV - code coverage report
Current view: top level - server/restapi/change - Mergeable.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 54 58 93.1 %
Date: 2022-11-19 15:00:39 Functions: 6 6 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2013 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.change;
      16             : 
      17             : import static com.google.gerrit.server.project.ProjectCache.illegalState;
      18             : 
      19             : import com.google.gerrit.entities.BranchOrderSection;
      20             : import com.google.gerrit.entities.Change;
      21             : import com.google.gerrit.entities.PatchSet;
      22             : import com.google.gerrit.entities.SubmitTypeRecord;
      23             : import com.google.gerrit.extensions.client.SubmitType;
      24             : import com.google.gerrit.extensions.common.MergeableInfo;
      25             : import com.google.gerrit.extensions.restapi.AuthException;
      26             : import com.google.gerrit.extensions.restapi.BadRequestException;
      27             : import com.google.gerrit.extensions.restapi.ResourceConflictException;
      28             : import com.google.gerrit.extensions.restapi.Response;
      29             : import com.google.gerrit.extensions.restapi.RestReadView;
      30             : import com.google.gerrit.server.ChangeUtil;
      31             : import com.google.gerrit.server.change.MergeabilityCache;
      32             : import com.google.gerrit.server.change.RevisionResource;
      33             : import com.google.gerrit.server.git.GitRepositoryManager;
      34             : import com.google.gerrit.server.git.MergeUtilFactory;
      35             : import com.google.gerrit.server.index.change.ChangeIndexer;
      36             : import com.google.gerrit.server.project.ProjectCache;
      37             : import com.google.gerrit.server.project.ProjectState;
      38             : import com.google.gerrit.server.project.SubmitRuleEvaluator;
      39             : import com.google.gerrit.server.project.SubmitRuleOptions;
      40             : import com.google.gerrit.server.query.change.ChangeData;
      41             : import com.google.inject.Inject;
      42             : import java.io.IOException;
      43             : import java.util.ArrayList;
      44             : import java.util.List;
      45             : import java.util.Map;
      46             : import java.util.Objects;
      47             : import java.util.Optional;
      48             : import java.util.concurrent.Future;
      49             : import org.eclipse.jgit.lib.Constants;
      50             : import org.eclipse.jgit.lib.ObjectId;
      51             : import org.eclipse.jgit.lib.Ref;
      52             : import org.eclipse.jgit.lib.Repository;
      53             : import org.kohsuke.args4j.Option;
      54             : 
      55             : public class Mergeable implements RestReadView<RevisionResource> {
      56             :   @Option(
      57             :       name = "--other-branches",
      58             :       aliases = {"-o"},
      59             :       usage = "test mergeability for other branches too")
      60             :   private boolean otherBranches;
      61             : 
      62             :   private final GitRepositoryManager gitManager;
      63             :   private final ProjectCache projectCache;
      64             :   private final MergeUtilFactory mergeUtilFactory;
      65             :   private final ChangeData.Factory changeDataFactory;
      66             :   private final ChangeIndexer indexer;
      67             :   private final MergeabilityCache cache;
      68             :   private final SubmitRuleEvaluator submitRuleEvaluator;
      69             : 
      70             :   @Inject
      71             :   Mergeable(
      72             :       GitRepositoryManager gitManager,
      73             :       ProjectCache projectCache,
      74             :       MergeUtilFactory mergeUtilFactory,
      75             :       ChangeData.Factory changeDataFactory,
      76             :       ChangeIndexer indexer,
      77             :       MergeabilityCache cache,
      78          86 :       SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory) {
      79          86 :     this.gitManager = gitManager;
      80          86 :     this.projectCache = projectCache;
      81          86 :     this.mergeUtilFactory = mergeUtilFactory;
      82          86 :     this.changeDataFactory = changeDataFactory;
      83          86 :     this.indexer = indexer;
      84          86 :     this.cache = cache;
      85          86 :     submitRuleEvaluator = submitRuleEvaluatorFactory.create(SubmitRuleOptions.defaults());
      86          86 :   }
      87             : 
      88             :   public void setOtherBranches(boolean otherBranches) {
      89           1 :     this.otherBranches = otherBranches;
      90           1 :   }
      91             : 
      92             :   @Override
      93             :   public Response<MergeableInfo> apply(RevisionResource resource)
      94             :       throws AuthException, ResourceConflictException, BadRequestException, IOException {
      95           2 :     Change change = resource.getChange();
      96           2 :     PatchSet ps = resource.getPatchSet();
      97           2 :     MergeableInfo result = new MergeableInfo();
      98             : 
      99           2 :     if (!change.isNew()) {
     100           0 :       throw new ResourceConflictException("change is " + ChangeUtil.status(change));
     101           2 :     } else if (!ps.id().equals(change.currentPatchSetId())) {
     102             :       // Only the current revision is mergeable. Others always fail.
     103           0 :       return Response.ok(result);
     104             :     }
     105             : 
     106           2 :     ChangeData cd = changeDataFactory.create(resource.getNotes());
     107           2 :     result.submitType = getSubmitType(cd);
     108             : 
     109           2 :     try (Repository git = gitManager.openRepository(change.getProject())) {
     110           2 :       ObjectId commit = ps.commitId();
     111           2 :       Ref ref = git.getRefDatabase().exactRef(change.getDest().branch());
     112           2 :       ProjectState projectState =
     113           2 :           projectCache.get(change.getProject()).orElseThrow(illegalState(change.getProject()));
     114           2 :       String strategy = mergeUtilFactory.create(projectState).mergeStrategyName();
     115           2 :       result.strategy = strategy;
     116           2 :       result.mergeable = isMergable(git, change, commit, ref, result.submitType, strategy);
     117             : 
     118           2 :       if (otherBranches) {
     119           1 :         result.mergeableInto = new ArrayList<>();
     120           1 :         Optional<BranchOrderSection> branchOrder = projectState.getBranchOrderSection();
     121           1 :         if (branchOrder.isPresent()) {
     122           1 :           int prefixLen = Constants.R_HEADS.length();
     123           1 :           List<String> names = branchOrder.get().getMoreStable(ref.getName());
     124           1 :           Map<String, Ref> refs =
     125           1 :               git.getRefDatabase().exactRef(names.toArray(new String[names.size()]));
     126           1 :           for (String n : names) {
     127           1 :             Ref other = refs.get(n);
     128           1 :             if (other == null) {
     129           1 :               continue;
     130             :             }
     131           1 :             if (cache.get(commit, other, SubmitType.CHERRY_PICK, strategy, change.getDest(), git)) {
     132           1 :               result.mergeableInto.add(other.getName().substring(prefixLen));
     133             :             }
     134           1 :           }
     135             :         }
     136             :       }
     137             :     }
     138           2 :     return Response.ok(result);
     139             :   }
     140             : 
     141             :   private SubmitType getSubmitType(ChangeData cd) throws ResourceConflictException {
     142           2 :     SubmitTypeRecord rec = submitRuleEvaluator.getSubmitType(cd);
     143           2 :     if (rec.status != SubmitTypeRecord.Status.OK) {
     144           0 :       throw new ResourceConflictException("submit type rule error: " + rec.errorMessage);
     145             :     }
     146           2 :     return rec.type;
     147             :   }
     148             : 
     149             :   private boolean isMergable(
     150             :       Repository git,
     151             :       Change change,
     152             :       ObjectId commit,
     153             :       Ref ref,
     154             :       SubmitType submitType,
     155             :       String strategy) {
     156           2 :     if (commit == null) {
     157           0 :       return false;
     158             :     }
     159             : 
     160           2 :     Boolean old = cache.getIfPresent(commit, ref, submitType, strategy);
     161           2 :     if (old != null) {
     162           1 :       return old;
     163             :     }
     164           2 :     return refresh(change, commit, ref, submitType, strategy, git, old);
     165             :   }
     166             : 
     167             :   private boolean refresh(
     168             :       final Change change,
     169             :       ObjectId commit,
     170             :       final Ref ref,
     171             :       SubmitType type,
     172             :       String strategy,
     173             :       Repository git,
     174             :       Boolean old) {
     175           2 :     boolean mergeable = cache.get(commit, ref, type, strategy, change.getDest(), git);
     176             :     // TODO(dborowitz): Include something else in the change ETag that it's possible to bump here,
     177             :     // such as cache or secondary index update time.
     178           2 :     if (!Objects.equals(mergeable, old)) {
     179             :       @SuppressWarnings("unused")
     180           2 :       Future<?> possiblyIgnoredError = indexer.indexAsync(change.getProject(), change.getId());
     181             :     }
     182           2 :     return mergeable;
     183             :   }
     184             : }

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