| /* |
| * Copyright (C) 2008-2010, Google Inc. |
| * and other copyright owners as documented in the project's IP log. |
| * |
| * This program and the accompanying materials are made available |
| * under the terms of the Eclipse Distribution License v1.0 which |
| * accompanies this distribution, is reproduced below, and is |
| * available at http://www.eclipse.org/org/documents/edl-v10.php |
| * |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * |
| * - Neither the name of the Eclipse Foundation, Inc. nor the |
| * names of its contributors may be used to endorse or promote |
| * products derived from this software without specific prior |
| * written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
| * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
| * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package org.eclipse.jgit.transport; |
| |
| import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC; |
| import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_OPTIONS; |
| import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import org.eclipse.jgit.annotations.Nullable; |
| import org.eclipse.jgit.errors.UnpackException; |
| import org.eclipse.jgit.lib.ConfigConstants; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.NullProgressMonitor; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.transport.ReceiveCommand.Result; |
| import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; |
| |
| /** |
| * Implements the server side of a push connection, receiving objects. |
| */ |
| public class ReceivePack extends BaseReceivePack { |
| /** Hook to validate the update commands before execution. */ |
| private PreReceiveHook preReceive; |
| |
| /** Hook to report on the commands after execution. */ |
| private PostReceiveHook postReceive; |
| |
| /** If {@link BasePackPushConnection#CAPABILITY_REPORT_STATUS} is enabled. */ |
| private boolean reportStatus; |
| |
| private boolean echoCommandFailures; |
| |
| /** Whether the client intends to use push options. */ |
| private boolean usePushOptions; |
| private List<String> pushOptions; |
| |
| /** |
| * Create a new pack receive for an open repository. |
| * |
| * @param into |
| * the destination repository. |
| */ |
| public ReceivePack(Repository into) { |
| super(into); |
| preReceive = PreReceiveHook.NULL; |
| postReceive = PostReceiveHook.NULL; |
| } |
| |
| /** |
| * Gets an unmodifiable view of the option strings associated with the push. |
| * |
| * @return an unmodifiable view of pushOptions, or null (if pushOptions is). |
| * @since 4.5 |
| */ |
| @Nullable |
| public List<String> getPushOptions() { |
| if (isAllowPushOptions() && usePushOptions) { |
| return Collections.unmodifiableList(pushOptions); |
| } |
| |
| // The client doesn't support push options. Return null to |
| // distinguish this from the case where the client declared support |
| // for push options and sent an empty list of them. |
| return null; |
| } |
| |
| /** |
| * Set the push options supplied by the client. |
| * <p> |
| * Should only be called if reconstructing an instance without going through |
| * the normal {@link #recvCommands()} flow. |
| * |
| * @param options |
| * the list of options supplied by the client. The |
| * {@code ReceivePack} instance takes ownership of this list. |
| * Callers are encouraged to first create a copy if the list may |
| * be modified later. |
| * @since 4.5 |
| */ |
| public void setPushOptions(@Nullable List<String> options) { |
| usePushOptions = options != null; |
| pushOptions = options; |
| } |
| |
| /** |
| * Get the hook invoked before updates occur. |
| * |
| * @return the hook invoked before updates occur. |
| */ |
| public PreReceiveHook getPreReceiveHook() { |
| return preReceive; |
| } |
| |
| /** |
| * Set the hook which is invoked prior to commands being executed. |
| * <p> |
| * Only valid commands (those which have no obvious errors according to the |
| * received input and this instance's configuration) are passed into the |
| * hook. The hook may mark a command with a result of any value other than |
| * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#NOT_ATTEMPTED} to |
| * block its execution. |
| * <p> |
| * The hook may be called with an empty command collection if the current |
| * set is completely invalid. |
| * |
| * @param h |
| * the hook instance; may be null to disable the hook. |
| */ |
| public void setPreReceiveHook(PreReceiveHook h) { |
| preReceive = h != null ? h : PreReceiveHook.NULL; |
| } |
| |
| /** |
| * Get the hook invoked after updates occur. |
| * |
| * @return the hook invoked after updates occur. |
| */ |
| public PostReceiveHook getPostReceiveHook() { |
| return postReceive; |
| } |
| |
| /** |
| * Set the hook which is invoked after commands are executed. |
| * <p> |
| * Only successful commands (type is |
| * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#OK}) are passed |
| * into the hook. The hook may be called with an empty command collection if |
| * the current set all resulted in an error. |
| * |
| * @param h |
| * the hook instance; may be null to disable the hook. |
| */ |
| public void setPostReceiveHook(PostReceiveHook h) { |
| postReceive = h != null ? h : PostReceiveHook.NULL; |
| } |
| |
| /** |
| * Set whether this class will report command failures as warning messages |
| * before sending the command results. |
| * |
| * @param echo |
| * if true this class will report command failures as warning |
| * messages before sending the command results. This is usually |
| * not necessary, but may help buggy Git clients that discard the |
| * errors when all branches fail. |
| */ |
| public void setEchoCommandFailures(boolean echo) { |
| echoCommandFailures = echo; |
| } |
| |
| /** |
| * Execute the receive task on the socket. |
| * |
| * @param input |
| * raw input to read client commands and pack data from. Caller |
| * must ensure the input is buffered, otherwise read performance |
| * may suffer. |
| * @param output |
| * response back to the Git network client. Caller must ensure |
| * the output is buffered, otherwise write performance may |
| * suffer. |
| * @param messages |
| * secondary "notice" channel to send additional messages out |
| * through. When run over SSH this should be tied back to the |
| * standard error channel of the command execution. For most |
| * other network connections this should be null. |
| * @throws java.io.IOException |
| */ |
| public void receive(final InputStream input, final OutputStream output, |
| final OutputStream messages) throws IOException { |
| init(input, output, messages); |
| try { |
| service(); |
| } finally { |
| try { |
| close(); |
| } finally { |
| release(); |
| } |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| protected void enableCapabilities() { |
| reportStatus = isCapabilityEnabled(CAPABILITY_REPORT_STATUS); |
| usePushOptions = isCapabilityEnabled(CAPABILITY_PUSH_OPTIONS); |
| super.enableCapabilities(); |
| } |
| |
| @Override |
| void readPostCommands(PacketLineIn in) throws IOException { |
| if (usePushOptions) { |
| pushOptions = new ArrayList<>(4); |
| for (;;) { |
| String option = in.readString(); |
| if (option == PacketLineIn.END) { |
| break; |
| } |
| pushOptions.add(option); |
| } |
| } |
| } |
| |
| private void service() throws IOException { |
| if (isBiDirectionalPipe()) { |
| sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut)); |
| pckOut.flush(); |
| } else |
| getAdvertisedOrDefaultRefs(); |
| if (hasError()) |
| return; |
| recvCommands(); |
| if (hasCommands()) { |
| Throwable unpackError = null; |
| if (needPack()) { |
| try { |
| receivePackAndCheckConnectivity(); |
| } catch (IOException | RuntimeException | Error err) { |
| unpackError = err; |
| } |
| } |
| |
| if (unpackError == null) { |
| boolean atomic = isCapabilityEnabled(CAPABILITY_ATOMIC); |
| setAtomic(atomic); |
| |
| validateCommands(); |
| if (atomic && anyRejects()) |
| failPendingCommands(); |
| |
| preReceive.onPreReceive(this, filterCommands(Result.NOT_ATTEMPTED)); |
| if (atomic && anyRejects()) |
| failPendingCommands(); |
| executeCommands(); |
| } |
| unlockPack(); |
| |
| if (reportStatus) { |
| if (echoCommandFailures && msgOut != null) { |
| sendStatusReport(false, unpackError, new Reporter() { |
| @Override |
| void sendString(String s) throws IOException { |
| msgOut.write(Constants.encode(s + "\n")); //$NON-NLS-1$ |
| } |
| }); |
| msgOut.flush(); |
| try { |
| Thread.sleep(500); |
| } catch (InterruptedException wakeUp) { |
| // Ignore an early wake up. |
| } |
| } |
| sendStatusReport(true, unpackError, new Reporter() { |
| @Override |
| void sendString(String s) throws IOException { |
| pckOut.writeString(s + "\n"); //$NON-NLS-1$ |
| } |
| }); |
| pckOut.end(); |
| } else if (msgOut != null) { |
| sendStatusReport(false, unpackError, new Reporter() { |
| @Override |
| void sendString(String s) throws IOException { |
| msgOut.write(Constants.encode(s + "\n")); //$NON-NLS-1$ |
| } |
| }); |
| } |
| |
| if (unpackError != null) { |
| // we already know which exception to throw. Ignore |
| // potential additional exceptions raised in postReceiveHooks |
| try { |
| postReceive.onPostReceive(this, filterCommands(Result.OK)); |
| } catch (Throwable e) { |
| // empty |
| } |
| throw new UnpackException(unpackError); |
| } |
| postReceive.onPostReceive(this, filterCommands(Result.OK)); |
| autoGc(); |
| } |
| } |
| |
| private void autoGc() { |
| Repository repo = getRepository(); |
| if (!repo.getConfig().getBoolean(ConfigConstants.CONFIG_RECEIVE_SECTION, |
| ConfigConstants.CONFIG_KEY_AUTOGC, true)) { |
| return; |
| } |
| repo.autoGC(NullProgressMonitor.INSTANCE); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| protected String getLockMessageProcessName() { |
| return "jgit receive-pack"; //$NON-NLS-1$ |
| } |
| } |