LCOV - code coverage report
Current view: top level - server/git/receive - HackPushNegotiateHook.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 5 46 10.9 %
Date: 2022-11-19 15:00:39 Functions: 3 7 42.9 %

          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 java.util.stream.Collectors.toMap;
      18             : 
      19             : import com.google.common.collect.Sets;
      20             : import com.google.common.flogger.FluentLogger;
      21             : import com.google.gerrit.git.ObjectIds;
      22             : import java.io.IOException;
      23             : import java.util.Collection;
      24             : import java.util.Map;
      25             : import java.util.Set;
      26             : import org.eclipse.jgit.lib.ObjectId;
      27             : import org.eclipse.jgit.lib.Ref;
      28             : import org.eclipse.jgit.revwalk.RevCommit;
      29             : import org.eclipse.jgit.revwalk.RevWalk;
      30             : import org.eclipse.jgit.transport.AdvertiseRefsHook;
      31             : import org.eclipse.jgit.transport.ReceivePack;
      32             : import org.eclipse.jgit.transport.ServiceMayNotContinueException;
      33             : import org.eclipse.jgit.transport.UploadPack;
      34             : 
      35             : /**
      36             :  * Advertises part of history to git push clients.
      37             :  *
      38             :  * <p>This is a hack to work around the lack of negotiation in the send-pack/receive-pack wire
      39             :  * protocol.
      40             :  *
      41             :  * <p>When the server is frequently advancing master by creating merge commits, the client may not
      42             :  * be able to discover a common ancestor during push. Attempting to push will re-upload a very large
      43             :  * amount of history. This hook hacks in a fake negotiation replacement by walking history and
      44             :  * sending recent commits as {@code ".have"} lines in the wire protocol, allowing the client to find
      45             :  * a common ancestor.
      46             :  */
      47          97 : public class HackPushNegotiateHook implements AdvertiseRefsHook {
      48          97 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      49             : 
      50             :   /** Size of an additional ".have" line. */
      51             :   private static final int HAVE_LINE_LEN = 4 + ObjectIds.STR_LEN + 1 + 5 + 1;
      52             : 
      53             :   /**
      54             :    * Maximum number of bytes to "waste" in the advertisement with a peek at this repository's
      55             :    * current reachable history.
      56             :    */
      57             :   private static final int MAX_EXTRA_BYTES = 8192;
      58             : 
      59             :   /**
      60             :    * Number of recent commits to advertise immediately, hoping to show a client a nearby merge base.
      61             :    */
      62             :   private static final int BASE_COMMITS = 64;
      63             : 
      64             :   /** Number of commits to skip once base has already been shown. */
      65             :   private static final int STEP_COMMITS = 16;
      66             : 
      67             :   /** Total number of commits to extract from the history. */
      68             :   private static final int MAX_HISTORY = MAX_EXTRA_BYTES / HAVE_LINE_LEN;
      69             : 
      70             :   @Override
      71             :   public void advertiseRefs(UploadPack us) {
      72           0 :     throw new UnsupportedOperationException("HackPushNegotiateHook cannot be used for UploadPack");
      73             :   }
      74             : 
      75             :   @Override
      76             :   public void advertiseRefs(ReceivePack rp) throws ServiceMayNotContinueException {
      77          97 :     Map<String, Ref> r = rp.getAdvertisedRefs();
      78          97 :     if (r == null) {
      79             :       try {
      80           0 :         r =
      81           0 :             rp.getRepository().getRefDatabase().getRefs().stream()
      82           0 :                 .collect(toMap(Ref::getName, x -> x));
      83           0 :         rp.setAdvertisedRefs(r, history(r.values(), rp));
      84           0 :       } catch (ServiceMayNotContinueException e) {
      85           0 :         throw e;
      86           0 :       } catch (IOException e) {
      87           0 :         throw new ServiceMayNotContinueException(e);
      88           0 :       }
      89             :     }
      90          97 :   }
      91             : 
      92             :   private Set<ObjectId> history(Collection<Ref> refs, ReceivePack rp) {
      93           0 :     Set<ObjectId> alreadySending = rp.getAdvertisedObjects();
      94           0 :     if (MAX_HISTORY <= alreadySending.size()) {
      95           0 :       return alreadySending;
      96             :     }
      97             : 
      98             :     // Scan history until the advertisement is full.
      99           0 :     RevWalk rw = rp.getRevWalk();
     100           0 :     rw.reset();
     101             :     try {
     102           0 :       Set<ObjectId> tips = idsOf(refs);
     103           0 :       for (ObjectId tip : tips) {
     104             :         try {
     105           0 :           rw.markStart(rw.parseCommit(tip));
     106           0 :         } catch (IOException badCommit) {
     107           0 :           continue;
     108           0 :         }
     109           0 :       }
     110             : 
     111           0 :       Set<ObjectId> history = Sets.newHashSetWithExpectedSize(MAX_HISTORY);
     112           0 :       history.addAll(alreadySending);
     113             :       try {
     114           0 :         int stepCnt = 0;
     115           0 :         for (RevCommit c; history.size() < MAX_HISTORY && (c = rw.next()) != null; ) {
     116           0 :           if (c.getParentCount() <= 1
     117           0 :               && !tips.contains(c)
     118           0 :               && (history.size() < BASE_COMMITS || (++stepCnt % STEP_COMMITS) == 0)) {
     119           0 :             history.add(c);
     120             :           }
     121             :         }
     122           0 :       } catch (IOException err) {
     123           0 :         logger.atSevere().withCause(err).log("error trying to advertise history");
     124           0 :       }
     125           0 :       return history;
     126             :     } finally {
     127           0 :       rw.reset();
     128             :     }
     129             :   }
     130             : 
     131             :   private static Set<ObjectId> idsOf(Collection<Ref> refs) {
     132           0 :     Set<ObjectId> r = Sets.newHashSetWithExpectedSize(refs.size());
     133           0 :     for (Ref ref : refs) {
     134           0 :       if (ref.getObjectId() != null) {
     135           0 :         r.add(ref.getObjectId());
     136             :       }
     137           0 :     }
     138           0 :     return r;
     139             :   }
     140             : }

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