LCOV - code coverage report
Current view: top level - server/submit - MergeOpRepoManager.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 81 86 94.2 %
Date: 2022-11-19 15:00:39 Functions: 15 15 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.submit;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkState;
      18             : import static com.google.gerrit.server.project.ProjectCache.noSuchProject;
      19             : import static java.util.Objects.requireNonNull;
      20             : 
      21             : import com.google.common.collect.ImmutableSet;
      22             : import com.google.common.collect.Maps;
      23             : import com.google.gerrit.entities.BranchNameKey;
      24             : import com.google.gerrit.entities.Project;
      25             : import com.google.gerrit.entities.RefNames;
      26             : import com.google.gerrit.exceptions.StorageException;
      27             : import com.google.gerrit.server.IdentifiedUser;
      28             : import com.google.gerrit.server.change.NotifyResolver;
      29             : import com.google.gerrit.server.git.CodeReviewCommit;
      30             : import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
      31             : import com.google.gerrit.server.git.GitRepositoryManager;
      32             : import com.google.gerrit.server.git.MergeTip;
      33             : import com.google.gerrit.server.git.validators.OnSubmitValidators;
      34             : import com.google.gerrit.server.project.NoSuchProjectException;
      35             : import com.google.gerrit.server.project.ProjectCache;
      36             : import com.google.gerrit.server.project.ProjectState;
      37             : import com.google.gerrit.server.update.BatchUpdate;
      38             : import com.google.inject.Inject;
      39             : import java.io.IOException;
      40             : import java.time.Instant;
      41             : import java.util.ArrayList;
      42             : import java.util.Collection;
      43             : import java.util.HashMap;
      44             : import java.util.List;
      45             : import java.util.Map;
      46             : import java.util.Objects;
      47             : import org.eclipse.jgit.errors.RepositoryNotFoundException;
      48             : import org.eclipse.jgit.lib.ObjectInserter;
      49             : import org.eclipse.jgit.lib.ObjectReader;
      50             : import org.eclipse.jgit.lib.Ref;
      51             : import org.eclipse.jgit.lib.Repository;
      52             : import org.eclipse.jgit.revwalk.RevFlag;
      53             : import org.eclipse.jgit.revwalk.RevSort;
      54             : 
      55             : /**
      56             :  * This is a helper class for MergeOp and not intended for general use.
      57             :  *
      58             :  * <p>Some database backends require to open a repository just once within a transaction of a
      59             :  * submission, this caches open repositories to satisfy that requirement.
      60             :  */
      61             : public class MergeOpRepoManager implements AutoCloseable {
      62             :   public class OpenRepo {
      63             :     final Repository repo;
      64             :     final CodeReviewRevWalk rw;
      65             :     final RevFlag canMergeFlag;
      66             :     final ObjectInserter ins;
      67             : 
      68             :     final ProjectState project;
      69             :     BatchUpdate update;
      70             : 
      71             :     private final ObjectReader reader;
      72             :     private final Map<BranchNameKey, OpenBranch> branches;
      73             : 
      74          54 :     private OpenRepo(Repository repo, ProjectState project) {
      75          54 :       this.repo = repo;
      76          54 :       this.project = project;
      77          54 :       ins = repo.newObjectInserter();
      78          54 :       reader = ins.newReader();
      79          54 :       rw = CodeReviewCommit.newRevWalk(reader);
      80          54 :       rw.sort(RevSort.TOPO);
      81          54 :       rw.sort(RevSort.COMMIT_TIME_DESC, true);
      82          54 :       rw.setRetainBody(false);
      83          54 :       canMergeFlag = rw.newFlag("CAN_MERGE");
      84          54 :       rw.retainOnReset(canMergeFlag);
      85             : 
      86          54 :       branches = Maps.newHashMapWithExpectedSize(1);
      87          54 :     }
      88             : 
      89             :     OpenBranch getBranch(BranchNameKey branch) throws IntegrationConflictException {
      90          53 :       OpenBranch ob = branches.get(branch);
      91          53 :       if (ob == null) {
      92          53 :         ob = new OpenBranch(this, branch);
      93          53 :         branches.put(branch, ob);
      94             :       }
      95          53 :       return ob;
      96             :     }
      97             : 
      98             :     public Repository getRepo() {
      99          53 :       return repo;
     100             :     }
     101             : 
     102             :     Project.NameKey getProjectName() {
     103          53 :       return project.getNameKey();
     104             :     }
     105             : 
     106             :     public CodeReviewRevWalk getCodeReviewRevWalk() {
     107           3 :       return rw;
     108             :     }
     109             : 
     110             :     public BatchUpdate getUpdate() {
     111          53 :       checkState(caller != null, "call setContext before getUpdate");
     112          53 :       if (update == null) {
     113          53 :         update =
     114             :             batchUpdateFactory
     115          53 :                 .create(getProjectName(), caller, ts)
     116          53 :                 .setRepository(repo, rw, ins)
     117          53 :                 .setNotify(notify)
     118          53 :                 .setOnSubmitValidators(onSubmitValidatorsFactory.create());
     119             :       }
     120          53 :       return update;
     121             :     }
     122             : 
     123             :     // We want to reuse the open repo BUT not the BatchUpdate (because they are already executed)
     124             :     public void resetExecutedUpdates() {
     125          53 :       if (update != null && update.isExecuted()) {
     126          53 :         update.close();
     127          53 :         update = null;
     128             :       }
     129          53 :     }
     130             : 
     131             :     private void close() {
     132          53 :       if (update != null) {
     133          11 :         update.close();
     134             :       }
     135          53 :       rw.close();
     136          53 :       reader.close();
     137          53 :       ins.close();
     138          53 :       repo.close();
     139          53 :     }
     140             :   }
     141             : 
     142             :   public static class OpenBranch {
     143             :     final CodeReviewCommit oldTip;
     144             :     MergeTip mergeTip;
     145             : 
     146          53 :     OpenBranch(OpenRepo or, BranchNameKey name) throws IntegrationConflictException {
     147             :       try {
     148          53 :         Ref ref = or.getRepo().exactRef(name.branch());
     149          53 :         if (ref != null) {
     150          53 :           oldTip = or.rw.parseCommit(ref.getObjectId());
     151          16 :         } else if (Objects.equals(or.repo.getFullBranch(), name.branch())
     152           3 :             || Objects.equals(RefNames.REFS_CONFIG, name.branch())) {
     153          16 :           oldTip = null;
     154             :         } else {
     155           0 :           throw new IntegrationConflictException(
     156             :               "The destination branch " + name + " does not exist anymore.");
     157             :         }
     158           0 :       } catch (IOException e) {
     159           0 :         throw new StorageException("Cannot open branch " + name, e);
     160          53 :       }
     161          53 :     }
     162             :   }
     163             : 
     164             :   private final Map<Project.NameKey, OpenRepo> openRepos;
     165             :   private final BatchUpdate.Factory batchUpdateFactory;
     166             :   private final OnSubmitValidators.Factory onSubmitValidatorsFactory;
     167             :   private final GitRepositoryManager repoManager;
     168             :   private final ProjectCache projectCache;
     169             : 
     170             :   private Instant ts;
     171             :   private IdentifiedUser caller;
     172             :   private NotifyResolver.Result notify;
     173             : 
     174             :   @Inject
     175             :   MergeOpRepoManager(
     176             :       GitRepositoryManager repoManager,
     177             :       ProjectCache projectCache,
     178             :       BatchUpdate.Factory batchUpdateFactory,
     179          70 :       OnSubmitValidators.Factory onSubmitValidatorsFactory) {
     180          70 :     this.repoManager = repoManager;
     181          70 :     this.projectCache = projectCache;
     182          70 :     this.batchUpdateFactory = batchUpdateFactory;
     183          70 :     this.onSubmitValidatorsFactory = onSubmitValidatorsFactory;
     184             : 
     185          70 :     openRepos = new HashMap<>();
     186          70 :   }
     187             : 
     188             :   public void setContext(Instant ts, IdentifiedUser caller, NotifyResolver.Result notify) {
     189          69 :     this.ts = requireNonNull(ts);
     190          69 :     this.caller = requireNonNull(caller);
     191          69 :     this.notify = requireNonNull(notify);
     192          69 :   }
     193             : 
     194             :   public OpenRepo getRepo(Project.NameKey project) throws NoSuchProjectException, IOException {
     195          54 :     if (openRepos.containsKey(project)) {
     196          54 :       return openRepos.get(project);
     197             :     }
     198             : 
     199          54 :     ProjectState projectState = projectCache.get(project).orElseThrow(noSuchProject(project));
     200             :     try {
     201          54 :       OpenRepo or = new OpenRepo(repoManager.openRepository(project), projectState);
     202          54 :       openRepos.put(project, or);
     203          54 :       return or;
     204           0 :     } catch (RepositoryNotFoundException e) {
     205           0 :       throw new NoSuchProjectException(project, e);
     206             :     }
     207             :   }
     208             : 
     209             :   public List<BatchUpdate> batchUpdates(Collection<Project.NameKey> projects, String refLogMessage)
     210             :       throws NoSuchProjectException, IOException {
     211          68 :     requireNonNull(refLogMessage, "refLogMessage");
     212          68 :     List<BatchUpdate> updates = new ArrayList<>(projects.size());
     213          68 :     for (Project.NameKey project : projects) {
     214          53 :       updates.add(getRepo(project).getUpdate().setNotify(notify).setRefLogMessage(refLogMessage));
     215          53 :     }
     216          68 :     return updates;
     217             :   }
     218             : 
     219             :   public void resetUpdates(ImmutableSet<Project.NameKey> projects)
     220             :       throws NoSuchProjectException, IOException {
     221          53 :     for (Project.NameKey project : projects) {
     222          53 :       getRepo(project).resetExecutedUpdates();
     223          53 :     }
     224          53 :   }
     225             : 
     226             :   @Override
     227             :   public void close() {
     228          69 :     for (OpenRepo repo : openRepos.values()) {
     229          53 :       repo.close();
     230          53 :     }
     231          69 :     openRepos.clear();
     232          69 :   }
     233             : }

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