LCOV - code coverage report
Current view: top level - server/git/receive - ReceiveCommitsAdvertiseRefsHook.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 36 44 81.8 %
Date: 2022-11-19 15:00:39 Functions: 6 7 85.7 %

          Line data    Source code
       1             : // Copyright (C) 2010 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.git.receive;
      16             : 
      17             : import static com.google.common.collect.ImmutableList.toImmutableList;
      18             : 
      19             : import com.google.common.collect.Sets;
      20             : import com.google.common.flogger.FluentLogger;
      21             : import com.google.gerrit.entities.Account;
      22             : import com.google.gerrit.entities.PatchSet;
      23             : import com.google.gerrit.entities.Project;
      24             : import com.google.gerrit.entities.RefNames;
      25             : import com.google.gerrit.exceptions.StorageException;
      26             : import com.google.gerrit.index.query.Predicate;
      27             : import com.google.gerrit.server.git.HookUtil;
      28             : import com.google.gerrit.server.index.change.ChangeField;
      29             : import com.google.gerrit.server.query.change.ChangeData;
      30             : import com.google.gerrit.server.query.change.ChangePredicates;
      31             : import com.google.gerrit.server.query.change.ChangeStatusPredicate;
      32             : import com.google.gerrit.server.query.change.InternalChangeQuery;
      33             : import com.google.gerrit.server.util.MagicBranch;
      34             : import com.google.inject.Provider;
      35             : import java.io.IOException;
      36             : import java.util.Collections;
      37             : import java.util.Map;
      38             : import java.util.Set;
      39             : import org.eclipse.jgit.lib.ObjectId;
      40             : import org.eclipse.jgit.lib.Ref;
      41             : import org.eclipse.jgit.lib.Repository;
      42             : import org.eclipse.jgit.transport.AdvertiseRefsHook;
      43             : import org.eclipse.jgit.transport.ReceivePack;
      44             : import org.eclipse.jgit.transport.ServiceMayNotContinueException;
      45             : import org.eclipse.jgit.transport.UploadPack;
      46             : 
      47             : /**
      48             :  * Exposes only the non refs/changes/ reference names and provide additional haves.
      49             :  *
      50             :  * <p>Negotiation on Git push is suboptimal in that it tends to send more objects to the server than
      51             :  * it should. This results in increased latencies for {@code git push}.
      52             :  *
      53             :  * <p>Ref advertisement for Git pushes still works in a "the server speaks first fashion" as Git
      54             :  * Protocol V2 only addressed fetches Therefore the server needs to send all available references.
      55             :  * For large repositories, this can be in the tens of megabytes to send to the client. We therefore
      56             :  * remove all refs in refs/changes/* to scale down that footprint. Trivially, this would increase
      57             :  * the unnecessary objects that the client has to send to the server because the common ancestor it
      58             :  * finds in negotiation might be further back in history.
      59             :  *
      60             :  * <p>To work around this, we advertise the last 32 changes in that repository as additional {@code
      61             :  * .haves}. This is a heuristical approach that aims at scaling down the number of unnecessary
      62             :  * objects that client sends to the server. Unnecessary here refers to objects that the server
      63             :  * already has.
      64             :  *
      65             :  * <p>TODO(hiesel): Instrument this heuristic and proof its value.
      66             :  */
      67             : public class ReceiveCommitsAdvertiseRefsHook implements AdvertiseRefsHook {
      68          97 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      69             : 
      70             :   private final Provider<InternalChangeQuery> queryProvider;
      71             :   private final Project.NameKey projectName;
      72             :   private final Account.Id user;
      73             : 
      74             :   public ReceiveCommitsAdvertiseRefsHook(
      75          97 :       Provider<InternalChangeQuery> queryProvider, Project.NameKey projectName, Account.Id user) {
      76          97 :     this.queryProvider = queryProvider;
      77          97 :     this.projectName = projectName;
      78          97 :     this.user = user;
      79          97 :   }
      80             : 
      81             :   @Override
      82             :   public void advertiseRefs(UploadPack us) {
      83           0 :     throw new UnsupportedOperationException(
      84             :         "ReceiveCommitsAdvertiseRefsHook cannot be used for UploadPack");
      85             :   }
      86             : 
      87             :   @Override
      88             :   public void advertiseRefs(ReceivePack rp) throws ServiceMayNotContinueException {
      89          97 :     Map<String, Ref> advertisedRefs = HookUtil.ensureAllRefsAdvertised(rp);
      90          97 :     advertisedRefs.keySet().stream()
      91          97 :         .filter(ReceiveCommitsAdvertiseRefsHook::skip)
      92          97 :         .collect(toImmutableList())
      93          97 :         .forEach(r -> advertisedRefs.remove(r));
      94             :     try {
      95          97 :       rp.setAdvertisedRefs(advertisedRefs, advertiseOpenChanges(rp.getRepository()));
      96           0 :     } catch (IOException e) {
      97           0 :       throw new ServiceMayNotContinueException(e);
      98          97 :     }
      99          97 :   }
     100             : 
     101             :   private Set<ObjectId> advertiseOpenChanges(Repository repo)
     102             :       throws ServiceMayNotContinueException {
     103             :     // Advertise the user's most recent open changes. It's likely that the user has one of these in
     104             :     // their local repo and they can serve as starting points to figure out the common ancestor of
     105             :     // what the client and server have in common.
     106          97 :     int limit = 32;
     107             :     try {
     108          97 :       Set<ObjectId> r = Sets.newHashSetWithExpectedSize(limit);
     109             :       for (ChangeData cd :
     110             :           queryProvider
     111          97 :               .get()
     112          97 :               .setRequestedFields(
     113             :                   // Required for ChangeIsVisibleToPrdicate.
     114             :                   ChangeField.CHANGE,
     115             :                   ChangeField.REVIEWER_SPEC,
     116             :                   // Required during advertiseOpenChanges.
     117             :                   ChangeField.PATCH_SET)
     118          97 :               .enforceVisibility(true)
     119          97 :               .setLimit(limit)
     120          97 :               .query(
     121          97 :                   Predicate.and(
     122          97 :                       ChangePredicates.project(projectName),
     123          97 :                       ChangeStatusPredicate.open(),
     124          97 :                       ChangePredicates.owner(user)))) {
     125          62 :         PatchSet ps = cd.currentPatchSet();
     126          62 :         if (ps != null) {
     127             :           // Ensure we actually observed a patch set ref pointing to this
     128             :           // object, in case the database is out of sync with the repo and the
     129             :           // object doesn't actually exist.
     130             :           try {
     131          62 :             Ref psRef = repo.getRefDatabase().exactRef(RefNames.patchSetRef(ps.id()));
     132          62 :             if (psRef != null) {
     133          62 :               r.add(ps.commitId());
     134             :             }
     135           0 :           } catch (IOException e) {
     136           0 :             throw new ServiceMayNotContinueException(e);
     137          62 :           }
     138             :         }
     139          62 :       }
     140             : 
     141          97 :       return r;
     142           0 :     } catch (StorageException err) {
     143           0 :       logger.atSevere().withCause(err).log("Cannot list open changes of %s", projectName);
     144           0 :       return Collections.emptySet();
     145             :     }
     146             :   }
     147             : 
     148             :   private static boolean skip(String name) {
     149          97 :     return name.startsWith(RefNames.REFS_CHANGES)
     150          97 :         || name.startsWith(RefNames.REFS_CACHE_AUTOMERGE)
     151          97 :         || MagicBranch.isMagicBranch(name);
     152             :   }
     153             : }

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