LCOV - code coverage report
Current view: top level - server/submit - LocalMergeSuperSetComputation.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 107 115 93.0 %
Date: 2022-11-19 15:00:39 Functions: 14 15 93.3 %

          Line data    Source code
       1             : // Copyright (C) 2017 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.submit;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkState;
      18             : 
      19             : import com.google.auto.value.AutoValue;
      20             : import com.google.common.collect.ImmutableList;
      21             : import com.google.common.collect.ImmutableListMultimap;
      22             : import com.google.common.collect.ImmutableSet;
      23             : import com.google.common.collect.Iterables;
      24             : import com.google.common.flogger.FluentLogger;
      25             : import com.google.gerrit.common.UsedAt;
      26             : import com.google.gerrit.entities.BranchNameKey;
      27             : import com.google.gerrit.entities.Project;
      28             : import com.google.gerrit.entities.SubmitTypeRecord;
      29             : import com.google.gerrit.exceptions.StorageException;
      30             : import com.google.gerrit.extensions.client.SubmitType;
      31             : import com.google.gerrit.extensions.registration.DynamicItem;
      32             : import com.google.gerrit.server.CurrentUser;
      33             : import com.google.gerrit.server.config.GerritServerConfig;
      34             : import com.google.gerrit.server.project.NoSuchProjectException;
      35             : import com.google.gerrit.server.query.change.ChangeData;
      36             : import com.google.gerrit.server.query.change.ChangeIsVisibleToPredicate;
      37             : import com.google.gerrit.server.query.change.InternalChangeQuery;
      38             : import com.google.gerrit.server.submit.MergeOpRepoManager.OpenRepo;
      39             : import com.google.inject.AbstractModule;
      40             : import com.google.inject.Inject;
      41             : import com.google.inject.Provider;
      42             : import java.io.IOException;
      43             : import java.util.ArrayList;
      44             : import java.util.Collection;
      45             : import java.util.Collections;
      46             : import java.util.HashMap;
      47             : import java.util.HashSet;
      48             : import java.util.List;
      49             : import java.util.Map;
      50             : import java.util.Optional;
      51             : import java.util.Set;
      52             : import org.eclipse.jgit.lib.Config;
      53             : import org.eclipse.jgit.lib.Ref;
      54             : import org.eclipse.jgit.revwalk.RevCommit;
      55             : import org.eclipse.jgit.revwalk.RevSort;
      56             : 
      57             : /**
      58             :  * Default implementation of MergeSuperSet that does the computation of the merge super set
      59             :  * sequentially on the local Gerrit instance.
      60             :  */
      61             : public class LocalMergeSuperSetComputation implements MergeSuperSetComputation {
      62          53 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      63             : 
      64             :   public static final int MAX_SUBMITTABLE_CHANGES_AT_ONCE_DEFAULT = 1024;
      65             : 
      66         152 :   public static class LocalMergeSuperSetComputationModule extends AbstractModule {
      67             :     @Override
      68             :     protected void configure() {
      69         152 :       DynamicItem.bind(binder(), MergeSuperSetComputation.class)
      70         152 :           .to(LocalMergeSuperSetComputation.class);
      71         152 :     }
      72             :   }
      73             : 
      74             :   @AutoValue
      75          53 :   abstract static class QueryKey {
      76             :     private static QueryKey create(BranchNameKey branch, Iterable<String> hashes) {
      77          53 :       return new AutoValue_LocalMergeSuperSetComputation_QueryKey(
      78          53 :           branch, ImmutableSet.copyOf(hashes));
      79             :     }
      80             : 
      81             :     abstract BranchNameKey branch();
      82             : 
      83             :     abstract ImmutableSet<String> hashes();
      84             :   }
      85             : 
      86             :   private final Provider<InternalChangeQuery> queryProvider;
      87             :   private final Map<QueryKey, ImmutableList<ChangeData>> queryCache;
      88             :   private final Map<BranchNameKey, Optional<RevCommit>> heads;
      89             :   private final ChangeIsVisibleToPredicate.Factory changeIsVisibleToPredicateFactory;
      90             :   private final int maxSubmittableChangesAtOnce;
      91             : 
      92             :   @Inject
      93             :   LocalMergeSuperSetComputation(
      94             :       Provider<InternalChangeQuery> queryProvider,
      95             :       ChangeIsVisibleToPredicate.Factory changeIsVisibleToPredicateFactory,
      96          53 :       @GerritServerConfig Config gerritConfig) {
      97          53 :     this.queryProvider = queryProvider;
      98          53 :     this.queryCache = new HashMap<>();
      99          53 :     this.heads = new HashMap<>();
     100          53 :     this.changeIsVisibleToPredicateFactory = changeIsVisibleToPredicateFactory;
     101          53 :     this.maxSubmittableChangesAtOnce =
     102          53 :         gerritConfig.getInt(
     103             :             "change", "maxSubmittableAtOnce", MAX_SUBMITTABLE_CHANGES_AT_ONCE_DEFAULT);
     104          53 :   }
     105             : 
     106             :   @Override
     107             :   public ChangeSet completeWithoutTopic(
     108             :       MergeOpRepoManager orm, ChangeSet changeSet, CurrentUser user) throws IOException {
     109          53 :     Collection<ChangeData> visibleChanges = new ArrayList<>();
     110          53 :     Collection<ChangeData> nonVisibleChanges = new ArrayList<>();
     111             : 
     112             :     // For each target branch we run a separate rev walk to find open changes
     113             :     // reachable from changes already in the merge super set.
     114          53 :     ImmutableSet<BranchNameKey> branches =
     115          53 :         byBranch(Iterables.concat(changeSet.changes(), changeSet.nonVisibleChanges())).keySet();
     116          53 :     ImmutableListMultimap<BranchNameKey, ChangeData> visibleChangesPerBranch =
     117          53 :         byBranch(changeSet.changes());
     118          53 :     ImmutableListMultimap<BranchNameKey, ChangeData> nonVisibleChangesPerBranch =
     119          53 :         byBranch(changeSet.nonVisibleChanges());
     120             : 
     121          53 :     for (BranchNameKey branchNameKey : branches) {
     122          53 :       OpenRepo or = getRepo(orm, branchNameKey.project());
     123          53 :       List<RevCommit> visibleCommits = new ArrayList<>();
     124          53 :       List<RevCommit> nonVisibleCommits = new ArrayList<>();
     125             : 
     126          53 :       for (ChangeData cd : visibleChangesPerBranch.get(branchNameKey)) {
     127          53 :         if (submitType(cd) == SubmitType.CHERRY_PICK) {
     128           8 :           visibleChanges.add(cd);
     129             :         } else {
     130          53 :           visibleCommits.add(or.rw.parseCommit(cd.currentPatchSet().commitId()));
     131             :         }
     132          53 :       }
     133          53 :       for (ChangeData cd : nonVisibleChangesPerBranch.get(branchNameKey)) {
     134           7 :         if (submitType(cd) == SubmitType.CHERRY_PICK) {
     135           1 :           nonVisibleChanges.add(cd);
     136             :         } else {
     137           6 :           nonVisibleCommits.add(or.rw.parseCommit(cd.currentPatchSet().commitId()));
     138             :         }
     139           7 :       }
     140             : 
     141          53 :       Set<String> visibleHashes =
     142          53 :           walkChangesByHashes(
     143             :               visibleCommits,
     144          53 :               Collections.emptySet(),
     145             :               or,
     146             :               branchNameKey,
     147             :               maxSubmittableChangesAtOnce);
     148          53 :       Set<String> nonVisibleHashes =
     149          53 :           walkChangesByHashes(
     150             :               nonVisibleCommits, visibleHashes, or, branchNameKey, maxSubmittableChangesAtOnce);
     151             : 
     152          53 :       ChangeSet partialSet =
     153          53 :           byCommitsOnBranchNotMerged(or, branchNameKey, visibleHashes, nonVisibleHashes, user);
     154          53 :       Iterables.addAll(visibleChanges, partialSet.changes());
     155          53 :       Iterables.addAll(nonVisibleChanges, partialSet.nonVisibleChanges());
     156          53 :     }
     157             : 
     158          53 :     return new ChangeSet(visibleChanges, nonVisibleChanges);
     159             :   }
     160             : 
     161             :   private static ImmutableListMultimap<BranchNameKey, ChangeData> byBranch(
     162             :       Iterable<ChangeData> changes) {
     163             :     ImmutableListMultimap.Builder<BranchNameKey, ChangeData> builder =
     164          53 :         ImmutableListMultimap.builder();
     165          53 :     for (ChangeData cd : changes) {
     166          53 :       builder.put(cd.change().getDest(), cd);
     167          53 :     }
     168          53 :     return builder.build();
     169             :   }
     170             : 
     171             :   private OpenRepo getRepo(MergeOpRepoManager orm, Project.NameKey project) throws IOException {
     172             :     try {
     173          53 :       OpenRepo or = orm.getRepo(project);
     174          53 :       checkState(or.rw.hasRevSort(RevSort.TOPO));
     175          53 :       return or;
     176           0 :     } catch (NoSuchProjectException e) {
     177           0 :       throw new IOException(e);
     178             :     }
     179             :   }
     180             : 
     181             :   private SubmitType submitType(ChangeData cd) {
     182          53 :     SubmitTypeRecord str = cd.submitTypeRecord();
     183          53 :     if (!str.isOk()) {
     184           0 :       logErrorAndThrow("Failed to get submit type for " + cd.getId() + ": " + str.errorMessage);
     185             :     }
     186          53 :     return str.type;
     187             :   }
     188             : 
     189             :   @UsedAt(UsedAt.Project.GOOGLE)
     190             :   public ChangeSet byCommitsOnBranchNotMerged(
     191             :       OpenRepo or,
     192             :       BranchNameKey branch,
     193             :       Set<String> visibleHashes,
     194             :       Set<String> nonVisibleHashes,
     195             :       CurrentUser user)
     196             :       throws IOException {
     197          53 :     List<ChangeData> potentiallyVisibleChanges =
     198          53 :         byCommitsOnBranchNotMerged(or, branch, visibleHashes);
     199          53 :     List<ChangeData> invisibleChanges =
     200          53 :         new ArrayList<>(byCommitsOnBranchNotMerged(or, branch, nonVisibleHashes));
     201          53 :     List<ChangeData> visibleChanges = new ArrayList<>(potentiallyVisibleChanges.size());
     202          53 :     ChangeIsVisibleToPredicate changeIsVisibleToPredicate =
     203          53 :         changeIsVisibleToPredicateFactory.forUser(user);
     204          53 :     for (ChangeData cd : potentiallyVisibleChanges) {
     205             :       // short circuit permission checks for non-private changes, as we already checked all
     206             :       // permissions (except for private changes).
     207          53 :       if (!cd.change().isPrivate() || changeIsVisibleToPredicate.match(cd)) {
     208          53 :         visibleChanges.add(cd);
     209             :       } else {
     210           2 :         invisibleChanges.add(cd);
     211             :       }
     212          53 :     }
     213          53 :     return new ChangeSet(visibleChanges, invisibleChanges);
     214             :   }
     215             : 
     216             :   private ImmutableList<ChangeData> byCommitsOnBranchNotMerged(
     217             :       OpenRepo or, BranchNameKey branch, Set<String> hashes) throws IOException {
     218          53 :     if (hashes.isEmpty()) {
     219          53 :       return ImmutableList.of();
     220             :     }
     221          53 :     QueryKey k = QueryKey.create(branch, hashes);
     222          53 :     if (queryCache.containsKey(k)) {
     223           0 :       return queryCache.get(k);
     224             :     }
     225          53 :     ImmutableList<ChangeData> result =
     226          53 :         ImmutableList.copyOf(
     227          53 :             queryProvider.get().byCommitsOnBranchNotMerged(or.repo, branch, hashes));
     228          53 :     queryCache.put(k, result);
     229          53 :     return result;
     230             :   }
     231             : 
     232             :   @UsedAt(UsedAt.Project.GOOGLE)
     233             :   public Set<String> walkChangesByHashes(
     234             :       Collection<RevCommit> sourceCommits,
     235             :       Set<String> ignoreHashes,
     236             :       OpenRepo or,
     237             :       BranchNameKey b,
     238             :       int limit)
     239             :       throws IOException {
     240          53 :     Set<String> destHashes = new HashSet<>();
     241          53 :     or.rw.reset();
     242          53 :     markHeadUninteresting(or, b);
     243          53 :     for (RevCommit c : sourceCommits) {
     244          53 :       String name = c.name();
     245          53 :       if (ignoreHashes.contains(name)) {
     246           0 :         continue;
     247             :       }
     248          53 :       if (destHashes.size() < limit) {
     249          53 :         destHashes.add(name);
     250             :       } else {
     251             :         break;
     252             :       }
     253          53 :       or.rw.markStart(c);
     254          53 :     }
     255          53 :     for (RevCommit c : or.rw) {
     256          53 :       String name = c.name();
     257          53 :       if (ignoreHashes.contains(name)) {
     258           0 :         continue;
     259             :       }
     260          53 :       if (destHashes.size() < limit) {
     261          53 :         destHashes.add(name);
     262             :       } else {
     263             :         break;
     264             :       }
     265          53 :     }
     266             : 
     267          53 :     return destHashes;
     268             :   }
     269             : 
     270             :   private void markHeadUninteresting(OpenRepo or, BranchNameKey b) throws IOException {
     271          53 :     Optional<RevCommit> head = heads.get(b);
     272          53 :     if (head == null) {
     273          53 :       Ref ref = or.repo.getRefDatabase().exactRef(b.branch());
     274          53 :       head = ref != null ? Optional.of(or.rw.parseCommit(ref.getObjectId())) : Optional.empty();
     275          53 :       heads.put(b, head);
     276             :     }
     277          53 :     if (head.isPresent()) {
     278          53 :       or.rw.markUninteresting(head.get());
     279             :     }
     280          53 :   }
     281             : 
     282             :   private void logErrorAndThrow(String msg) {
     283           0 :     logger.atSevere().log("%s", msg);
     284           0 :     throw new StorageException(msg);
     285             :   }
     286             : }

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