LCOV - code coverage report
Current view: top level - server/update - RepoView.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 57 57 100.0 %
Date: 2022-11-19 15:00:39 Functions: 20 20 100.0 %

          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.update;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkArgument;
      18             : import static java.util.Objects.requireNonNull;
      19             : import static java.util.stream.Collectors.toMap;
      20             : 
      21             : import com.google.gerrit.entities.Project;
      22             : import com.google.gerrit.server.git.GitRepositoryManager;
      23             : import java.io.IOException;
      24             : import java.util.Map;
      25             : import java.util.Optional;
      26             : import org.eclipse.jgit.lib.Config;
      27             : import org.eclipse.jgit.lib.ObjectId;
      28             : import org.eclipse.jgit.lib.ObjectInserter;
      29             : import org.eclipse.jgit.lib.Ref;
      30             : import org.eclipse.jgit.lib.Repository;
      31             : import org.eclipse.jgit.revwalk.RevWalk;
      32             : 
      33             : /**
      34             :  * Restricted view of a {@link Repository} for use by {@link BatchUpdateOp} implementations.
      35             :  *
      36             :  * <p>This class serves two purposes in the context of {@link BatchUpdate}. First, the subset of
      37             :  * normal Repository functionality is purely read-only, which prevents implementors from modifying
      38             :  * the repository outside of {@link BatchUpdateOp#updateRepo}. Write operations can only be
      39             :  * performed by calling methods on {@link RepoContext}.
      40             :  *
      41             :  * <p>Second, the read methods take into account any pending operations on the repository that
      42             :  * implementations have staged using the write methods on {@link RepoContext}. Callers do not have
      43             :  * to worry about whether operations have been performed yet.
      44             :  */
      45             : public class RepoView {
      46             :   private final Repository repo;
      47             :   private final RevWalk rw;
      48             :   private final ObjectInserter inserter;
      49             :   private final ObjectInserter inserterWrapper;
      50             :   private final ChainedReceiveCommands commands;
      51             :   private final boolean closeRepo;
      52             : 
      53          76 :   RepoView(GitRepositoryManager repoManager, Project.NameKey project) throws IOException {
      54          76 :     repo = repoManager.openRepository(project);
      55          76 :     inserter = repo.newObjectInserter();
      56          76 :     inserterWrapper = new NonFlushingInserter(inserter);
      57          76 :     rw = new RevWalk(inserter.newReader());
      58          76 :     commands = new ChainedReceiveCommands(repo);
      59          76 :     closeRepo = true;
      60          76 :   }
      61             : 
      62         109 :   public RepoView(Repository repo, RevWalk rw, ObjectInserter inserter) {
      63         109 :     checkArgument(
      64         109 :         rw.getObjectReader().getCreatedFromInserter() == inserter,
      65             :         "expected RevWalk %s to be created by ObjectInserter %s",
      66             :         rw,
      67             :         inserter);
      68         109 :     this.repo = requireNonNull(repo);
      69         109 :     this.rw = requireNonNull(rw);
      70         109 :     this.inserter = requireNonNull(inserter);
      71         109 :     inserterWrapper = new NonFlushingInserter(inserter);
      72         109 :     commands = new ChainedReceiveCommands(repo);
      73         109 :     closeRepo = false;
      74         109 :   }
      75             : 
      76             :   /**
      77             :    * Get this repo's configuration.
      78             :    *
      79             :    * <p>This is the storage-level config you would get with {@link Repository#getConfig()}, not, for
      80             :    * example, the Gerrit-level project config.
      81             :    *
      82             :    * @return a defensive copy of the config; modifications have no effect on the underlying config.
      83             :    */
      84             :   public Config getConfig() {
      85          77 :     return new Config(repo.getConfig());
      86             :   }
      87             : 
      88             :   /**
      89             :    * Get an open revwalk on the repo.
      90             :    *
      91             :    * <p>Guaranteed to be able to read back any objects inserted in the repository via {@link
      92             :    * RepoContext#getInserter()}, even if objects have not been flushed to the underlying repo. In
      93             :    * particular this includes any object returned by {@link #getRef(String)}, even taking into
      94             :    * account not-yet-executed commands.
      95             :    *
      96             :    * @return revwalk.
      97             :    */
      98             :   public RevWalk getRevWalk() {
      99         110 :     return rw;
     100             :   }
     101             : 
     102             :   /**
     103             :    * Read a single ref from the repo.
     104             :    *
     105             :    * <p>Takes into account any ref update commands added during the course of the update using
     106             :    * {@link RepoContext#addRefUpdate}, even if they have not yet been executed on the underlying
     107             :    * repo.
     108             :    *
     109             :    * <p>The results of individual ref lookups are cached: calling this method multiple times with
     110             :    * the same ref name will return the same result (unless a command was added in the meantime). The
     111             :    * repo is not reread.
     112             :    *
     113             :    * @param name exact ref name.
     114             :    * @return the value of the ref, if present.
     115             :    * @throws IOException if an error occurred.
     116             :    */
     117             :   public Optional<ObjectId> getRef(String name) throws IOException {
     118         103 :     return getCommands().get(name);
     119             :   }
     120             : 
     121             :   /**
     122             :    * Look up refs by prefix.
     123             :    *
     124             :    * <p>Takes into account any ref update commands added during the course of the update using
     125             :    * {@link RepoContext#addRefUpdate}, even if they have not yet been executed on the underlying
     126             :    * repo.
     127             :    *
     128             :    * <p>For any ref that has previously been accessed with {@link #getRef(String)}, the value in the
     129             :    * result map will be that same cached value. Any refs that have <em>not</em> been previously
     130             :    * accessed are re-scanned from the repo on each call.
     131             :    *
     132             :    * @param prefix ref prefix; must end in '/' or else be empty.
     133             :    * @return a map of ref suffixes to SHA-1s. The refs are all under {@code prefix} and have the
     134             :    *     prefix stripped.
     135             :    * @throws IOException if an error occurred.
     136             :    */
     137             :   public Map<String, ObjectId> getRefs(String prefix) throws IOException {
     138          59 :     Map<String, ObjectId> result =
     139          59 :         repo.getRefDatabase().getRefsByPrefix(prefix).stream()
     140          59 :             .collect(toMap(r -> r.getName().substring(prefix.length()), Ref::getObjectId));
     141             : 
     142             :     // First, overwrite any cached reads from the underlying RepoRefCache. If any of these differ,
     143             :     // it's because a ref was updated after the RepoRefCache read it. It feels a little odd to
     144             :     // prefer the *old* value in this case, but it would be weirder to be inconsistent with getRef.
     145             :     //
     146             :     // Mostly this doesn't matter. If the caller was intending to write to the ref, they lost a
     147             :     // race, and they will get a lock failure. If they just want to read, well, the JGit interface
     148             :     // doesn't currently guarantee that any snapshot of multiple refs is consistent, so they were
     149             :     // probably out of luck anyway.
     150          59 :     commands
     151          59 :         .getRepoRefCache()
     152          59 :         .getCachedRefs()
     153          59 :         .forEach((k, v) -> updateRefIfPrefixMatches(result, prefix, k, v));
     154             : 
     155             :     // Second, overwrite with any pending commands.
     156          59 :     commands
     157          59 :         .getCommands()
     158          59 :         .values()
     159          59 :         .forEach(
     160             :             c ->
     161          20 :                 updateRefIfPrefixMatches(result, prefix, c.getRefName(), toOptional(c.getNewId())));
     162             : 
     163          59 :     return result;
     164             :   }
     165             : 
     166             :   private static Optional<ObjectId> toOptional(ObjectId id) {
     167          20 :     return id.equals(ObjectId.zeroId()) ? Optional.empty() : Optional.of(id);
     168             :   }
     169             : 
     170             :   private static void updateRefIfPrefixMatches(
     171             :       Map<String, ObjectId> map, String prefix, String fullRefName, Optional<ObjectId> maybeId) {
     172          20 :     if (!fullRefName.startsWith(prefix)) {
     173          19 :       return;
     174             :     }
     175           1 :     String suffix = fullRefName.substring(prefix.length());
     176           1 :     if (maybeId.isPresent()) {
     177           1 :       map.put(suffix, maybeId.get());
     178             :     } else {
     179           1 :       map.remove(suffix);
     180             :     }
     181           1 :   }
     182             : 
     183             :   // Not AutoCloseable so callers can't improperly close it. Plus it's never managed with a try
     184             :   // block anyway.
     185             :   void close() {
     186         110 :     if (closeRepo) {
     187          76 :       inserter.close();
     188          76 :       rw.close();
     189          76 :       repo.close();
     190             :     }
     191         110 :   }
     192             : 
     193             :   Repository getRepository() {
     194         110 :     return repo;
     195             :   }
     196             : 
     197             :   ObjectInserter getInserter() {
     198         110 :     return inserter;
     199             :   }
     200             : 
     201             :   ObjectInserter getInserterWrapper() {
     202         103 :     return inserterWrapper;
     203             :   }
     204             : 
     205             :   ChainedReceiveCommands getCommands() {
     206         110 :     return commands;
     207             :   }
     208             : 
     209             :   private static class NonFlushingInserter extends ObjectInserter.Filter {
     210             :     private final ObjectInserter delegate;
     211             : 
     212         110 :     private NonFlushingInserter(ObjectInserter delegate) {
     213         110 :       this.delegate = delegate;
     214         110 :     }
     215             : 
     216             :     @Override
     217             :     protected ObjectInserter delegate() {
     218          45 :       return delegate;
     219             :     }
     220             : 
     221             :     @Override
     222             :     public void flush() {
     223             :       // Do nothing.
     224          36 :     }
     225             : 
     226             :     @Override
     227             :     public void close() {
     228             :       // Do nothing; the delegate is closed separately.
     229          30 :     }
     230             :   }
     231             : }

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