RefAdvertiser.java

  1. /*
  2.  * Copyright (C) 2008, 2020 Google Inc. and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

  10. package org.eclipse.jgit.transport;

  11. import static java.nio.charset.StandardCharsets.UTF_8;
  12. import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
  13. import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SYMREF;
  14. import static org.eclipse.jgit.transport.GitProtocolConstants.REF_ATTR_PEELED;
  15. import static org.eclipse.jgit.transport.GitProtocolConstants.REF_ATTR_SYMREF_TARGET;

  16. import java.io.IOException;
  17. import java.nio.ByteBuffer;
  18. import java.nio.CharBuffer;
  19. import java.nio.charset.CharacterCodingException;
  20. import java.nio.charset.CharsetEncoder;
  21. import java.nio.charset.CoderResult;
  22. import java.util.Collection;
  23. import java.util.HashMap;
  24. import java.util.HashSet;
  25. import java.util.LinkedHashSet;
  26. import java.util.Map;
  27. import java.util.Set;

  28. import org.eclipse.jgit.lib.AnyObjectId;
  29. import org.eclipse.jgit.lib.Constants;
  30. import org.eclipse.jgit.lib.ObjectId;
  31. import org.eclipse.jgit.lib.Ref;
  32. import org.eclipse.jgit.lib.RefComparator;
  33. import org.eclipse.jgit.lib.Repository;

  34. /**
  35.  * Support for the start of {@link org.eclipse.jgit.transport.UploadPack} and
  36.  * {@link org.eclipse.jgit.transport.ReceivePack}.
  37.  */
  38. public abstract class RefAdvertiser {
  39.     /** Advertiser which frames lines in a {@link PacketLineOut} format. */
  40.     public static class PacketLineOutRefAdvertiser extends RefAdvertiser {
  41.         private final CharsetEncoder utf8 = UTF_8.newEncoder();
  42.         private final PacketLineOut pckOut;

  43.         private byte[] binArr = new byte[256];
  44.         private ByteBuffer binBuf = ByteBuffer.wrap(binArr);

  45.         private char[] chArr = new char[256];
  46.         private CharBuffer chBuf = CharBuffer.wrap(chArr);

  47.         /**
  48.          * Create a new advertiser for the supplied stream.
  49.          *
  50.          * @param out
  51.          *            the output stream.
  52.          */
  53.         public PacketLineOutRefAdvertiser(PacketLineOut out) {
  54.             pckOut = out;
  55.         }

  56.         @Override
  57.         public void advertiseId(AnyObjectId id, String refName)
  58.                 throws IOException {
  59.             id.copyTo(binArr, 0);
  60.             binArr[OBJECT_ID_STRING_LENGTH] = ' ';
  61.             binBuf.position(OBJECT_ID_STRING_LENGTH + 1);
  62.             append(refName);
  63.             if (first) {
  64.                 first = false;
  65.                 if (!capablities.isEmpty()) {
  66.                     append('\0');
  67.                     for (String cap : capablities) {
  68.                         append(' ');
  69.                         append(cap);
  70.                     }
  71.                 }
  72.             }
  73.             append('\n');
  74.             pckOut.writePacket(binArr, 0, binBuf.position());
  75.         }

  76.         private void append(String str) throws CharacterCodingException {
  77.             int n = str.length();
  78.             if (n > chArr.length) {
  79.                 chArr = new char[n + 256];
  80.                 chBuf = CharBuffer.wrap(chArr);
  81.             }
  82.             str.getChars(0, n, chArr, 0);
  83.             chBuf.position(0).limit(n);
  84.             utf8.reset();
  85.             for (;;) {
  86.                 CoderResult cr = utf8.encode(chBuf, binBuf, true);
  87.                 if (cr.isOverflow()) {
  88.                     grow();
  89.                 } else if (cr.isUnderflow()) {
  90.                     break;
  91.                 } else {
  92.                     cr.throwException();
  93.                 }
  94.             }
  95.         }

  96.         private void append(int b) {
  97.             if (!binBuf.hasRemaining()) {
  98.                 grow();
  99.             }
  100.             binBuf.put((byte) b);
  101.         }

  102.         private void grow() {
  103.             int cnt = binBuf.position();
  104.             byte[] tmp = new byte[binArr.length << 1];
  105.             System.arraycopy(binArr, 0, tmp, 0, cnt);
  106.             binArr = tmp;
  107.             binBuf = ByteBuffer.wrap(binArr);
  108.             binBuf.position(cnt);
  109.         }

  110.         @Override
  111.         protected void writeOne(CharSequence line) throws IOException {
  112.             pckOut.writeString(line.toString());
  113.         }

  114.         @Override
  115.         protected void end() throws IOException {
  116.             pckOut.end();
  117.         }
  118.     }

  119.     private final StringBuilder tmpLine = new StringBuilder(100);

  120.     private final char[] tmpId = new char[Constants.OBJECT_ID_STRING_LENGTH];

  121.     final Set<String> capablities = new LinkedHashSet<>();

  122.     private final Set<ObjectId> sent = new HashSet<>();

  123.     private Repository repository;

  124.     private boolean derefTags;

  125.     boolean first = true;

  126.     private boolean useProtocolV2;

  127.     /* only used in protocol v2 */
  128.     private final Map<String, String> symrefs = new HashMap<>();

  129.     /**
  130.      * Initialize this advertiser with a repository for peeling tags.
  131.      *
  132.      * @param src
  133.      *            the repository to read from.
  134.      */
  135.     public void init(Repository src) {
  136.         repository = src;
  137.     }

  138.     /**
  139.      * @param b
  140.      *              true if this advertiser should advertise using the protocol
  141.      *              v2 format, false otherwise
  142.      * @since 5.0
  143.      */
  144.     public void setUseProtocolV2(boolean b) {
  145.         useProtocolV2 = b;
  146.     }

  147.     /**
  148.      * Toggle tag peeling.
  149.      * <p>
  150.      * <p>
  151.      * This method must be invoked prior to any of the following:
  152.      * <ul>
  153.      * <li>{@link #send(Map)}</li>
  154.      * <li>{@link #send(Collection)}</li>
  155.      * </ul>
  156.      *
  157.      * @param deref
  158.      *            true to show the dereferenced value of a tag as the special
  159.      *            ref <code>$tag^{}</code> ; false to omit it from the output.
  160.      */
  161.     public void setDerefTags(boolean deref) {
  162.         derefTags = deref;
  163.     }

  164.     /**
  165.      * Add one protocol capability to the initial advertisement.
  166.      * <p>
  167.      * This method must be invoked prior to any of the following:
  168.      * <ul>
  169.      * <li>{@link #send(Map)}</li>
  170.      * <li>{@link #send(Collection)}</li>
  171.      * <li>{@link #advertiseHave(AnyObjectId)}</li>
  172.      * </ul>
  173.      *
  174.      * @param name
  175.      *            the name of a single protocol capability supported by the
  176.      *            caller. The set of capabilities are sent to the client in the
  177.      *            advertisement, allowing the client to later selectively enable
  178.      *            features it recognizes.
  179.      */
  180.     public void advertiseCapability(String name) {
  181.         capablities.add(name);
  182.     }

  183.     /**
  184.      * Add one protocol capability with a value ({@code "name=value"}).
  185.      *
  186.      * @param name
  187.      *            name of the capability.
  188.      * @param value
  189.      *            value. If null the capability will not be added.
  190.      * @since 4.0
  191.      */
  192.     public void advertiseCapability(String name, String value) {
  193.         if (value != null) {
  194.             capablities.add(name + '=' + value);
  195.         }
  196.     }

  197.     /**
  198.      * Add a symbolic ref to capabilities.
  199.      * <p>
  200.      * This method must be invoked prior to any of the following:
  201.      * <ul>
  202.      * <li>{@link #send(Map)}</li>
  203.      * <li>{@link #send(Collection)}</li>
  204.      * <li>{@link #advertiseHave(AnyObjectId)}</li>
  205.      * </ul>
  206.      *
  207.      * @param from
  208.      *            The symbolic ref, e.g. "HEAD"
  209.      * @param to
  210.      *            The real ref it points to, e.g. "refs/heads/master"
  211.      * @since 3.6
  212.      */
  213.     public void addSymref(String from, String to) {
  214.         if (useProtocolV2) {
  215.             symrefs.put(from, to);
  216.         } else {
  217.             advertiseCapability(OPTION_SYMREF, from + ':' + to);
  218.         }
  219.     }

  220.     /**
  221.      * Format an advertisement for the supplied refs.
  222.      *
  223.      * @param refs
  224.      *            zero or more refs to format for the client. The collection is
  225.      *            sorted before display if necessary, and therefore may appear
  226.      *            in any order.
  227.      * @return set of ObjectIds that were advertised to the client.
  228.      * @throws java.io.IOException
  229.      *             the underlying output stream failed to write out an
  230.      *             advertisement record.
  231.      * @deprecated use {@link #send(Collection)} instead.
  232.      */
  233.     @Deprecated
  234.     public Set<ObjectId> send(Map<String, Ref> refs) throws IOException {
  235.         return send(refs.values());
  236.     }

  237.     /**
  238.      * Format an advertisement for the supplied refs.
  239.      *
  240.      * @param refs
  241.      *            zero or more refs to format for the client. The collection is
  242.      *            sorted before display if necessary, and therefore may appear
  243.      *            in any order.
  244.      * @return set of ObjectIds that were advertised to the client.
  245.      * @throws java.io.IOException
  246.      *             the underlying output stream failed to write out an
  247.      *             advertisement record.
  248.      * @since 5.0
  249.      */
  250.     public Set<ObjectId> send(Collection<Ref> refs) throws IOException {
  251.         for (Ref ref : RefComparator.sort(refs)) {
  252.             // TODO(jrn) revive the SortedMap optimization e.g. by introducing
  253.             // SortedList
  254.             ObjectId objectId = ref.getObjectId();
  255.             if (objectId == null) {
  256.                 continue;
  257.             }

  258.             if (useProtocolV2) {
  259.                 String symrefPart = symrefs.containsKey(ref.getName())
  260.                         ? (' ' + REF_ATTR_SYMREF_TARGET
  261.                                 + symrefs.get(ref.getName()))
  262.                         : ""; //$NON-NLS-1$
  263.                 String peelPart = ""; //$NON-NLS-1$
  264.                 if (derefTags) {
  265.                     if (!ref.isPeeled() && repository != null) {
  266.                         ref = repository.getRefDatabase().peel(ref);
  267.                     }
  268.                     ObjectId peeledObjectId = ref.getPeeledObjectId();
  269.                     if (peeledObjectId != null) {
  270.                         peelPart = ' ' + REF_ATTR_PEELED
  271.                                 + peeledObjectId.getName();
  272.                     }
  273.                 }
  274.                 writeOne(objectId.getName() + " " + ref.getName() + symrefPart //$NON-NLS-1$
  275.                         + peelPart + "\n"); //$NON-NLS-1$
  276.                 continue;
  277.             }

  278.             advertiseAny(objectId, ref.getName());

  279.             if (!derefTags)
  280.                 continue;

  281.             if (!ref.isPeeled()) {
  282.                 if (repository == null)
  283.                     continue;
  284.                 ref = repository.getRefDatabase().peel(ref);
  285.             }

  286.             if (ref.getPeeledObjectId() != null)
  287.                 advertiseAny(ref.getPeeledObjectId(), ref.getName() + "^{}"); //$NON-NLS-1$
  288.         }
  289.         return sent;
  290.     }

  291.     /**
  292.      * Advertise one object is available using the magic {@code .have}.
  293.      * <p>
  294.      * The magic {@code .have} advertisement is not available for fetching by a
  295.      * client, but can be used by a client when considering a delta base
  296.      * candidate before transferring data in a push. Within the record created
  297.      * by this method the ref name is simply the invalid string {@code .have}.
  298.      *
  299.      * @param id
  300.      *            identity of the object that is assumed to exist.
  301.      * @throws java.io.IOException
  302.      *             the underlying output stream failed to write out an
  303.      *             advertisement record.
  304.      */
  305.     public void advertiseHave(AnyObjectId id) throws IOException {
  306.         advertiseAnyOnce(id, ".have"); //$NON-NLS-1$
  307.     }

  308.     /**
  309.      * Whether no advertisements have been sent yet.
  310.      *
  311.      * @return true if no advertisements have been sent yet.
  312.      */
  313.     public boolean isEmpty() {
  314.         return first;
  315.     }

  316.     private void advertiseAnyOnce(AnyObjectId obj, String refName)
  317.             throws IOException {
  318.         if (!sent.contains(obj))
  319.             advertiseAny(obj, refName);
  320.     }

  321.     private void advertiseAny(AnyObjectId obj, String refName)
  322.             throws IOException {
  323.         sent.add(obj.toObjectId());
  324.         advertiseId(obj, refName);
  325.     }

  326.     /**
  327.      * Advertise one object under a specific name.
  328.      * <p>
  329.      * If the advertised object is a tag, this method does not advertise the
  330.      * peeled version of it.
  331.      *
  332.      * @param id
  333.      *            the object to advertise.
  334.      * @param refName
  335.      *            name of the reference to advertise the object as, can be any
  336.      *            string not including the NUL byte.
  337.      * @throws java.io.IOException
  338.      *             the underlying output stream failed to write out an
  339.      *             advertisement record.
  340.      */
  341.     public void advertiseId(AnyObjectId id, String refName)
  342.             throws IOException {
  343.         tmpLine.setLength(0);
  344.         id.copyTo(tmpId, tmpLine);
  345.         tmpLine.append(' ');
  346.         tmpLine.append(refName);
  347.         if (first) {
  348.             first = false;
  349.             if (!capablities.isEmpty()) {
  350.                 tmpLine.append('\0');
  351.                 for (String capName : capablities) {
  352.                     tmpLine.append(' ');
  353.                     tmpLine.append(capName);
  354.                 }
  355.                 tmpLine.append(' ');
  356.             }
  357.         }
  358.         tmpLine.append('\n');
  359.         writeOne(tmpLine);
  360.     }

  361.     /**
  362.      * Write a single advertisement line.
  363.      *
  364.      * @param line
  365.      *            the advertisement line to be written. The line always ends
  366.      *            with LF. Never null or the empty string.
  367.      * @throws java.io.IOException
  368.      *             the underlying output stream failed to write out an
  369.      *             advertisement record.
  370.      */
  371.     protected abstract void writeOne(CharSequence line) throws IOException;

  372.     /**
  373.      * Mark the end of the advertisements.
  374.      *
  375.      * @throws java.io.IOException
  376.      *             the underlying output stream failed to write out an
  377.      *             advertisement record.
  378.      */
  379.     protected abstract void end() throws IOException;
  380. }