BasePackPushConnection.java

  1. /*
  2.  * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
  3.  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  4.  * and other copyright owners as documented in the project's IP log.
  5.  *
  6.  * This program and the accompanying materials are made available
  7.  * under the terms of the Eclipse Distribution License v1.0 which
  8.  * accompanies this distribution, is reproduced below, and is
  9.  * available at http://www.eclipse.org/org/documents/edl-v10.php
  10.  *
  11.  * All rights reserved.
  12.  *
  13.  * Redistribution and use in source and binary forms, with or
  14.  * without modification, are permitted provided that the following
  15.  * conditions are met:
  16.  *
  17.  * - Redistributions of source code must retain the above copyright
  18.  *   notice, this list of conditions and the following disclaimer.
  19.  *
  20.  * - Redistributions in binary form must reproduce the above
  21.  *   copyright notice, this list of conditions and the following
  22.  *   disclaimer in the documentation and/or other materials provided
  23.  *   with the distribution.
  24.  *
  25.  * - Neither the name of the Eclipse Foundation, Inc. nor the
  26.  *   names of its contributors may be used to endorse or promote
  27.  *   products derived from this software without specific prior
  28.  *   written permission.
  29.  *
  30.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  31.  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  32.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  33.  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  34.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  35.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  36.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  37.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  38.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  39.  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  40.  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  41.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  42.  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  43.  */

  44. package org.eclipse.jgit.transport;

  45. import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC;

  46. import java.io.IOException;
  47. import java.io.InputStream;
  48. import java.io.OutputStream;
  49. import java.text.MessageFormat;
  50. import java.util.Collection;
  51. import java.util.HashSet;
  52. import java.util.List;
  53. import java.util.Map;
  54. import java.util.Set;

  55. import org.eclipse.jgit.errors.NoRemoteRepositoryException;
  56. import org.eclipse.jgit.errors.NotSupportedException;
  57. import org.eclipse.jgit.errors.PackProtocolException;
  58. import org.eclipse.jgit.errors.TooLargeObjectInPackException;
  59. import org.eclipse.jgit.errors.TooLargePackException;
  60. import org.eclipse.jgit.errors.TransportException;
  61. import org.eclipse.jgit.internal.JGitText;
  62. import org.eclipse.jgit.internal.storage.pack.PackWriter;
  63. import org.eclipse.jgit.lib.ObjectId;
  64. import org.eclipse.jgit.lib.ProgressMonitor;
  65. import org.eclipse.jgit.lib.Ref;
  66. import org.eclipse.jgit.transport.RemoteRefUpdate.Status;

  67. /**
  68.  * Push implementation using the native Git pack transfer service.
  69.  * <p>
  70.  * This is the canonical implementation for transferring objects to the remote
  71.  * repository from the local repository by talking to the 'git-receive-pack'
  72.  * service. Objects are packed on the local side into a pack file and then sent
  73.  * to the remote repository.
  74.  * <p>
  75.  * This connection requires only a bi-directional pipe or socket, and thus is
  76.  * easily wrapped up into a local process pipe, anonymous TCP socket, or a
  77.  * command executed through an SSH tunnel.
  78.  * <p>
  79.  * This implementation honors
  80.  * {@link org.eclipse.jgit.transport.Transport#isPushThin()} option.
  81.  * <p>
  82.  * Concrete implementations should just call
  83.  * {@link #init(java.io.InputStream, java.io.OutputStream)} and
  84.  * {@link #readAdvertisedRefs()} methods in constructor or before any use. They
  85.  * should also handle resources releasing in {@link #close()} method if needed.
  86.  */
  87. public abstract class BasePackPushConnection extends BasePackConnection implements
  88.         PushConnection {
  89.     /**
  90.      * The client expects a status report after the server processes the pack.
  91.      * @since 2.0
  92.      */
  93.     public static final String CAPABILITY_REPORT_STATUS = GitProtocolConstants.CAPABILITY_REPORT_STATUS;

  94.     /**
  95.      * The server supports deleting refs.
  96.      * @since 2.0
  97.      */
  98.     public static final String CAPABILITY_DELETE_REFS = GitProtocolConstants.CAPABILITY_DELETE_REFS;

  99.     /**
  100.      * The server supports packs with OFS deltas.
  101.      * @since 2.0
  102.      */
  103.     public static final String CAPABILITY_OFS_DELTA = GitProtocolConstants.CAPABILITY_OFS_DELTA;

  104.     /**
  105.      * The client supports using the 64K side-band for progress messages.
  106.      * @since 2.0
  107.      */
  108.     public static final String CAPABILITY_SIDE_BAND_64K = GitProtocolConstants.CAPABILITY_SIDE_BAND_64K;

  109.     /**
  110.      * The server supports the receiving of push options.
  111.      * @since 4.5
  112.      */
  113.     public static final String CAPABILITY_PUSH_OPTIONS = GitProtocolConstants.CAPABILITY_PUSH_OPTIONS;

  114.     private final boolean thinPack;
  115.     private final boolean atomic;

  116.     /** A list of option strings associated with this push. */
  117.     private List<String> pushOptions;

  118.     private boolean capableAtomic;
  119.     private boolean capableDeleteRefs;
  120.     private boolean capableReport;
  121.     private boolean capableSideBand;
  122.     private boolean capableOfsDelta;
  123.     private boolean capablePushOptions;

  124.     private boolean sentCommand;
  125.     private boolean writePack;

  126.     /** Time in milliseconds spent transferring the pack data. */
  127.     private long packTransferTime;

  128.     /**
  129.      * Create a new connection to push using the native git transport.
  130.      *
  131.      * @param packTransport
  132.      *            the transport.
  133.      */
  134.     public BasePackPushConnection(PackTransport packTransport) {
  135.         super(packTransport);
  136.         thinPack = transport.isPushThin();
  137.         atomic = transport.isPushAtomic();
  138.         pushOptions = transport.getPushOptions();
  139.     }

  140.     /** {@inheritDoc} */
  141.     @Override
  142.     public void push(final ProgressMonitor monitor,
  143.             final Map<String, RemoteRefUpdate> refUpdates)
  144.             throws TransportException {
  145.         push(monitor, refUpdates, null);
  146.     }

  147.     /** {@inheritDoc} */
  148.     @Override
  149.     public void push(final ProgressMonitor monitor,
  150.             final Map<String, RemoteRefUpdate> refUpdates, OutputStream outputStream)
  151.             throws TransportException {
  152.         markStartedOperation();
  153.         doPush(monitor, refUpdates, outputStream);
  154.     }

  155.     /** {@inheritDoc} */
  156.     @Override
  157.     protected TransportException noRepository() {
  158.         // Sadly we cannot tell the "invalid URI" case from "push not allowed".
  159.         // Opening a fetch connection can help us tell the difference, as any
  160.         // useful repository is going to support fetch if it also would allow
  161.         // push. So if fetch throws NoRemoteRepositoryException we know the
  162.         // URI is wrong. Otherwise we can correctly state push isn't allowed
  163.         // as the fetch connection opened successfully.
  164.         //
  165.         try {
  166.             transport.openFetch().close();
  167.         } catch (NotSupportedException e) {
  168.             // Fall through.
  169.         } catch (NoRemoteRepositoryException e) {
  170.             // Fetch concluded the repository doesn't exist.
  171.             //
  172.             return e;
  173.         } catch (TransportException e) {
  174.             // Fall through.
  175.         }
  176.         return new TransportException(uri, JGitText.get().pushNotPermitted);
  177.     }

  178.     /**
  179.      * Push one or more objects and update the remote repository.
  180.      *
  181.      * @param monitor
  182.      *            progress monitor to receive status updates.
  183.      * @param refUpdates
  184.      *            update commands to be applied to the remote repository.
  185.      * @param outputStream
  186.      *            output stream to write sideband messages to
  187.      * @throws org.eclipse.jgit.errors.TransportException
  188.      *             if any exception occurs.
  189.      * @since 3.0
  190.      */
  191.     protected void doPush(final ProgressMonitor monitor,
  192.             final Map<String, RemoteRefUpdate> refUpdates,
  193.             OutputStream outputStream) throws TransportException {
  194.         try {
  195.             writeCommands(refUpdates.values(), monitor, outputStream);

  196.             if (pushOptions != null && capablePushOptions)
  197.                 transmitOptions();
  198.             if (writePack)
  199.                 writePack(refUpdates, monitor);
  200.             if (sentCommand) {
  201.                 if (capableReport)
  202.                     readStatusReport(refUpdates);
  203.                 if (capableSideBand) {
  204.                     // Ensure the data channel is at EOF, so we know we have
  205.                     // read all side-band data from all channels and have a
  206.                     // complete copy of the messages (if any) buffered from
  207.                     // the other data channels.
  208.                     //
  209.                     int b = in.read();
  210.                     if (0 <= b)
  211.                         throw new TransportException(uri, MessageFormat.format(
  212.                                 JGitText.get().expectedEOFReceived,
  213.                                 Character.valueOf((char) b)));
  214.                 }
  215.             }
  216.         } catch (TransportException e) {
  217.             throw e;
  218.         } catch (Exception e) {
  219.             throw new TransportException(uri, e.getMessage(), e);
  220.         } finally {
  221.             close();
  222.         }
  223.     }

  224.     private void writeCommands(final Collection<RemoteRefUpdate> refUpdates,
  225.             final ProgressMonitor monitor, OutputStream outputStream) throws IOException {
  226.         final String capabilities = enableCapabilities(monitor, outputStream);
  227.         if (atomic && !capableAtomic) {
  228.             throw new TransportException(uri,
  229.                     JGitText.get().atomicPushNotSupported);
  230.         }

  231.         if (pushOptions != null && !capablePushOptions) {
  232.             throw new TransportException(uri,
  233.                     MessageFormat.format(JGitText.get().pushOptionsNotSupported,
  234.                             pushOptions.toString()));
  235.         }

  236.         for (RemoteRefUpdate rru : refUpdates) {
  237.             if (!capableDeleteRefs && rru.isDelete()) {
  238.                 rru.setStatus(Status.REJECTED_NODELETE);
  239.                 continue;
  240.             }

  241.             final StringBuilder sb = new StringBuilder();
  242.             ObjectId oldId = rru.getExpectedOldObjectId();
  243.             if (oldId == null) {
  244.                 final Ref advertised = getRef(rru.getRemoteName());
  245.                 oldId = advertised != null ? advertised.getObjectId() : null;
  246.                 if (oldId == null) {
  247.                     oldId = ObjectId.zeroId();
  248.                 }
  249.             }
  250.             sb.append(oldId.name());
  251.             sb.append(' ');
  252.             sb.append(rru.getNewObjectId().name());
  253.             sb.append(' ');
  254.             sb.append(rru.getRemoteName());
  255.             if (!sentCommand) {
  256.                 sentCommand = true;
  257.                 sb.append(capabilities);
  258.             }

  259.             pckOut.writeString(sb.toString());
  260.             rru.setStatus(Status.AWAITING_REPORT);
  261.             if (!rru.isDelete())
  262.                 writePack = true;
  263.         }

  264.         if (monitor.isCancelled())
  265.             throw new TransportException(uri, JGitText.get().pushCancelled);
  266.         pckOut.end();
  267.         outNeedsEnd = false;
  268.     }

  269.     private void transmitOptions() throws IOException {
  270.         for (String pushOption : pushOptions) {
  271.             pckOut.writeString(pushOption);
  272.         }

  273.         pckOut.end();
  274.     }

  275.     private String enableCapabilities(final ProgressMonitor monitor,
  276.             OutputStream outputStream) {
  277.         final StringBuilder line = new StringBuilder();
  278.         if (atomic)
  279.             capableAtomic = wantCapability(line, CAPABILITY_ATOMIC);
  280.         capableReport = wantCapability(line, CAPABILITY_REPORT_STATUS);
  281.         capableDeleteRefs = wantCapability(line, CAPABILITY_DELETE_REFS);
  282.         capableOfsDelta = wantCapability(line, CAPABILITY_OFS_DELTA);

  283.         if (pushOptions != null) {
  284.             capablePushOptions = wantCapability(line, CAPABILITY_PUSH_OPTIONS);
  285.         }

  286.         capableSideBand = wantCapability(line, CAPABILITY_SIDE_BAND_64K);
  287.         if (capableSideBand) {
  288.             in = new SideBandInputStream(in, monitor, getMessageWriter(),
  289.                     outputStream);
  290.             pckIn = new PacketLineIn(in);
  291.         }
  292.         addUserAgentCapability(line);

  293.         if (line.length() > 0)
  294.             line.setCharAt(0, '\0');
  295.         return line.toString();
  296.     }

  297.     private void writePack(final Map<String, RemoteRefUpdate> refUpdates,
  298.             final ProgressMonitor monitor) throws IOException {
  299.         Set<ObjectId> remoteObjects = new HashSet<>();
  300.         Set<ObjectId> newObjects = new HashSet<>();

  301.         try (PackWriter writer = new PackWriter(transport.getPackConfig(),
  302.                 local.newObjectReader())) {

  303.             for (Ref r : getRefs()) {
  304.                 // only add objects that we actually have
  305.                 ObjectId oid = r.getObjectId();
  306.                 if (local.getObjectDatabase().has(oid))
  307.                     remoteObjects.add(oid);
  308.             }
  309.             remoteObjects.addAll(additionalHaves);
  310.             for (RemoteRefUpdate r : refUpdates.values()) {
  311.                 if (!ObjectId.zeroId().equals(r.getNewObjectId()))
  312.                     newObjects.add(r.getNewObjectId());
  313.             }

  314.             writer.setIndexDisabled(true);
  315.             writer.setUseCachedPacks(true);
  316.             writer.setUseBitmaps(true);
  317.             writer.setThin(thinPack);
  318.             writer.setReuseValidatingObjects(false);
  319.             writer.setDeltaBaseAsOffset(capableOfsDelta);
  320.             writer.preparePack(monitor, newObjects, remoteObjects);

  321.             OutputStream packOut = out;
  322.             if (capableSideBand) {
  323.                 packOut = new CheckingSideBandOutputStream(in, out);
  324.             }
  325.             writer.writePack(monitor, monitor, packOut);

  326.             packTransferTime = writer.getStatistics().getTimeWriting();
  327.         }
  328.     }

  329.     private void readStatusReport(Map<String, RemoteRefUpdate> refUpdates)
  330.             throws IOException {
  331.         final String unpackLine = readStringLongTimeout();
  332.         if (!unpackLine.startsWith("unpack ")) //$NON-NLS-1$
  333.             throw new PackProtocolException(uri, MessageFormat
  334.                     .format(JGitText.get().unexpectedReportLine, unpackLine));
  335.         final String unpackStatus = unpackLine.substring("unpack ".length()); //$NON-NLS-1$
  336.         if (unpackStatus.startsWith("error Pack exceeds the limit of")) {//$NON-NLS-1$
  337.             throw new TooLargePackException(uri,
  338.                     unpackStatus.substring("error ".length())); //$NON-NLS-1$
  339.         } else if (unpackStatus.startsWith("error Object too large")) {//$NON-NLS-1$
  340.             throw new TooLargeObjectInPackException(uri,
  341.                     unpackStatus.substring("error ".length())); //$NON-NLS-1$
  342.         } else if (!unpackStatus.equals("ok")) { //$NON-NLS-1$
  343.             throw new TransportException(uri, MessageFormat.format(
  344.                     JGitText.get().errorOccurredDuringUnpackingOnTheRemoteEnd, unpackStatus));
  345.         }

  346.         String refLine;
  347.         while ((refLine = pckIn.readString()) != PacketLineIn.END) {
  348.             boolean ok = false;
  349.             int refNameEnd = -1;
  350.             if (refLine.startsWith("ok ")) { //$NON-NLS-1$
  351.                 ok = true;
  352.                 refNameEnd = refLine.length();
  353.             } else if (refLine.startsWith("ng ")) { //$NON-NLS-1$
  354.                 ok = false;
  355.                 refNameEnd = refLine.indexOf(" ", 3); //$NON-NLS-1$
  356.             }
  357.             if (refNameEnd == -1)
  358.                 throw new PackProtocolException(MessageFormat.format(JGitText.get().unexpectedReportLine2
  359.                         , uri, refLine));
  360.             final String refName = refLine.substring(3, refNameEnd);
  361.             final String message = (ok ? null : refLine
  362.                     .substring(refNameEnd + 1));

  363.             final RemoteRefUpdate rru = refUpdates.get(refName);
  364.             if (rru == null)
  365.                 throw new PackProtocolException(MessageFormat.format(JGitText.get().unexpectedRefReport, uri, refName));
  366.             if (ok) {
  367.                 rru.setStatus(Status.OK);
  368.             } else {
  369.                 rru.setStatus(Status.REJECTED_OTHER_REASON);
  370.                 rru.setMessage(message);
  371.             }
  372.         }
  373.         for (RemoteRefUpdate rru : refUpdates.values()) {
  374.             if (rru.getStatus() == Status.AWAITING_REPORT)
  375.                 throw new PackProtocolException(MessageFormat.format(
  376.                         JGitText.get().expectedReportForRefNotReceived , uri, rru.getRemoteName()));
  377.         }
  378.     }

  379.     private String readStringLongTimeout() throws IOException {
  380.         if (timeoutIn == null)
  381.             return pckIn.readString();

  382.         // The remote side may need a lot of time to choke down the pack
  383.         // we just sent them. There may be many deltas that need to be
  384.         // resolved by the remote. Its hard to say how long the other
  385.         // end is going to be silent. Taking 10x the configured timeout
  386.         // or the time spent transferring the pack, whichever is larger,
  387.         // gives the other side some reasonable window to process the data,
  388.         // but this is just a wild guess.
  389.         //
  390.         final int oldTimeout = timeoutIn.getTimeout();
  391.         final int sendTime = (int) Math.min(packTransferTime, 28800000L);
  392.         try {
  393.             int timeout = 10 * Math.max(sendTime, oldTimeout);
  394.             timeoutIn.setTimeout((timeout < 0) ? Integer.MAX_VALUE : timeout);
  395.             return pckIn.readString();
  396.         } finally {
  397.             timeoutIn.setTimeout(oldTimeout);
  398.         }
  399.     }

  400.     /**
  401.      * Gets the list of option strings associated with this push.
  402.      *
  403.      * @return pushOptions
  404.      * @since 4.5
  405.      */
  406.     public List<String> getPushOptions() {
  407.         return pushOptions;
  408.     }

  409.     private static class CheckingSideBandOutputStream extends OutputStream {
  410.         private final InputStream in;
  411.         private final OutputStream out;

  412.         CheckingSideBandOutputStream(InputStream in, OutputStream out) {
  413.             this.in = in;
  414.             this.out = out;
  415.         }

  416.         @Override
  417.         public void write(int b) throws IOException {
  418.             write(new byte[] { (byte) b });
  419.         }

  420.         @Override
  421.         public void write(byte[] buf, int ptr, int cnt) throws IOException {
  422.             try {
  423.                 out.write(buf, ptr, cnt);
  424.             } catch (IOException e) {
  425.                 throw checkError(e);
  426.             }
  427.         }

  428.         @Override
  429.         public void flush() throws IOException {
  430.             try {
  431.                 out.flush();
  432.             } catch (IOException e) {
  433.                 throw checkError(e);
  434.             }
  435.         }

  436.         private IOException checkError(IOException e1) {
  437.             try {
  438.                 in.read();
  439.             } catch (TransportException e2) {
  440.                 return e2;
  441.             } catch (IOException e2) {
  442.                 return e1;
  443.             }
  444.             return e1;
  445.         }
  446.     }
  447. }