From cc37cb777e57f6a5a8e0068945d322ac0f6834e2 Mon Sep 17 00:00:00 2001 From: Dave Borowitz Date: Thu, 3 May 2012 16:44:53 -0700 Subject: [PATCH] Factor a base class out of ReceivePack We are working on a publish/subscribe based git protocol, and we want to reuse several parts of the ReceivePack-like code for reading commands and processing a pack. In this new implementation, the connection management will be very different, in particular, there may be multiple packs received on a single open connection. So, hoist out as much as we can from ReceivePack, mostly just leaving behind the single-connection version in that class. Change-Id: I5567aad6ae77951f73f59c1f91996d934ea88334 --- .../transport/AbstractAdvertiseRefsHook.java | 2 +- .../jgit/transport/AdvertiseRefsHook.java | 8 +- .../transport/AdvertiseRefsHookChain.java | 6 +- .../jgit/transport/BaseReceivePack.java | 1294 +++++++++++++++++ .../jgit/transport/ReceiveCommand.java | 8 +- .../eclipse/jgit/transport/ReceivePack.java | 1136 +-------------- 6 files changed, 1322 insertions(+), 1132 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java index a13a4ff93..2d730a10f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java @@ -63,7 +63,7 @@ public abstract class AbstractAdvertiseRefsHook implements AdvertiseRefsHook { uploadPack.getRepository(), uploadPack.getRevWalk())); } - public void advertiseRefs(ReceivePack receivePack) + public void advertiseRefs(BaseReceivePack receivePack) throws ServiceMayNotContinueException { Map refs = getAdvertisedRefs(receivePack.getRepository(), receivePack.getRevWalk()); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java index 0f76f6752..c3af74a7c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java @@ -53,14 +53,14 @@ public interface AdvertiseRefsHook { *

* The method implementations do nothing to preserve the default behavior; see * {@link UploadPack#setAdvertisedRefs(java.util.Map)} and - * {@link ReceivePack#setAdvertisedRefs(java.util.Map,java.util.Set)}. + * {@link BaseReceivePack#setAdvertisedRefs(java.util.Map,java.util.Set)}. */ public static final AdvertiseRefsHook DEFAULT = new AdvertiseRefsHook() { public void advertiseRefs(UploadPack uploadPack) { // Do nothing. } - public void advertiseRefs(ReceivePack receivePack) { + public void advertiseRefs(BaseReceivePack receivePack) { // Do nothing. } }; @@ -81,11 +81,11 @@ public interface AdvertiseRefsHook { * Advertise refs for receive-pack. * * @param receivePack instance on which to call - * {@link ReceivePack#setAdvertisedRefs(java.util.Map,java.util.Set)} + * {@link BaseReceivePack#setAdvertisedRefs(java.util.Map,java.util.Set)} * if necessary. * @throws ServiceMayNotContinueException * abort; the message will be sent to the user. */ - public void advertiseRefs(ReceivePack receivePack) + public void advertiseRefs(BaseReceivePack receivePack) throws ServiceMayNotContinueException; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java index ec1e62d23..00942aba4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java @@ -51,8 +51,8 @@ import java.util.List; * Hooks are run in the order passed to the constructor. A hook may inspect or * modify the results of the previous hooks in the chain by calling * {@link UploadPack#getAdvertisedRefs()}, or - * {@link ReceivePack#getAdvertisedRefs()} or - * {@link ReceivePack#getAdvertisedObjects()}. + * {@link BaseReceivePack#getAdvertisedRefs()} or + * {@link BaseReceivePack#getAdvertisedObjects()}. */ public class AdvertiseRefsHookChain implements AdvertiseRefsHook { private final AdvertiseRefsHook[] hooks; @@ -79,7 +79,7 @@ public class AdvertiseRefsHookChain implements AdvertiseRefsHook { return new AdvertiseRefsHookChain(newHooks, i); } - public void advertiseRefs(ReceivePack rp) + public void advertiseRefs(BaseReceivePack rp) throws ServiceMayNotContinueException { for (int i = 0; i < count; i++) hooks[i].advertiseRefs(rp); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java new file mode 100644 index 000000000..c0d44b1db --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java @@ -0,0 +1,1294 @@ +/* + * 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.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 java.util.concurrent.TimeUnit; + +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.PackProtocolException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.Config.SectionParser; +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.ObjectInserter; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +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.RevSort; +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.util.io.InterruptTimer; +import org.eclipse.jgit.util.io.TimeoutInputStream; +import org.eclipse.jgit.util.io.TimeoutOutputStream; + +/** + * Base implementation of the side of a push connection that receives objects. + *

+ * Contains high-level operations for initializing and closing streams, + * advertising refs, reading commands, and receiving and applying a pack. + * Subclasses compose these operations into full service implementations. + */ +public abstract class BaseReceivePack { + /** Data in the first line of a request, the line itself plus capabilities. */ + public static class FirstLine { + private final String line; + private final Set capabilities; + + /** + * Parse the first line of a receive-pack request. + * + * @param line + * line from the client. + */ + public FirstLine(String line) { + final HashSet caps = new HashSet(); + final int nul = line.indexOf('\0'); + if (nul >= 0) { + for (String c : line.substring(nul + 1).split(" ")) + caps.add(c); + this.line = line.substring(0, nul); + } else + this.line = line; + this.capabilities = Collections.unmodifiableSet(caps); + } + + /** @return non-capabilities part of the line. */ + public String getLine() { + return line; + } + + /** @return capabilities parsed from the line. */ + public Set getCapabilities() { + return capabilities; + } + } + + /** Database we write the stored objects into. */ + protected final Repository db; + + /** Revision traversal support over {@link #db}. */ + protected 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. + */ + protected boolean biDirectionalPipe = true; + + /** Should an incoming transfer validate objects? */ + protected boolean checkReceivedObjects; + + /** Should an incoming transfer permit create requests? */ + protected boolean allowCreates; + + /** Should an incoming transfer permit delete requests? */ + protected boolean allowDeletes; + + /** Should an incoming transfer permit non-fast-forward requests? */ + protected boolean allowNonFastForwards; + + private boolean allowOfsDelta; + + /** Identity to record action as within the reflog. */ + private PersonIdent refLogIdent; + + /** Hook used while advertising the refs to the client. */ + private AdvertiseRefsHook advertiseRefsHook; + + /** Filter used while advertising the refs to the client. */ + private RefFilter refFilter; + + /** Timeout in seconds to wait for client interaction. */ + private int timeout; + + /** Timer to manage {@link #timeout}. */ + private InterruptTimer timer; + + private TimeoutInputStream timeoutIn; + + // Original stream passed to init(), since rawOut may be wrapped in a + // sideband. + private OutputStream origOut; + + /** Raw input stream. */ + protected InputStream rawIn; + + /** Raw output stream. */ + protected OutputStream rawOut; + + /** Optional message output stream. */ + protected OutputStream msgOut; + + /** Packet line input stream around {@link #rawIn}. */ + protected PacketLineIn pckIn; + + /** Packet line output stream around {@link #rawOut}. */ + protected PacketLineOut pckOut; + + private final MessageOutputWrapper msgOutWrapper = new MessageOutputWrapper(); + + private PackParser parser; + + /** The refs we advertised as existing at the start of the connection. */ + protected Map refs; + + /** All SHA-1s shown to the client, which can be possible edges. */ + protected Set advertisedHaves; + + /** Capabilities requested by the client. */ + protected Set enabledCapabilities; + + private List commands; + + private StringBuilder advertiseError; + + /** If {@link BasePackPushConnection#CAPABILITY_REPORT_STATUS} is enabled. */ + protected boolean reportStatus; + + /** If {@link BasePackPushConnection#CAPABILITY_SIDE_BAND_64K} is enabled. */ + protected boolean sideBand; + + /** Lock around the received pack file, while updating refs. */ + private PackLock packLock; + + private boolean checkReferencedIsReachable; + + /** Git object size limit */ + private long maxObjectSizeLimit; + + /** + * Create a new pack receive for an open repository. + * + * @param into + * the destination repository. + */ + protected BaseReceivePack(final Repository into) { + db = into; + walk = new RevWalk(db); + + final ReceiveConfig cfg = db.getConfig().get(ReceiveConfig.KEY); + checkReceivedObjects = cfg.checkReceivedObjects; + allowCreates = cfg.allowCreates; + allowDeletes = cfg.allowDeletes; + allowNonFastForwards = cfg.allowNonFastForwards; + allowOfsDelta = cfg.allowOfsDelta; + advertiseRefsHook = AdvertiseRefsHook.DEFAULT; + refFilter = RefFilter.DEFAULT; + advertisedHaves = new HashSet(); + } + + /** Configuration for receive operations. */ + protected static class ReceiveConfig { + static final SectionParser KEY = new SectionParser() { + public ReceiveConfig parse(final Config cfg) { + return new ReceiveConfig(cfg); + } + }; + + final boolean checkReceivedObjects; + + final boolean allowCreates; + + final boolean allowDeletes; + + final boolean allowNonFastForwards; + + final boolean allowOfsDelta; + + ReceiveConfig(final Config config) { + checkReceivedObjects = config.getBoolean("receive", "fsckobjects", + false); + allowCreates = true; + allowDeletes = !config.getBoolean("receive", "denydeletes", false); + allowNonFastForwards = !config.getBoolean("receive", + "denynonfastforwards", false); + allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset", + true); + } + } + + /** + * Output stream that wraps the current {@link #msgOut}. + *

+ * We don't want to expose {@link #msgOut} directly because it can change + * several times over the course of a session. + */ + class MessageOutputWrapper extends OutputStream { + @Override + public void write(int ch) { + if (msgOut != null) { + try { + msgOut.write(ch); + } catch (IOException e) { + // Ignore write failures. + } + } + } + + @Override + public void write(byte[] b, int off, int len) { + if (msgOut != null) { + try { + msgOut.write(b, off, len); + } catch (IOException e) { + // Ignore write failures. + } + } + } + + @Override + public void write(byte[] b) { + write(b, 0, b.length); + } + + @Override + public void flush() { + if (msgOut != null) { + try { + msgOut.flush(); + } catch (IOException e) { + // Ignore write failures. + } + } + } + } + + /** @return the process name used for pack lock messages. */ + protected abstract String getLockMessageProcessName(); + + /** @return the repository this receive completes into. */ + public final Repository getRepository() { + return db; + } + + /** @return the RevWalk instance used by this connection. */ + public final RevWalk getRevWalk() { + return walk; + } + + /** + * Get refs which were advertised to the client. + * + * @return all refs which were advertised to the client, or null if + * {@link #setAdvertisedRefs(Map, Set)} has not been called yet. + */ + public final Map getAdvertisedRefs() { + return refs; + } + + /** + * Set the refs advertised by this ReceivePack. + *

+ * Intended to be called from a {@link PreReceiveHook}. + * + * @param allRefs + * explicit set of references to claim as advertised by this + * ReceivePack instance. This overrides any references that + * may exist in the source repository. The map is passed + * to the configured {@link #getRefFilter()}. If null, assumes + * all refs were advertised. + * @param additionalHaves + * explicit set of additional haves to claim as advertised. If + * null, assumes the default set of additional haves from the + * repository. + */ + public void setAdvertisedRefs(Map allRefs, Set additionalHaves) { + refs = allRefs != null ? allRefs : db.getAllRefs(); + refs = refFilter.filter(refs); + + Ref head = refs.get(Constants.HEAD); + if (head != null && head.isSymbolic()) + refs.remove(Constants.HEAD); + + for (Ref ref : refs.values()) { + if (ref.getObjectId() != null) + advertisedHaves.add(ref.getObjectId()); + } + if (additionalHaves != null) + advertisedHaves.addAll(additionalHaves); + else + advertisedHaves.addAll(db.getAdditionalHaves()); + } + + /** + * Get objects advertised to the client. + * + * @return the set of objects advertised to the as present in this repository, + * or null if {@link #setAdvertisedRefs(Map, Set)} has not been called + * yet. + */ + public final Set getAdvertisedObjects() { + return advertisedHaves; + } + + /** + * @return true if this instance will validate all referenced, but not + * supplied by the client, objects are reachable from another + * reference. + */ + public boolean isCheckReferencedObjectsAreReachable() { + return checkReferencedIsReachable; + } + + /** + * Validate all referenced but not supplied objects are reachable. + *

+ * 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 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 AdvertiseRefsHook} or + * {@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 hook used while advertising the refs to the client */ + public AdvertiseRefsHook getAdvertiseRefsHook() { + return advertiseRefsHook; + } + + /** @return the filter used while advertising the refs to the client */ + public RefFilter getRefFilter() { + return refFilter; + } + + /** + * Set the hook used while advertising the refs to the client. + *

+ * If the {@link AdvertiseRefsHook} chooses to call + * {@link #setAdvertisedRefs(Map,Set)}, only refs set by this hook + * and selected by the {@link RefFilter} will be shown to the client. + * Clients may still attempt to create or update a reference not advertised by + * the configured {@link AdvertiseRefsHook}. These attempts should be rejected + * by a matching {@link PreReceiveHook}. + * + * @param advertiseRefsHook + * the hook; may be null to show all refs. + */ + public void setAdvertiseRefsHook(final AdvertiseRefsHook advertiseRefsHook) { + if (advertiseRefsHook != null) + this.advertiseRefsHook = advertiseRefsHook; + else + this.advertiseRefsHook = AdvertiseRefsHook.DEFAULT; + } + + /** + * Set the filter used while advertising the refs to the client. + *

+ * Only refs allowed by this filter will be shown to the client. + * The filter is run against the refs specified by the + * {@link AdvertiseRefsHook} (if applicable). + * + * @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 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; + } + + /** + * Set the maximum allowed Git object size. + *

+ * If an object is larger than the given size the pack-parsing will throw an + * exception aborting the receive-pack operation. + * + * @param limit + * the Git object size limit. If zero then there is not limit. + */ + public void setMaxObjectSizeLimit(final long limit) { + maxObjectSizeLimit = limit; + } + + /** + * Check whether the client expects a side-band stream. + * + * @return true if the client has advertised a side-band capability, false + * otherwise. + * @throws RequestNotYetReadException + * if the client's request has not yet been read from the wire, so + * we do not know if they expect side-band. Note that the client + * may have already written the request, it just has not been + * read. + */ + public boolean isSideBand() throws RequestNotYetReadException { + if (enabledCapabilities == null) + throw new RequestNotYetReadException(); + return enabledCapabilities.contains(CAPABILITY_SIDE_BAND_64K); + } + + /** @return all of the command received by the current request. */ + public List getAllCommands() { + return Collections.unmodifiableList(commands); + } + + /** + * Send an error message to the client. + *

+ * 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 { + msgOutWrapper.write(Constants.encode("error: " + what + "\n")); + } + } + + /** + * 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) { + msgOutWrapper.write(Constants.encode(what + "\n")); + } + + /** @return an underlying stream for sending messages to the client. */ + public OutputStream getMessageOutputStream() { + return msgOutWrapper; + } + + /** @return true if any commands to be executed have been read. */ + protected boolean hasCommands() { + return !commands.isEmpty(); + } + + /** @return true if an error occurred that should be advertised. */ + protected boolean hasError() { + return advertiseError != null; + } + + /** + * Initialize the instance with the given streams. + * + * @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. + */ + protected void init(final InputStream input, final OutputStream output, + final OutputStream messages) { + origOut = output; + rawIn = input; + rawOut = output; + msgOut = messages; + + 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); + pckOut.setFlushOnEnd(false); + + enabledCapabilities = new HashSet(); + commands = new ArrayList(); + } + + /** @return advertised refs, or the default if not explicitly advertised. */ + protected Map getAdvertisedOrDefaultRefs() { + if (refs == null) + setAdvertisedRefs(null, null); + return refs; + } + + /** + * Receive a pack from the stream and check connectivity if necessary. + * + * @throws IOException + * an error occurred during unpacking or connectivity checking. + */ + protected void receivePackAndCheckConnectivity() throws IOException { + receivePack(); + if (needCheckConnectivity()) + checkConnectivity(); + parser = null; + } + + /** + * Unlock the pack written by this object. + * + * @throws IOException + * the pack could not be unlocked. + */ + protected void unlockPack() throws IOException { + if (packLock != null) { + packLock.unlock(); + packLock = null; + } + } + + /** + * Generate an advertisement of available refs and capabilities. + * + * @param adv + * the advertisement formatter. + * @throws IOException + * the formatter failed to write an advertisement. + * @throws ServiceMayNotContinueException + * the hook denied advertisement. + */ + public void sendAdvertisedRefs(final RefAdvertiser adv) + throws IOException, ServiceMayNotContinueException { + if (advertiseError != null) { + adv.writeOne("ERR " + advertiseError); + return; + } + + try { + advertiseRefsHook.advertiseRefs(this); + } catch (ServiceMayNotContinueException fail) { + if (fail.getMessage() != null) { + adv.writeOne("ERR " + fail.getMessage()); + fail.setOutput(); + } + throw fail; + } + + adv.init(db); + adv.advertiseCapability(CAPABILITY_SIDE_BAND_64K); + adv.advertiseCapability(CAPABILITY_DELETE_REFS); + adv.advertiseCapability(CAPABILITY_REPORT_STATUS); + if (allowOfsDelta) + adv.advertiseCapability(CAPABILITY_OFS_DELTA); + adv.send(getAdvertisedOrDefaultRefs()); + for (ObjectId obj : advertisedHaves) + adv.advertiseHave(obj); + if (adv.isEmpty()) + adv.advertiseId(ObjectId.zeroId(), "capabilities^{}"); + adv.end(); + } + + /** + * Receive a list of commands from the input. + * + * @throws IOException + */ + protected void recvCommands() throws IOException { + for (;;) { + String line; + try { + line = pckIn.readStringRaw(); + } catch (EOFException eof) { + if (commands.isEmpty()) + return; + throw eof; + } + if (line == PacketLineIn.END) + break; + + if (commands.isEmpty()) { + final FirstLine firstLine = new FirstLine(line); + enabledCapabilities = firstLine.getCapabilities(); + line = firstLine.getLine(); + } + + if (line.length() < 83) { + final String m = JGitText.get().errorInvalidProtocolWantedOldNewRef; + sendError(m); + throw new PackProtocolException(m); + } + + final ObjectId oldId = ObjectId.fromString(line.substring(0, 40)); + final ObjectId newId = ObjectId.fromString(line.substring(41, 81)); + final String name = line.substring(82); + final ReceiveCommand cmd = new ReceiveCommand(oldId, newId, name); + if (name.equals(Constants.HEAD)) { + cmd.setResult(Result.REJECTED_CURRENT_BRANCH); + } else { + cmd.setRef(refs.get(cmd.getRefName())); + } + commands.add(cmd); + } + } + + /** Enable capabilities based on a previously read capabilities line. */ + protected void enableCapabilities() { + reportStatus = enabledCapabilities.contains(CAPABILITY_REPORT_STATUS); + + sideBand = enabledCapabilities.contains(CAPABILITY_SIDE_BAND_64K); + if (sideBand) { + OutputStream out = rawOut; + + rawOut = new SideBandOutputStream(CH_DATA, MAX_BUF, out); + msgOut = new SideBandOutputStream(CH_PROGRESS, MAX_BUF, out); + + pckOut = new PacketLineOut(rawOut); + pckOut.setFlushOnEnd(false); + } + } + + /** @return true if a pack is expected based on the list of commands. */ + protected boolean needPack() { + for (final ReceiveCommand cmd : commands) { + if (cmd.getType() != ReceiveCommand.Type.DELETE) + return true; + } + return false; + } + + /** + * Receive a pack from the input and store it in the repository. + * + * @throws IOException + * an error occurred reading or indexing the pack. + */ + private void receivePack() throws IOException { + // It might take the client a while to pack the objects it needs + // to send to us. We should increase our timeout so we don't + // abort while the client is computing. + // + if (timeoutIn != null) + timeoutIn.setTimeout(10 * timeout * 1000); + + ProgressMonitor receiving = NullProgressMonitor.INSTANCE; + ProgressMonitor resolving = NullProgressMonitor.INSTANCE; + if (sideBand) + resolving = new SideBandProgressMonitor(msgOut); + + ObjectInserter ins = db.newObjectInserter(); + try { + String lockMsg = "jgit receive-pack"; + if (getRefLogIdent() != null) + lockMsg += " from " + getRefLogIdent().toExternalString(); + + parser = ins.newPackParser(rawIn); + parser.setAllowThin(true); + parser.setNeedNewObjectIds(checkReferencedIsReachable); + parser.setNeedBaseObjectIds(checkReferencedIsReachable); + parser.setCheckEofAfterPackFooter(!biDirectionalPipe); + parser.setObjectChecking(isCheckReceivedObjects()); + parser.setLockMessage(lockMsg); + parser.setMaxObjectSizeLimit(maxObjectSizeLimit); + packLock = parser.parse(receiving, resolving); + ins.flush(); + } finally { + ins.release(); + } + + if (timeoutIn != null) + timeoutIn.setTimeout(timeout * 1000); + } + + private boolean needCheckConnectivity() { + return isCheckReceivedObjects() + || isCheckReferencedObjectsAreReachable(); + } + + private void checkConnectivity() throws IOException { + ObjectIdSubclassMap baseObjects = null; + ObjectIdSubclassMap providedObjects = null; + + if (checkReferencedIsReachable) { + baseObjects = parser.getBaseObjectIds(); + providedObjects = parser.getNewObjectIds(); + } + parser = null; + + final ObjectWalk ow = new ObjectWalk(db); + ow.setRetainBody(false); + if (checkReferencedIsReachable) { + ow.sort(RevSort.TOPO); + if (!baseObjects.isEmpty()) + ow.sort(RevSort.BOUNDARY, true); + } + + for (final ReceiveCommand cmd : commands) { + if (cmd.getResult() != Result.NOT_ATTEMPTED) + continue; + if (cmd.getType() == ReceiveCommand.Type.DELETE) + continue; + ow.markStart(ow.parseAny(cmd.getNewId())); + } + for (final ObjectId have : advertisedHaves) { + RevObject o = ow.parseAny(have); + ow.markUninteresting(o); + + if (checkReferencedIsReachable && !baseObjects.isEmpty()) { + o = ow.peel(o); + if (o instanceof RevCommit) + o = ((RevCommit) o).getTree(); + if (o instanceof RevTree) + ow.markUninteresting(o); + } + } + + RevCommit c; + while ((c = ow.next()) != null) { + if (checkReferencedIsReachable // + && !c.has(RevFlag.UNINTERESTING) // + && !providedObjects.contains(c)) + throw new MissingObjectException(c, Constants.TYPE_COMMIT); + } + + RevObject o; + while ((o = ow.nextObject()) != null) { + if (o.has(RevFlag.UNINTERESTING)) + continue; + + if (checkReferencedIsReachable) { + if (providedObjects.contains(o)) + continue; + else + throw new MissingObjectException(o, o.getType()); + } + + if (o instanceof RevBlob && !db.hasObject(o)) + throw new MissingObjectException(o, Constants.TYPE_BLOB); + } + + if (checkReferencedIsReachable) { + for (ObjectId id : baseObjects) { + o = ow.parseAny(id); + if (!o.has(RevFlag.UNINTERESTING)) + throw new MissingObjectException(o, o.getType()); + } + } + } + + /** Validate the command list. */ + protected void validateCommands() { + for (final ReceiveCommand cmd : commands) { + final Ref ref = cmd.getRef(); + if (cmd.getResult() != Result.NOT_ATTEMPTED) + continue; + + if (cmd.getType() == ReceiveCommand.Type.DELETE + && !isAllowDeletes()) { + // Deletes are not supported on this repository. + // + cmd.setResult(Result.REJECTED_NODELETE); + continue; + } + + if (cmd.getType() == ReceiveCommand.Type.CREATE) { + if (!isAllowCreates()) { + cmd.setResult(Result.REJECTED_NOCREATE); + continue; + } + + if (ref != null && !isAllowNonFastForwards()) { + // Creation over an existing ref is certainly not going + // to be a fast-forward update. We can reject it early. + // + cmd.setResult(Result.REJECTED_NONFASTFORWARD); + continue; + } + + if (ref != null) { + // A well behaved client shouldn't have sent us a + // create command for a ref we advertised to it. + // + cmd.setResult(Result.REJECTED_OTHER_REASON, MessageFormat + .format(JGitText.get().refAlreadyExists, ref)); + continue; + } + } + + if (cmd.getType() == ReceiveCommand.Type.DELETE && ref != null + && !ObjectId.zeroId().equals(cmd.getOldId()) + && !ref.getObjectId().equals(cmd.getOldId())) { + // Delete commands can be sent with the old id matching our + // advertised value, *OR* with the old id being 0{40}. Any + // other requested old id is invalid. + // + cmd.setResult(Result.REJECTED_OTHER_REASON, + JGitText.get().invalidOldIdSent); + continue; + } + + if (cmd.getType() == ReceiveCommand.Type.UPDATE) { + if (ref == null) { + // The ref must have been advertised in order to be updated. + // + cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().noSuchRef); + continue; + } + + if (!ref.getObjectId().equals(cmd.getOldId())) { + // A properly functioning client will send the same + // object id we advertised. + // + cmd.setResult(Result.REJECTED_OTHER_REASON, + JGitText.get().invalidOldIdSent); + continue; + } + + // Is this possibly a non-fast-forward style update? + // + RevObject oldObj, newObj; + try { + oldObj = walk.parseAny(cmd.getOldId()); + } catch (IOException e) { + cmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd + .getOldId().name()); + continue; + } + + try { + newObj = walk.parseAny(cmd.getNewId()); + } catch (IOException e) { + cmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd + .getNewId().name()); + continue; + } + + if (oldObj instanceof RevCommit && newObj instanceof RevCommit) { + try { + if (!walk.isMergedInto((RevCommit) oldObj, + (RevCommit) newObj)) { + cmd + .setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD); + } + } catch (MissingObjectException e) { + cmd.setResult(Result.REJECTED_MISSING_OBJECT, e + .getMessage()); + } catch (IOException e) { + cmd.setResult(Result.REJECTED_OTHER_REASON); + } + } else { + cmd.setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD); + } + } + + if (!cmd.getRefName().startsWith(Constants.R_REFS) + || !Repository.isValidRefName(cmd.getRefName())) { + cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().funnyRefname); + } + } + } + + /** + * Filter the list of commands according to result. + * + * @param want + * desired status to filter by. + * @return a copy of the command list containing only those commands with the + * desired status. + */ + protected List filterCommands(final Result want) { + return ReceiveCommand.filter(commands, want); + } + + /** Execute commands to update references. */ + protected void executeCommands() { + List toApply = ReceiveCommand.filter(commands, + Result.NOT_ATTEMPTED); + ProgressMonitor updating = NullProgressMonitor.INSTANCE; + if (sideBand) { + SideBandProgressMonitor pm = new SideBandProgressMonitor(msgOut); + pm.setDelayStart(250, TimeUnit.MILLISECONDS); + updating = pm; + } + updating.beginTask(JGitText.get().updatingReferences, toApply.size()); + for (ReceiveCommand cmd : toApply) { + updating.update(1); + cmd.execute(this); + } + updating.endTask(); + } + + /** + * Send a status report. + * + * @param forClient + * true if this report is for a Git client, false if it is for an + * end-user. + * @param unpackError + * an error that occurred during unpacking, or {@code null} + * @param out + * the reporter for sending the status strings. + * @throws IOException + * an error occurred writing the status report. + */ + protected void sendStatusReport(final boolean forClient, + final Throwable unpackError, final Reporter out) throws IOException { + if (unpackError != null) { + out.sendString("unpack error " + unpackError.getMessage()); + if (forClient) { + for (final ReceiveCommand cmd : commands) { + out.sendString("ng " + cmd.getRefName() + + " n/a (unpacker error)"); + } + } + return; + } + + if (forClient) + out.sendString("unpack ok"); + for (final ReceiveCommand cmd : commands) { + if (cmd.getResult() == Result.OK) { + if (forClient) + out.sendString("ok " + cmd.getRefName()); + continue; + } + + final StringBuilder r = new StringBuilder(); + r.append("ng "); + r.append(cmd.getRefName()); + r.append(" "); + + switch (cmd.getResult()) { + case NOT_ATTEMPTED: + r.append("server bug; ref not processed"); + break; + + case REJECTED_NOCREATE: + r.append("creation prohibited"); + break; + + case REJECTED_NODELETE: + r.append("deletion prohibited"); + break; + + case REJECTED_NONFASTFORWARD: + r.append("non-fast forward"); + break; + + case REJECTED_CURRENT_BRANCH: + r.append("branch is currently checked out"); + break; + + case REJECTED_MISSING_OBJECT: + if (cmd.getMessage() == null) + r.append("missing object(s)"); + else if (cmd.getMessage().length() == Constants.OBJECT_ID_STRING_LENGTH) + r.append("object " + cmd.getMessage() + " missing"); + else + r.append(cmd.getMessage()); + break; + + case REJECTED_OTHER_REASON: + if (cmd.getMessage() == null) + r.append("unspecified reason"); + else + r.append(cmd.getMessage()); + break; + + case LOCK_FAILURE: + r.append("failed to lock"); + break; + + case OK: + // We shouldn't have reached this case (see 'ok' case above). + continue; + } + out.sendString(r.toString()); + } + } + + /** + * Close and flush (if necessary) the underlying streams. + * + * @throws IOException + */ + protected void close() throws IOException { + if (sideBand) { + // If we are using side band, we need to send a final + // flush-pkt to tell the remote peer the side band is + // complete and it should stop decoding. We need to + // use the original output stream as rawOut is now the + // side band data channel. + // + ((SideBandOutputStream) msgOut).flushBuffer(); + ((SideBandOutputStream) rawOut).flushBuffer(); + + PacketLineOut plo = new PacketLineOut(origOut); + plo.setFlushOnEnd(false); + plo.end(); + } + + if (biDirectionalPipe) { + // If this was a native git connection, flush the pipe for + // the caller. For smart HTTP we don't do this flush and + // instead let the higher level HTTP servlet code do it. + // + if (!sideBand && msgOut != null) + msgOut.flush(); + rawOut.flush(); + } + } + + /** + * Release any resources used by this object. + * + * @throws IOException + * the pack could not be unlocked. + */ + protected void release() throws IOException { + walk.release(); + unlockPack(); + timeoutIn = null; + rawIn = null; + rawOut = null; + msgOut = null; + pckIn = null; + pckOut = null; + refs = null; + enabledCapabilities = null; + commands = null; + if (timer != null) { + try { + timer.terminate(); + } finally { + timer = null; + } + } + } + + /** Interface for reporting status messages. */ + static abstract class Reporter { + abstract void sendString(String s) throws IOException; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java index 44cf82916..96814c85a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java @@ -54,7 +54,7 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; /** - * A command being processed by {@link ReceivePack}. + * A command being processed by {@link BaseReceivePack}. *

* This command instance roughly translates to the server side representation of * the {@link RemoteRefUpdate} created by the client. @@ -158,7 +158,7 @@ public class ReceiveCommand { private String message; /** - * Create a new command for {@link ReceivePack}. + * Create a new command for {@link BaseReceivePack}. * * @param oldId * the old object id; must not be null. Use @@ -184,7 +184,7 @@ public class ReceiveCommand { } /** - * Create a new command for {@link ReceivePack}. + * Create a new command for {@link BaseReceivePack}. * * @param oldId * the old object id; must not be null. Use @@ -273,7 +273,7 @@ public class ReceiveCommand { * receive-pack session. * @since 2.0 */ - public void execute(final ReceivePack rp) { + public void execute(final BaseReceivePack rp) { try { final RefUpdate ru = rp.getRepository().updateRef(getRefName()); ru.setRefLogIdent(rp.getRefLogIdent()); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java index 126d74fc3..99e629a13 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java @@ -43,197 +43,26 @@ 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.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 java.util.concurrent.TimeUnit; -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.UnpackException; -import org.eclipse.jgit.internal.JGitText; -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.ObjectInserter; -import org.eclipse.jgit.lib.PersonIdent; -import org.eclipse.jgit.lib.ProgressMonitor; -import org.eclipse.jgit.lib.Ref; 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.RevSort; -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 { - /** Data in the first line of a request, the line itself plus capabilities. */ - public static class FirstLine { - private final String line; - private final Set capabilities; - - /** - * Parse the first line of a receive-pack request. - * - * @param line - * line from the client. - */ - public FirstLine(String line) { - final HashSet caps = new HashSet(); - final int nul = line.indexOf('\0'); - if (nul >= 0) { - for (String c : line.substring(nul + 1).split(" ")) - caps.add(c); - this.line = line.substring(0, nul); - } else - this.line = line; - this.capabilities = Collections.unmodifiableSet(caps); - } - - /** @return non-capabilities part of the line. */ - public String getLine() { - return line; - } - - /** @return capabilities parsed from the line. */ - public Set getCapabilities() { - return capabilities; - } - } - - /** 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; - - /** Hook used while advertising the refs to the client. */ - private AdvertiseRefsHook advertiseRefsHook; - - /** Filter used while advertising the refs to the client. */ - private RefFilter refFilter; - +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; - /** 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 OutputStream msgOut; - - private final MessageOutputWrapper msgOutWrapper = new MessageOutputWrapper(); - - private PacketLineIn pckIn; - - private PacketLineOut pckOut; - - private PackParser parser; - - /** The refs we advertised as existing at the start of the connection. */ - private Map refs; - - /** All SHA-1s shown to the client, which can be possible edges. */ - private Set advertisedHaves; - - /** Capabilities requested by the client. */ - private Set enabledCapabilities; - - /** Commands to execute, as received by the client. */ - private List commands; - - /** Error to display instead of advertising the references. */ - private StringBuilder advertiseError; - - /** An exception caught while unpacking and fsck'ing the objects. */ - private Throwable unpackError; - - /** If {@link BasePackPushConnection#CAPABILITY_REPORT_STATUS} is enabled. */ - private boolean reportStatus; - - /** If {@link BasePackPushConnection#CAPABILITY_SIDE_BAND_64K} is enabled. */ - private boolean sideBand; - - /** Lock around the received pack file, while updating refs. */ - private PackLock packLock; - - private boolean checkReferencedIsReachable; - - /** Git object size limit */ - private long maxObjectSizeLimit; - /** * Create a new pack receive for an open repository. * @@ -241,341 +70,9 @@ public class ReceivePack { * the destination repository. */ public ReceivePack(final Repository into) { - db = into; - walk = new RevWalk(db); - - final ReceiveConfig cfg = db.getConfig().get(ReceiveConfig.KEY); - checkReceivedObjects = cfg.checkReceivedObjects; - allowCreates = cfg.allowCreates; - allowDeletes = cfg.allowDeletes; - allowNonFastForwards = cfg.allowNonFastForwards; - allowOfsDelta = cfg.allowOfsDelta; - advertiseRefsHook = AdvertiseRefsHook.DEFAULT; - refFilter = RefFilter.DEFAULT; + super(into); preReceive = PreReceiveHook.NULL; postReceive = PostReceiveHook.NULL; - advertisedHaves = new HashSet(); - } - - private static class ReceiveConfig { - static final SectionParser KEY = new SectionParser() { - public ReceiveConfig parse(final Config cfg) { - return new ReceiveConfig(cfg); - } - }; - - final boolean checkReceivedObjects; - - final boolean allowCreates; - - final boolean allowDeletes; - - final boolean allowNonFastForwards; - - final boolean allowOfsDelta; - - ReceiveConfig(final Config config) { - checkReceivedObjects = config.getBoolean("receive", "fsckobjects", - false); - allowCreates = true; - allowDeletes = !config.getBoolean("receive", "denydeletes", false); - allowNonFastForwards = !config.getBoolean("receive", - "denynonfastforwards", false); - allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset", - true); - } - } - - /** - * Output stream that wraps the current {@link #msgOut}. - *

- * We don't want to expose {@link #msgOut} directly because it can change - * several times over the course of a session. - */ - private class MessageOutputWrapper extends OutputStream { - @Override - public void write(int ch) { - if (msgOut != null) { - try { - msgOut.write(ch); - } catch (IOException e) { - // Ignore write failures. - } - } - } - - @Override - public void write(byte[] b, int off, int len) { - if (msgOut != null) { - try { - msgOut.write(b, off, len); - } catch (IOException e) { - // Ignore write failures. - } - } - } - - @Override - public void write(byte[] b) { - write(b, 0, b.length); - } - - @Override - public void flush() { - if (msgOut != null) { - try { - msgOut.flush(); - } catch (IOException e) { - // Ignore write failures. - } - } - } - } - - /** @return the repository this receive completes into. */ - public final Repository getRepository() { - return db; - } - - /** @return the RevWalk instance used by this connection. */ - public final RevWalk getRevWalk() { - return walk; - } - - /** - * Get refs which were advertised to the client. - * - * @return all refs which were advertised to the client, or null if - * {@link #setAdvertisedRefs(Map, Set)} has not been called yet. - */ - public final Map getAdvertisedRefs() { - return refs; - } - - /** - * Set the refs advertised by this ReceivePack. - *

- * Intended to be called from a {@link PreReceiveHook}. - * - * @param allRefs - * explicit set of references to claim as advertised by this - * ReceivePack instance. This overrides any references that - * may exist in the source repository. The map is passed - * to the configured {@link #getRefFilter()}. If null, assumes - * all refs were advertised. - * @param additionalHaves - * explicit set of additional haves to claim as advertised. If - * null, assumes the default set of additional haves from the - * repository. - */ - public void setAdvertisedRefs(Map allRefs, - Set additionalHaves) { - refs = allRefs != null ? allRefs : db.getAllRefs(); - refs = refFilter.filter(refs); - - Ref head = refs.get(Constants.HEAD); - if (head != null && head.isSymbolic()) - refs.remove(Constants.HEAD); - - for (Ref ref : refs.values()) { - if (ref.getObjectId() != null) - advertisedHaves.add(ref.getObjectId()); - } - if (additionalHaves != null) - advertisedHaves.addAll(additionalHaves); - else - advertisedHaves.addAll(db.getAdditionalHaves()); - } - - /** - * Get objects advertised to the client. - * - * @return the set of objects advertised to the as present in this repository, - * or null if {@link #setAdvertisedRefs(Map, Set)} has not been called - * yet. - */ - public final Set getAdvertisedObjects() { - return advertisedHaves; - } - - /** - * @return true if this instance will validate all referenced, but not - * supplied by the client, objects are reachable from another - * reference. - */ - public boolean isCheckReferencedObjectsAreReachable() { - return checkReferencedIsReachable; - } - - /** - * Validate all referenced but not supplied objects are reachable. - *

- * 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 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 AdvertiseRefsHook} or - * {@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 hook used while advertising the refs to the client */ - public AdvertiseRefsHook getAdvertiseRefsHook() { - return advertiseRefsHook; - } - - /** @return the filter used while advertising the refs to the client */ - public RefFilter getRefFilter() { - return refFilter; - } - - /** - * Set the hook used while advertising the refs to the client. - *

- * If the {@link AdvertiseRefsHook} chooses to call - * {@link #setAdvertisedRefs(Map,Set)}, only refs set by this hook - * and selected by the {@link RefFilter} will be shown to the client. - * Clients may still attempt to create or update a reference not advertised by - * the configured {@link AdvertiseRefsHook}. These attempts should be rejected - * by a matching {@link PreReceiveHook}. - * - * @param advertiseRefsHook - * the hook; may be null to show all refs. - */ - public void setAdvertiseRefsHook(final AdvertiseRefsHook advertiseRefsHook) { - if (advertiseRefsHook != null) - this.advertiseRefsHook = advertiseRefsHook; - else - this.advertiseRefsHook = AdvertiseRefsHook.DEFAULT; - } - - /** - * Set the filter used while advertising the refs to the client. - *

- * Only refs allowed by this filter will be shown to the client. - * The filter is run against the refs specified by the - * {@link AdvertiseRefsHook} (if applicable). - * - * @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 the hook invoked before updates occur. */ @@ -620,110 +117,6 @@ public class ReceivePack { 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; - } - - /** - * Set the maximum allowed Git object size. - *

- * If an object is larger than the given size the pack-parsing will throw an - * exception aborting the receive-pack operation. - * - * @param limit - * the Git object size limit. If zero then there is not limit. - */ - public void setMaxObjectSizeLimit(final long limit) { - maxObjectSizeLimit = limit; - } - - /** - * Check whether the client expects a side-band stream. - * - * @return true if the client has advertised a side-band capability, false - * otherwise. - * @throws RequestNotYetReadException - * if the client's request has not yet been read from the wire, so - * we do not know if they expect side-band. Note that the client - * may have already written the request, it just has not been - * read. - */ - public boolean isSideBand() throws RequestNotYetReadException { - if (enabledCapabilities == null) - throw new RequestNotYetReadException(); - return enabledCapabilities.contains(CAPABILITY_SIDE_BAND_64K); - } - - /** @return all of the command received by the current request. */ - public List getAllCommands() { - return Collections.unmodifiableList(commands); - } - - /** - * Send an error message to the client. - *

- * 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 { - msgOutWrapper.write(Constants.encode("error: " + what + "\n")); - } - } - - /** - * 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) { - msgOutWrapper.write(Constants.encode(what + "\n")); - } - - /** @return an underlying stream for sending messages to the client. */ - public OutputStream getMessageOutputStream() { - return msgOutWrapper; - } - /** * Execute the receive task on the socket. * @@ -744,103 +137,34 @@ public class ReceivePack { */ public void receive(final InputStream input, final OutputStream output, final OutputStream messages) throws IOException { + init(input, output, messages); try { - rawIn = input; - rawOut = output; - msgOut = messages; - - 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); - pckOut.setFlushOnEnd(false); - - commands = new ArrayList(); - service(); } finally { - walk.release(); try { - if (sideBand) { - // If we are using side band, we need to send a final - // flush-pkt to tell the remote peer the side band is - // complete and it should stop decoding. We need to - // use the original output stream as rawOut is now the - // side band data channel. - // - ((SideBandOutputStream) msgOut).flushBuffer(); - ((SideBandOutputStream) rawOut).flushBuffer(); - - PacketLineOut plo = new PacketLineOut(output); - plo.setFlushOnEnd(false); - plo.end(); - } - - if (biDirectionalPipe) { - // If this was a native git connection, flush the pipe for - // the caller. For smart HTTP we don't do this flush and - // instead let the higher level HTTP servlet code do it. - // - if (!sideBand && msgOut != null) - msgOut.flush(); - rawOut.flush(); - } + close(); } finally { - unlockPack(); - timeoutIn = null; - rawIn = null; - rawOut = null; - msgOut = null; - pckIn = null; - pckOut = null; - refs = null; - enabledCapabilities = null; - commands = null; - if (timer != null) { - try { - timer.terminate(); - } finally { - timer = null; - } - } + release(); } } } - private Map getAdvertisedOrDefaultRefs() { - if (refs == null) - setAdvertisedRefs(null, null); - return refs; - } - private void service() throws IOException { if (biDirectionalPipe) { sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut)); pckOut.flush(); } else getAdvertisedOrDefaultRefs(); - if (advertiseError != null) + if (hasError()) return; recvCommands(); - if (!commands.isEmpty()) { + if (hasCommands()) { enableCapabilities(); + Throwable unpackError = null; if (needPack()) { try { - receivePack(); - if (needCheckConnectivity()) - checkConnectivity(); - parser = null; - unpackError = null; + receivePackAndCheckConnectivity(); } catch (IOException err) { unpackError = err; } catch (RuntimeException err) { @@ -852,463 +176,35 @@ public class ReceivePack { if (unpackError == null) { validateCommands(); + preReceive.onPreReceive(this, filterCommands(Result.NOT_ATTEMPTED)); executeCommands(); } unlockPack(); if (reportStatus) { - sendStatusReport(true, new Reporter() { + sendStatusReport(true, unpackError, new Reporter() { void sendString(final String s) throws IOException { pckOut.writeString(s + "\n"); } }); pckOut.end(); } else if (msgOut != null) { - sendStatusReport(false, new Reporter() { + sendStatusReport(false, unpackError, new Reporter() { void sendString(final String s) throws IOException { msgOut.write(Constants.encode(s + "\n")); } }); } - postReceive.onPostReceive(this, - ReceiveCommand.filter(commands, Result.OK)); + postReceive.onPostReceive(this, filterCommands(Result.OK)); if (unpackError != null) throw new UnpackException(unpackError); } } - private void unlockPack() throws IOException { - if (packLock != null) { - packLock.unlock(); - packLock = null; - } - } - - /** - * Generate an advertisement of available refs and capabilities. - * - * @param adv - * the advertisement formatter. - * @throws IOException - * the formatter failed to write an advertisement. - * @throws ServiceMayNotContinueException - * the hook denied advertisement. - */ - public void sendAdvertisedRefs(final RefAdvertiser adv) throws IOException, - ServiceMayNotContinueException { - if (advertiseError != null) { - adv.writeOne("ERR " + advertiseError); - return; - } - - try { - advertiseRefsHook.advertiseRefs(this); - } catch (ServiceMayNotContinueException fail) { - if (fail.getMessage() != null) { - adv.writeOne("ERR " + fail.getMessage()); - fail.setOutput(); - } - throw fail; - } - - adv.init(db); - adv.advertiseCapability(CAPABILITY_SIDE_BAND_64K); - adv.advertiseCapability(CAPABILITY_DELETE_REFS); - adv.advertiseCapability(CAPABILITY_REPORT_STATUS); - if (allowOfsDelta) - adv.advertiseCapability(CAPABILITY_OFS_DELTA); - adv.send(getAdvertisedOrDefaultRefs()); - for (ObjectId obj : advertisedHaves) - adv.advertiseHave(obj); - if (adv.isEmpty()) - adv.advertiseId(ObjectId.zeroId(), "capabilities^{}"); - adv.end(); - } - - private void recvCommands() throws IOException { - for (;;) { - String line; - try { - line = pckIn.readStringRaw(); - } catch (EOFException eof) { - if (commands.isEmpty()) - return; - throw eof; - } - if (line == PacketLineIn.END) - break; - - if (commands.isEmpty()) { - final FirstLine firstLine = new FirstLine(line); - enabledCapabilities = firstLine.getCapabilities(); - line = firstLine.getLine(); - } - - if (line.length() < 83) { - final String m = JGitText.get().errorInvalidProtocolWantedOldNewRef; - sendError(m); - throw new PackProtocolException(m); - } - - final ObjectId oldId = ObjectId.fromString(line.substring(0, 40)); - final ObjectId newId = ObjectId.fromString(line.substring(41, 81)); - final String name = line.substring(82); - final ReceiveCommand cmd = new ReceiveCommand(oldId, newId, name); - if (name.equals(Constants.HEAD)) { - cmd.setResult(Result.REJECTED_CURRENT_BRANCH); - } else { - cmd.setRef(refs.get(cmd.getRefName())); - } - commands.add(cmd); - } - } - - private void enableCapabilities() { - reportStatus = enabledCapabilities.contains(CAPABILITY_REPORT_STATUS); - - sideBand = enabledCapabilities.contains(CAPABILITY_SIDE_BAND_64K); - if (sideBand) { - OutputStream out = rawOut; - - rawOut = new SideBandOutputStream(CH_DATA, MAX_BUF, out); - msgOut = new SideBandOutputStream(CH_PROGRESS, MAX_BUF, out); - - pckOut = new PacketLineOut(rawOut); - pckOut.setFlushOnEnd(false); - } - } - - private boolean needPack() { - for (final ReceiveCommand cmd : commands) { - if (cmd.getType() != ReceiveCommand.Type.DELETE) - return true; - } - return false; - } - - private void receivePack() throws IOException { - // It might take the client a while to pack the objects it needs - // to send to us. We should increase our timeout so we don't - // abort while the client is computing. - // - if (timeoutIn != null) - timeoutIn.setTimeout(10 * timeout * 1000); - - ProgressMonitor receiving = NullProgressMonitor.INSTANCE; - ProgressMonitor resolving = NullProgressMonitor.INSTANCE; - if (sideBand) - resolving = new SideBandProgressMonitor(msgOut); - - ObjectInserter ins = db.newObjectInserter(); - try { - String lockMsg = "jgit receive-pack"; - if (getRefLogIdent() != null) - lockMsg += " from " + getRefLogIdent().toExternalString(); - - parser = ins.newPackParser(rawIn); - parser.setAllowThin(true); - parser.setNeedNewObjectIds(checkReferencedIsReachable); - parser.setNeedBaseObjectIds(checkReferencedIsReachable); - parser.setCheckEofAfterPackFooter(!biDirectionalPipe); - parser.setObjectChecking(isCheckReceivedObjects()); - parser.setLockMessage(lockMsg); - parser.setMaxObjectSizeLimit(maxObjectSizeLimit); - packLock = parser.parse(receiving, resolving); - ins.flush(); - } finally { - ins.release(); - } - - if (timeoutIn != null) - timeoutIn.setTimeout(timeout * 1000); - } - - private boolean needCheckConnectivity() { - return isCheckReceivedObjects() - || isCheckReferencedObjectsAreReachable(); - } - - private void checkConnectivity() throws IOException { - ObjectIdSubclassMap baseObjects = null; - ObjectIdSubclassMap providedObjects = null; - - if (checkReferencedIsReachable) { - baseObjects = parser.getBaseObjectIds(); - providedObjects = parser.getNewObjectIds(); - } - parser = null; - - final ObjectWalk ow = new ObjectWalk(db); - ow.setRetainBody(false); - if (checkReferencedIsReachable) { - ow.sort(RevSort.TOPO); - if (!baseObjects.isEmpty()) - ow.sort(RevSort.BOUNDARY, true); - } - - for (final ReceiveCommand cmd : commands) { - if (cmd.getResult() != Result.NOT_ATTEMPTED) - continue; - if (cmd.getType() == ReceiveCommand.Type.DELETE) - continue; - ow.markStart(ow.parseAny(cmd.getNewId())); - } - for (final ObjectId have : advertisedHaves) { - RevObject o = ow.parseAny(have); - ow.markUninteresting(o); - - if (checkReferencedIsReachable && !baseObjects.isEmpty()) { - o = ow.peel(o); - if (o instanceof RevCommit) - o = ((RevCommit) o).getTree(); - if (o instanceof RevTree) - ow.markUninteresting(o); - } - } - - RevCommit c; - while ((c = ow.next()) != null) { - if (checkReferencedIsReachable // - && !c.has(RevFlag.UNINTERESTING) // - && !providedObjects.contains(c)) - throw new MissingObjectException(c, Constants.TYPE_COMMIT); - } - - RevObject o; - while ((o = ow.nextObject()) != null) { - if (o.has(RevFlag.UNINTERESTING)) - continue; - - if (checkReferencedIsReachable) { - if (providedObjects.contains(o)) - continue; - else - throw new MissingObjectException(o, o.getType()); - } - - if (o instanceof RevBlob && !db.hasObject(o)) - throw new MissingObjectException(o, Constants.TYPE_BLOB); - } - - if (checkReferencedIsReachable) { - for (ObjectId id : baseObjects) { - o = ow.parseAny(id); - if (!o.has(RevFlag.UNINTERESTING)) - throw new MissingObjectException(o, o.getType()); - } - } - } - - private void validateCommands() { - for (final ReceiveCommand cmd : commands) { - final Ref ref = cmd.getRef(); - if (cmd.getResult() != Result.NOT_ATTEMPTED) - continue; - - if (cmd.getType() == ReceiveCommand.Type.DELETE - && !isAllowDeletes()) { - // Deletes are not supported on this repository. - // - cmd.setResult(Result.REJECTED_NODELETE); - continue; - } - - if (cmd.getType() == ReceiveCommand.Type.CREATE) { - if (!isAllowCreates()) { - cmd.setResult(Result.REJECTED_NOCREATE); - continue; - } - - if (ref != null && !isAllowNonFastForwards()) { - // Creation over an existing ref is certainly not going - // to be a fast-forward update. We can reject it early. - // - cmd.setResult(Result.REJECTED_NONFASTFORWARD); - continue; - } - - if (ref != null) { - // A well behaved client shouldn't have sent us a - // create command for a ref we advertised to it. - // - cmd.setResult(Result.REJECTED_OTHER_REASON, MessageFormat - .format(JGitText.get().refAlreadyExists, ref)); - continue; - } - } - - if (cmd.getType() == ReceiveCommand.Type.DELETE && ref != null - && !ObjectId.zeroId().equals(cmd.getOldId()) - && !ref.getObjectId().equals(cmd.getOldId())) { - // Delete commands can be sent with the old id matching our - // advertised value, *OR* with the old id being 0{40}. Any - // other requested old id is invalid. - // - cmd.setResult(Result.REJECTED_OTHER_REASON, - JGitText.get().invalidOldIdSent); - continue; - } - - if (cmd.getType() == ReceiveCommand.Type.UPDATE) { - if (ref == null) { - // The ref must have been advertised in order to be updated. - // - cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().noSuchRef); - continue; - } - - if (!ref.getObjectId().equals(cmd.getOldId())) { - // A properly functioning client will send the same - // object id we advertised. - // - cmd.setResult(Result.REJECTED_OTHER_REASON, - JGitText.get().invalidOldIdSent); - continue; - } - - // Is this possibly a non-fast-forward style update? - // - RevObject oldObj, newObj; - try { - oldObj = walk.parseAny(cmd.getOldId()); - } catch (IOException e) { - cmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd - .getOldId().name()); - continue; - } - - try { - newObj = walk.parseAny(cmd.getNewId()); - } catch (IOException e) { - cmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd - .getNewId().name()); - continue; - } - - if (oldObj instanceof RevCommit && newObj instanceof RevCommit) { - try { - if (!walk.isMergedInto((RevCommit) oldObj, - (RevCommit) newObj)) { - cmd - .setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD); - } - } catch (MissingObjectException e) { - cmd.setResult(Result.REJECTED_MISSING_OBJECT, e - .getMessage()); - } catch (IOException e) { - cmd.setResult(Result.REJECTED_OTHER_REASON); - } - } else { - cmd.setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD); - } - } - - if (!cmd.getRefName().startsWith(Constants.R_REFS) - || !Repository.isValidRefName(cmd.getRefName())) { - cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().funnyRefname); - } - } - } - - private void executeCommands() { - preReceive.onPreReceive(this, - ReceiveCommand.filter(commands, Result.NOT_ATTEMPTED)); - - List toApply = ReceiveCommand.filter(commands, - Result.NOT_ATTEMPTED); - ProgressMonitor updating = NullProgressMonitor.INSTANCE; - if (sideBand) { - SideBandProgressMonitor pm = new SideBandProgressMonitor(msgOut); - pm.setDelayStart(250, TimeUnit.MILLISECONDS); - updating = pm; - } - updating.beginTask(JGitText.get().updatingReferences, toApply.size()); - for (ReceiveCommand cmd : toApply) { - updating.update(1); - cmd.execute(this); - } - updating.endTask(); - } - - private void sendStatusReport(final boolean forClient, final Reporter out) - throws IOException { - if (unpackError != null) { - out.sendString("unpack error " + unpackError.getMessage()); - if (forClient) { - for (final ReceiveCommand cmd : commands) { - out.sendString("ng " + cmd.getRefName() - + " n/a (unpacker error)"); - } - } - return; - } - - if (forClient) - out.sendString("unpack ok"); - for (final ReceiveCommand cmd : commands) { - if (cmd.getResult() == Result.OK) { - if (forClient) - out.sendString("ok " + cmd.getRefName()); - continue; - } - - final StringBuilder r = new StringBuilder(); - r.append("ng "); - r.append(cmd.getRefName()); - r.append(" "); - - switch (cmd.getResult()) { - case NOT_ATTEMPTED: - r.append("server bug; ref not processed"); - break; - - case REJECTED_NOCREATE: - r.append("creation prohibited"); - break; - - case REJECTED_NODELETE: - r.append("deletion prohibited"); - break; - - case REJECTED_NONFASTFORWARD: - r.append("non-fast forward"); - break; - - case REJECTED_CURRENT_BRANCH: - r.append("branch is currently checked out"); - break; - - case REJECTED_MISSING_OBJECT: - if (cmd.getMessage() == null) - r.append("missing object(s)"); - else if (cmd.getMessage().length() == Constants.OBJECT_ID_STRING_LENGTH) - r.append("object " + cmd.getMessage() + " missing"); - else - r.append(cmd.getMessage()); - break; - - case REJECTED_OTHER_REASON: - if (cmd.getMessage() == null) - r.append("unspecified reason"); - else - r.append(cmd.getMessage()); - break; - - case LOCK_FAILURE: - r.append("failed to lock"); - break; - - case OK: - // We shouldn't have reached this case (see 'ok' case above). - continue; - } - out.sendString(r.toString()); - } - } - - static abstract class Reporter { - abstract void sendString(String s) throws IOException; + @Override + protected String getLockMessageProcessName() { + return "jgit receive-pack"; } }