/* * 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.BasePackPushConnection.CAPABILITY_DELETE_REFS; import static org.eclipse.jgit.transport.BasePackPushConnection.CAPABILITY_OFS_DELTA; import static org.eclipse.jgit.transport.BasePackPushConnection.CAPABILITY_REPORT_STATUS; import static org.eclipse.jgit.transport.BasePackPushConnection.CAPABILITY_SIDE_BAND_64K; import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA; import static org.eclipse.jgit.transport.SideBandOutputStream.CH_PROGRESS; import static org.eclipse.jgit.transport.SideBandOutputStream.MAX_BUF; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.UnpackException; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdSubclassMap; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.PackLock; import org.eclipse.jgit.transport.ReceiveCommand.Result; import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; import org.eclipse.jgit.util.io.InterruptTimer; import org.eclipse.jgit.util.io.TimeoutInputStream; import org.eclipse.jgit.util.io.TimeoutOutputStream; /** * Implements the server side of a push connection, receiving objects. */ public class ReceivePack { /** Database we write the stored objects into. */ private final Repository db; /** Revision traversal support over {@link #db}. */ private final RevWalk walk; /** * Is the client connection a bi-directional socket or pipe? *
* If true, this class assumes it can perform multiple read and write cycles * with the client over the input and output streams. This matches the * functionality available with a standard TCP/IP connection, or a local * operating system or in-memory pipe. *
* If false, this class runs in a read everything then output results mode,
* making it suitable for single round-trip systems RPCs such as HTTP.
*/
private boolean biDirectionalPipe = true;
/** Should an incoming transfer validate objects? */
private boolean checkReceivedObjects;
/** Should an incoming transfer permit create requests? */
private boolean allowCreates;
/** Should an incoming transfer permit delete requests? */
private boolean allowDeletes;
/** Should an incoming transfer permit non-fast-forward requests? */
private boolean allowNonFastForwards;
private boolean allowOfsDelta;
/** Identity to record action as within the reflog. */
private PersonIdent refLogIdent;
/** Filter used while advertising the refs to the client. */
private RefFilter refFilter;
/** Hook to validate the update commands before execution. */
private PreReceiveHook preReceive;
/** Hook to report on the commands after execution. */
private PostReceiveHook postReceive;
/** Timeout in seconds to wait for client interaction. */
private int timeout;
/** Timer to manage {@link #timeout}. */
private InterruptTimer timer;
private TimeoutInputStream timeoutIn;
private InputStream rawIn;
private OutputStream rawOut;
private PacketLineIn pckIn;
private PacketLineOut pckOut;
private Writer msgs;
private IndexPack ip;
/** The refs we advertised as existing at the start of the connection. */
private Map
* If enabled, this instance will verify that references to objects not
* contained within the received pack are already reachable through at least
* one other reference selected by the {@link #getRefFilter()} and displayed
* as part of {@link #getAdvertisedRefs()}.
*
* This feature is useful when the application doesn't trust the client to
* not provide a forged SHA-1 reference to an object, in an attempt to
* access parts of the DAG that they aren't allowed to see and which have
* been hidden from them via the configured {@link RefFilter}.
*
* Enabling this feature may imply at least some, if not all, of the same
* functionality performed by {@link #setCheckReceivedObjects(boolean)}.
* Applications are encouraged to enable both features, if desired.
*
* @param b
* {@code true} to enable the additional check.
*/
public void setCheckReferencedObjectsAreReachable(boolean b) {
this.checkReferencedIsReachable = b;
}
/**
* @return true if this class expects a bi-directional pipe opened between
* the client and itself. The default is true.
*/
public boolean isBiDirectionalPipe() {
return biDirectionalPipe;
}
/**
* @param twoWay
* if true, this class will assume the socket is a fully
* bidirectional pipe between the two peers and takes advantage
* of that by first transmitting the known refs, then waiting to
* read commands. If false, this class assumes it must read the
* commands before writing output and does not perform the
* initial advertising.
*/
public void setBiDirectionalPipe(final boolean twoWay) {
biDirectionalPipe = twoWay;
}
/**
* @return true if this instance will verify received objects are formatted
* correctly. Validating objects requires more CPU time on this side
* of the connection.
*/
public boolean isCheckReceivedObjects() {
return checkReceivedObjects;
}
/**
* @param check
* true to enable checking received objects; false to assume all
* received objects are valid.
*/
public void setCheckReceivedObjects(final boolean check) {
checkReceivedObjects = check;
}
/** @return true if the client can request refs to be created. */
public boolean isAllowCreates() {
return allowCreates;
}
/**
* @param canCreate
* true to permit create ref commands to be processed.
*/
public void setAllowCreates(final boolean canCreate) {
allowCreates = canCreate;
}
/** @return true if the client can request refs to be deleted. */
public boolean isAllowDeletes() {
return allowDeletes;
}
/**
* @param canDelete
* true to permit delete ref commands to be processed.
*/
public void setAllowDeletes(final boolean canDelete) {
allowDeletes = canDelete;
}
/**
* @return true if the client can request non-fast-forward updates of a ref,
* possibly making objects unreachable.
*/
public boolean isAllowNonFastForwards() {
return allowNonFastForwards;
}
/**
* @param canRewind
* true to permit the client to ask for non-fast-forward updates
* of an existing ref.
*/
public void setAllowNonFastForwards(final boolean canRewind) {
allowNonFastForwards = canRewind;
}
/** @return identity of the user making the changes in the reflog. */
public PersonIdent getRefLogIdent() {
return refLogIdent;
}
/**
* Set the identity of the user appearing in the affected reflogs.
*
* The timestamp portion of the identity is ignored. A new identity with the
* current timestamp will be created automatically when the updates occur
* and the log records are written.
*
* @param pi
* identity of the user. If null the identity will be
* automatically determined based on the repository
* configuration.
*/
public void setRefLogIdent(final PersonIdent pi) {
refLogIdent = pi;
}
/** @return the filter used while advertising the refs to the client */
public RefFilter getRefFilter() {
return refFilter;
}
/**
* Set the filter used while advertising the refs to the client.
*
* Only refs allowed by this filter will be shown to the client.
* Clients may still attempt to create or update a reference hidden
* by the configured {@link RefFilter}. These attempts should be
* rejected by a matching {@link PreReceiveHook}.
*
* @param refFilter
* the filter; may be null to show all refs.
*/
public void setRefFilter(final RefFilter refFilter) {
this.refFilter = refFilter != null ? refFilter : RefFilter.DEFAULT;
}
/** @return get the hook invoked before updates occur. */
public PreReceiveHook getPreReceiveHook() {
return preReceive;
}
/**
* Set the hook which is invoked prior to commands being executed.
*
* 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 Result#NOT_ATTEMPTED} to block its execution.
*
* 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(final PreReceiveHook h) {
preReceive = h != null ? h : PreReceiveHook.NULL;
}
/** @return get the hook invoked after updates occur. */
public PostReceiveHook getPostReceiveHook() {
return postReceive;
}
/**
* Set the hook which is invoked after commands are executed.
*
* Only successful commands (type is {@link 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(final PostReceiveHook h) {
postReceive = h != null ? h : PostReceiveHook.NULL;
}
/** @return timeout (in seconds) before aborting an IO operation. */
public int getTimeout() {
return timeout;
}
/**
* Set the timeout before willing to abort an IO call.
*
* @param seconds
* number of seconds to wait (with no data transfer occurring)
* before aborting an IO read or write operation with the
* connected client.
*/
public void setTimeout(final int seconds) {
timeout = seconds;
}
/** @return all of the command received by the current request. */
public List
* If any error messages are sent before the references are advertised to
* the client, the errors will be sent instead of the advertisement and the
* receive operation will be aborted. All clients should receive and display
* such early stage errors.
*
* If the reference advertisements have already been sent, messages are sent
* in a side channel. If the client doesn't support receiving messages, the
* message will be discarded, with no other indication to the caller or to
* the client.
*
* {@link PreReceiveHook}s should always try to use
* {@link ReceiveCommand#setResult(Result, String)} with a result status of
* {@link Result#REJECTED_OTHER_REASON} to indicate any reasons for
* rejecting an update. Messages attached to a command are much more likely
* to be returned to the client.
*
* @param what
* string describing the problem identified by the hook. The
* string must not end with an LF, and must not contain an LF.
*/
public void sendError(final String what) {
if (refs == null) {
if (advertiseError == null)
advertiseError = new StringBuilder();
advertiseError.append(what).append('\n');
} else {
try {
if (msgs != null)
msgs.write("error: " + what + "\n");
} catch (IOException e) {
// Ignore write failures.
}
}
}
/**
* Send a message to the client, if it supports receiving them.
*
* If the client doesn't support receiving messages, the message will be
* discarded, with no other indication to the caller or to the client.
*
* @param what
* string describing the problem identified by the hook. The
* string must not end with an LF, and must not contain an LF.
*/
public void sendMessage(final String what) {
try {
if (msgs != null)
msgs.write(what + "\n");
} catch (IOException e) {
// Ignore write failures.
}
}
/**
* 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 IOException
*/
public void receive(final InputStream input, final OutputStream output,
final OutputStream messages) throws IOException {
try {
rawIn = input;
rawOut = output;
if (timeout > 0) {
final Thread caller = Thread.currentThread();
timer = new InterruptTimer(caller.getName() + "-Timer");
timeoutIn = new TimeoutInputStream(rawIn, timer);
TimeoutOutputStream o = new TimeoutOutputStream(rawOut, timer);
timeoutIn.setTimeout(timeout * 1000);
o.setTimeout(timeout * 1000);
rawIn = timeoutIn;
rawOut = o;
}
pckIn = new PacketLineIn(rawIn);
pckOut = new PacketLineOut(rawOut);
if (messages != null)
msgs = new OutputStreamWriter(messages, Constants.CHARSET);
enabledCapablities = new HashSet