LCOV - code coverage report
Current view: top level - server/notedb - OpenRepo.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 78 79 98.7 %
Date: 2022-11-19 15:00:39 Functions: 10 10 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2019 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.notedb;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkArgument;
      18             : import static com.google.common.base.Preconditions.checkState;
      19             : import static java.util.Objects.requireNonNull;
      20             : 
      21             : import com.google.common.collect.ListMultimap;
      22             : import com.google.gerrit.common.Nullable;
      23             : import com.google.gerrit.entities.Project;
      24             : import com.google.gerrit.exceptions.StorageException;
      25             : import com.google.gerrit.server.git.GitRepositoryManager;
      26             : import com.google.gerrit.server.git.InMemoryInserter;
      27             : import com.google.gerrit.server.git.InsertedObject;
      28             : import com.google.gerrit.server.update.ChainedReceiveCommands;
      29             : import java.io.IOException;
      30             : import java.util.Collection;
      31             : import java.util.Map;
      32             : import java.util.Objects;
      33             : import java.util.Optional;
      34             : import org.eclipse.jgit.lib.ObjectId;
      35             : import org.eclipse.jgit.lib.ObjectInserter;
      36             : import org.eclipse.jgit.lib.ObjectReader;
      37             : import org.eclipse.jgit.lib.Repository;
      38             : import org.eclipse.jgit.revwalk.RevWalk;
      39             : import org.eclipse.jgit.transport.ReceiveCommand;
      40             : 
      41             : /**
      42             :  * Wrapper around {@link Repository} that keeps track of related {@link ObjectInserter}s and other
      43             :  * objects that are jointly closed when invoking {@link #close}.
      44             :  */
      45             : class OpenRepo implements AutoCloseable {
      46             :   final Repository repo;
      47             :   final RevWalk rw;
      48             :   final ChainedReceiveCommands cmds;
      49             :   final ObjectInserter tempIns;
      50             : 
      51             :   private final InMemoryInserter inMemIns;
      52             :   @Nullable private final ObjectInserter finalIns;
      53             :   private final boolean close;
      54             : 
      55             :   /** Returns a {@link OpenRepo} wrapping around an open {@link Repository}. */
      56             :   static OpenRepo open(GitRepositoryManager repoManager, Project.NameKey project)
      57             :       throws IOException {
      58          32 :     Repository repo = repoManager.openRepository(project); // Closed by OpenRepo#close.
      59          32 :     ObjectInserter ins = repo.newObjectInserter(); // Closed by OpenRepo#close.
      60          32 :     ObjectReader reader = ins.newReader(); // Not closed by OpenRepo#close.
      61          32 :     try (RevWalk rw = new RevWalk(reader)) { // Doesn't escape OpenRepo constructor.
      62          32 :       return new OpenRepo(repo, rw, ins, new ChainedReceiveCommands(repo), true) {
      63             :         @Override
      64             :         public void close() {
      65          32 :           reader.close();
      66          32 :           super.close();
      67          32 :         }
      68             :       };
      69             :     }
      70             :   }
      71             : 
      72             :   OpenRepo(
      73             :       Repository repo,
      74             :       RevWalk rw,
      75             :       @Nullable ObjectInserter ins,
      76             :       ChainedReceiveCommands cmds,
      77         110 :       boolean close) {
      78         110 :     ObjectReader reader = rw.getObjectReader();
      79         110 :     checkArgument(
      80         110 :         ins == null || reader.getCreatedFromInserter() == ins,
      81             :         "expected reader to be created from %s, but was %s",
      82             :         ins,
      83         110 :         reader.getCreatedFromInserter());
      84         110 :     this.repo = requireNonNull(repo);
      85             : 
      86         110 :     this.inMemIns = new InMemoryInserter(rw.getObjectReader());
      87         110 :     this.tempIns = inMemIns;
      88             : 
      89         110 :     this.rw = new RevWalk(tempIns.newReader());
      90         110 :     this.finalIns = ins;
      91         110 :     this.cmds = requireNonNull(cmds);
      92         110 :     this.close = close;
      93         110 :   }
      94             : 
      95             :   @Override
      96             :   public void close() {
      97         110 :     rw.getObjectReader().close();
      98         110 :     rw.close();
      99         110 :     if (close) {
     100          32 :       if (finalIns != null) {
     101          32 :         finalIns.close();
     102             :       }
     103          32 :       repo.close();
     104             :     }
     105         110 :   }
     106             : 
     107             :   void flush() throws IOException {
     108         109 :     flushToFinalInserter();
     109         109 :     finalIns.flush();
     110         109 :   }
     111             : 
     112             :   void flushToFinalInserter() throws IOException {
     113         109 :     checkState(finalIns != null);
     114         109 :     for (InsertedObject obj : inMemIns.getInsertedObjects()) {
     115         103 :       finalIns.insert(obj.type(), obj.data().toByteArray());
     116         103 :     }
     117         109 :     inMemIns.clear();
     118         109 :   }
     119             : 
     120             :   private static <U extends AbstractChangeUpdate> boolean allowWrite(
     121             :       Collection<U> updates, ObjectId old) {
     122         103 :     if (!old.equals(ObjectId.zeroId())) {
     123          90 :       return true;
     124             :     }
     125         103 :     return updates.iterator().next().allowWriteToNewRef();
     126             :   }
     127             : 
     128             :   <U extends AbstractChangeUpdate> void addUpdatesNoLimits(ListMultimap<String, U> all)
     129             :       throws IOException {
     130          29 :     addUpdates(
     131          29 :         all, Optional.empty() /* unlimited updates */, Optional.empty() /* unlimited patch sets */);
     132          29 :   }
     133             : 
     134             :   <U extends AbstractChangeUpdate> void addUpdates(
     135             :       ListMultimap<String, U> all, Optional<Integer> maxUpdates, Optional<Integer> maxPatchSets)
     136             :       throws IOException {
     137         109 :     for (Map.Entry<String, Collection<U>> e : all.asMap().entrySet()) {
     138         103 :       String refName = e.getKey();
     139         103 :       Collection<U> updates = e.getValue();
     140         103 :       ObjectId old = cmds.get(refName).orElse(ObjectId.zeroId());
     141             :       // Only actually write to the ref if one of the updates explicitly allows
     142             :       // us to do so, i.e. it is known to represent a new change. This avoids
     143             :       // writing partial change meta if the change hasn't been backfilled yet.
     144         103 :       if (!allowWrite(updates, old)) {
     145           0 :         continue;
     146             :       }
     147             : 
     148         103 :       int updateCount = 0;
     149         103 :       U first = updates.iterator().next();
     150         103 :       if (maxUpdates.isPresent()) {
     151         103 :         checkState(first.getNotes() != null, "expected ChangeNotes on %s", first);
     152         103 :         updateCount = first.getNotes().getUpdateCount();
     153             :       }
     154             : 
     155         103 :       ObjectId curr = old;
     156         103 :       for (U update : updates) {
     157         103 :         if (maxPatchSets.isPresent() && update.psId != null) {
     158             :           // Patch set IDs are assigned consecutively. Patch sets may have been deleted, but the ID
     159             :           // is still a good estimate and an upper bound.
     160         103 :           if (update.psId.get() > maxPatchSets.get()) {
     161           1 :             throw new LimitExceededException(
     162           1 :                 String.format(
     163             :                     "Change %d may not exceed %d patch sets. To continue working on this change, "
     164             :                         + "recreate it with a new Change-Id, then abandon this one.",
     165           1 :                     update.getId().get(), maxPatchSets.get()));
     166             :           }
     167             :         }
     168         103 :         if (update.isRootOnly() && !old.equals(ObjectId.zeroId())) {
     169           1 :           throw new StorageException("Given ChangeUpdate is only allowed on initial commit");
     170             :         }
     171         103 :         ObjectId next = update.apply(rw, tempIns, curr);
     172         103 :         if (next == null) {
     173          40 :           continue;
     174             :         }
     175         103 :         if (maxUpdates.isPresent()
     176         103 :             && !Objects.equals(next, curr)
     177         103 :             && ++updateCount > maxUpdates.get()
     178           2 :             && !update.bypassMaxUpdates()) {
     179           2 :           throw new LimitExceededException(
     180           2 :               String.format(
     181             :                   "Change %s may not exceed %d updates. It may still be abandoned, submitted and you can add/remove"
     182             :                       + " reviewers to/from the attention-set. To continue working on this change, recreate it with a new"
     183             :                       + " Change-Id, then abandon this one.",
     184           2 :                   update.getId(), maxUpdates.get()));
     185             :         }
     186         103 :         curr = next;
     187         103 :       }
     188         103 :       if (!old.equals(curr)) {
     189         103 :         cmds.add(new ReceiveCommand(old, curr, refName));
     190             :       }
     191         103 :     }
     192         109 :   }
     193             : }

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