diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/PacketLineIn.java b/org.spearce.jgit/src/org/spearce/jgit/transport/PacketLineIn.java
index f87517d..ef218be 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/PacketLineIn.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/PacketLineIn.java
@@ -111,6 +111,18 @@ String readString() throws IOException {
return RawParseUtils.decode(Constants.CHARSET, raw, 0, len);
}
+ String readStringNoLF() throws IOException {
+ int len = readLength();
+ if (len == 0)
+ return "";
+
+ len -= 4; // length header (4 bytes)
+
+ final byte[] raw = new byte[len];
+ NB.readFully(in, raw, 0, len);
+ return RawParseUtils.decode(Constants.CHARSET, raw, 0, len);
+ }
+
private void readLF() throws IOException {
if (in.read() != '\n')
throw new IOException("Protocol error: expected LF");diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/PostReceiveHook.java b/org.spearce.jgit/src/org/spearce/jgit/transport/PostReceiveHook.java
new file mode 100644
index 0000000..060efb3
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/PostReceiveHook.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * 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 Git Development Community 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.spearce.jgit.transport;
+
+import java.util.Collection;
+
+/**
+ * Hook invoked by {@link ReceivePack} after all updates are executed.
+ * <p>
+ * The hook is called after all commands have been processed. Only commands with
+ * a status of {@link ReceiveCommand.Result#OK} are passed into the hook. To get
+ * all commands within the hook, see {@link ReceivePack#getAllCommands()}.
+ * <p>
+ * Any post-receive hook implementation should not update the status of a
+ * command, as the command has already completed or failed, and the status has
+ * already been returned to the client.
+ * <p>
+ * Hooks should execute quickly, as they block the server and the client from
+ * completing the connection.
+ */
+public interface PostReceiveHook {
+ /** A simple no-op hook. */
+ public static final PostReceiveHook NULL = new PostReceiveHook() {
+ public void onPostReceive(final ReceivePack rp,
+ final Collection<ReceiveCommand> commands) {
+ // Do nothing.
+ }
+ };
+
+ /**
+ * Invoked after all commands are executed and status has been returned.
+ *
+ * @param rp
+ * the process handling the current receive. Hooks may obtain
+ * details about the destination repository through this handle.
+ * @param commands
+ * unmodifiable set of successfully completed commands. May be
+ * the empty set.
+ */
+ public void onPostReceive(ReceivePack rp,
+ Collection<ReceiveCommand> commands);
+}diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/PreReceiveHook.java b/org.spearce.jgit/src/org/spearce/jgit/transport/PreReceiveHook.java
new file mode 100644
index 0000000..0af466d
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/PreReceiveHook.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * 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 Git Development Community 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.spearce.jgit.transport;
+
+import java.util.Collection;
+
+/**
+ * Hook invoked by {@link ReceivePack} before any updates are executed.
+ * <p>
+ * The hook is called with any commands that are deemed valid after parsing them
+ * from the client and applying the standard receive configuration options to
+ * them:
+ * <ul>
+ * <li><code>receive.denyDenyDeletes</code></li>
+ * <li><code>receive.denyNonFastForwards</code></li>
+ * </ul>
+ * This means the hook will not receive a non-fast-forward update command if
+ * denyNonFastForwards is set to true in the configuration file. To get all
+ * commands within the hook, see {@link ReceivePack#getAllCommands()}.
+ * <p>
+ * As the hook is invoked prior to the commands being executed, the hook may
+ * choose to block any command by setting its result status with
+ * {@link ReceiveCommand#setResult(ReceiveCommand.Result)}.
+ * <p>
+ * The hook may also choose to perform the command itself (or merely pretend
+ * that it has performed the command), by setting the result status to
+ * {@link ReceiveCommand.Result#OK}.
+ * <p>
+ * Hooks should run quickly, as they block the caller thread and the client
+ * process from completing.
+ * <p>
+ * Hooks may send optional messages back to the client via methods on
+ * {@link ReceivePack}. Implementors should be aware that not all network
+ * transports support this output, so some (or all) messages may simply be
+ * discarded. These messages should be advisory only.
+ */
+public interface PreReceiveHook {
+ /** A simple no-op hook. */
+ public static final PreReceiveHook NULL = new PreReceiveHook() {
+ public void onPreReceive(final ReceivePack rp,
+ final Collection<ReceiveCommand> commands) {
+ // Do nothing.
+ }
+ };
+
+ /**
+ * Invoked just before commands are executed.
+ * <p>
+ * See the class description for how this method can impact execution.
+ *
+ * @param rp
+ * the process handling the current receive. Hooks may obtain
+ * details about the destination repository through this handle.
+ * @param commands
+ * unmodifiable set of valid commands still pending execution.
+ * May be the empty set.
+ */
+ public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands);
+}diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/ReceiveCommand.java b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceiveCommand.java
new file mode 100644
index 0000000..b07b976
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceiveCommand.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * 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 Git Development Community 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.spearce.jgit.transport;
+
+import org.spearce.jgit.lib.ObjectId;
+import org.spearce.jgit.lib.Ref;
+
+/**
+ * A command being processed by {@link ReceivePack}.
+ * <p>
+ * This command instance roughly translates to the server side representation of
+ * the {@link RemoteRefUpdate} created by the client.
+ */
+public class ReceiveCommand {
+ /** Type of operation requested. */
+ public static enum Type {
+ /** Create a new ref; the ref must not already exist. */
+ CREATE,
+
+ /**
+ * Update an existing ref with a fast-forward update.
+ * <p>
+ * During a fast-forward update no changes will be lost; only new
+ * commits are inserted into the ref.
+ */
+ UPDATE,
+
+ /**
+ * Update an existing ref by potentially discarding objects.
+ * <p>
+ * The current value of the ref is not fully reachable from the new
+ * value of the ref, so a successful command may result in one or more
+ * objects becoming unreachable.
+ */
+ UPDATE_NONFASTFORWARD,
+
+ /** Delete an existing ref; the ref should already exist. */
+ DELETE;
+ }
+
+ /** Result of the update command. */
+ public static enum Result {
+ /** The command has not yet been attempted by the server. */
+ NOT_ATTEMPTED,
+
+ /** The server is configured to deny creation of this ref. */
+ REJECTED_NOCREATE,
+
+ /** The server is configured to deny deletion of this ref. */
+ REJECTED_NODELETE,
+
+ /** The update is a non-fast-forward update and isn't permitted. */
+ REJECTED_NONFASTFORWARD,
+
+ /** The update affects <code>HEAD</code> and cannot be permitted. */
+ REJECTED_CURRENT_BRANCH,
+
+ /**
+ * One or more objects aren't in the repository.
+ * <p>
+ * This is severe indication of either repository corruption on the
+ * server side, or a bug in the client wherein the client did not supply
+ * all required objects during the pack transfer.
+ */
+ REJECTED_MISSING_OBJECT,
+
+ /** Other failure; see {@link ReceiveCommand#getMessage()}. */
+ REJECTED_OTHER_REASON,
+
+ /** The ref could not be locked and updated atomically; try again. */
+ LOCK_FAILURE,
+
+ /** The change was completed successfully. */
+ OK;
+ }
+
+ private final ObjectId oldId;
+
+ private final ObjectId newId;
+
+ private final String name;
+
+ private Type type;
+
+ private Ref ref;
+
+ private Result status;
+
+ private String message;
+
+ /**
+ * Create a new command for {@link ReceivePack}.
+ *
+ * @param oldId
+ * the old object id; must not be null. Use
+ * {@link ObjectId#zeroId()} to indicate a ref creation.
+ * @param newId
+ * the new object id; must not be null. Use
+ * {@link ObjectId#zeroId()} to indicate a ref deletion.
+ * @param name
+ * name of the ref being affected.
+ */
+ public ReceiveCommand(final ObjectId oldId, final ObjectId newId,
+ final String name) {
+ this.oldId = oldId;
+ this.newId = newId;
+ this.name = name;
+
+ type = Type.UPDATE;
+ if (ObjectId.zeroId().equals(oldId))
+ type = Type.CREATE;
+ if (ObjectId.zeroId().equals(newId))
+ type = Type.DELETE;
+ status = Result.NOT_ATTEMPTED;
+ }
+
+ /** @return the old value the client thinks the ref has. */
+ public ObjectId getOldId() {
+ return oldId;
+ }
+
+ /** @return the requested new value for this ref. */
+ public ObjectId getNewId() {
+ return newId;
+ }
+
+ /** @return the name of the ref being updated. */
+ public String getRefName() {
+ return name;
+ }
+
+ /** @return the type of this command; see {@link Type}. */
+ public Type getType() {
+ return type;
+ }
+
+ /** @return the ref, if this was advertised by the connection. */
+ public Ref getRef() {
+ return ref;
+ }
+
+ /** @return the current status code of this command. */
+ public Result getResult() {
+ return status;
+ }
+
+ /** @return the message associated with a failure status. */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Set the status of this command.
+ *
+ * @param s
+ * the new status code for this command.
+ */
+ public void setResult(final Result s) {
+ setResult(s, null);
+ }
+
+ /**
+ * Set the status of this command.
+ *
+ * @param s
+ * new status code for this command.
+ * @param m
+ * optional message explaining the new status.
+ */
+ public void setResult(final Result s, final String m) {
+ status = s;
+ message = m;
+ }
+
+ void setRef(final Ref r) {
+ ref = r;
+ }
+
+ void setType(final Type t) {
+ type = t;
+ }
+
+ @Override
+ public String toString() {
+ return getType().name() + ": " + getOldId().name() + " "
+ + getNewId().name() + " " + getRefName();
+ }
+}diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java
new file mode 100644
index 0000000..95519b8
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java
@@ -0,0 +1,793 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * 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 Git Development Community 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.spearce.jgit.transport;
+
+import java.io.BufferedWriter;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.spearce.jgit.errors.MissingObjectException;
+import org.spearce.jgit.errors.PackProtocolException;
+import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.NullProgressMonitor;
+import org.spearce.jgit.lib.ObjectId;
+import org.spearce.jgit.lib.Ref;
+import org.spearce.jgit.lib.RefComparator;
+import org.spearce.jgit.lib.RefUpdate;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.lib.RepositoryConfig;
+import org.spearce.jgit.revwalk.ObjectWalk;
+import org.spearce.jgit.revwalk.RevCommit;
+import org.spearce.jgit.revwalk.RevObject;
+import org.spearce.jgit.revwalk.RevWalk;
+import org.spearce.jgit.transport.ReceiveCommand.Result;
+
+/**
+ * Implements the server side of a push connection, receiving objects.
+ */
+public class ReceivePack {
+ static final String CAPABILITY_REPORT_STATUS = BasePackPushConnection.CAPABILITY_REPORT_STATUS;
+
+ static final String CAPABILITY_DELETE_REFS = BasePackPushConnection.CAPABILITY_DELETE_REFS;
+
+ /** Database we write the stored objects into. */
+ private final Repository db;
+
+ /** Revision traversal support over {@link #db}. */
+ private final RevWalk walk;
+
+ /** 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;
+
+ /** Hook to validate the update commands before execution. */
+ private PreReceiveHook preReceive;
+
+ /** Hook to report on the commands after execution. */
+ private PostReceiveHook postReceive;
+
+ private InputStream rawIn;
+
+ private OutputStream rawOut;
+
+ private PacketLineIn pckIn;
+
+ private PacketLineOut pckOut;
+
+ private PrintWriter msgs;
+
+ /** The refs we advertised as existing at the start of the connection. */
+ private Map<String, Ref> refs;
+
+ /** Capabilities requested by the client. */
+ private Set<String> enabledCapablities;
+
+ /** Commands to execute, as received by the client. */
+ private List<ReceiveCommand> commands;
+
+ /** An exception caught while unpacking and fsck'ing the objects. */
+ private Throwable unpackError;
+
+ /** if {@link #enabledCapablities} has {@link #CAPABILITY_REPORT_STATUS} */
+ private boolean reportStatus;
+
+ /**
+ * Create a new pack receive for an open repository.
+ *
+ * @param into
+ * the destination repository.
+ */
+ public ReceivePack(final Repository into) {
+ db = into;
+ walk = new RevWalk(db);
+
+ final RepositoryConfig cfg = db.getConfig();
+ checkReceivedObjects = cfg.getBoolean("receive", "fsckobjects", false);
+ allowCreates = true;
+ allowDeletes = !cfg.getBoolean("receive", "denydeletes", false);
+ allowNonFastForwards = !cfg.getBoolean("receive",
+ "denynonfastforwards", false);
+ preReceive = PreReceiveHook.NULL;
+ postReceive = PostReceiveHook.NULL;
+ }
+
+ /** @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;
+ }
+
+ /**
+ * @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 get the hook invoked before updates occur. */
+ public PreReceiveHook getPreReceiveHook() {
+ return preReceive;
+ }
+
+ /**
+ * Set the hook which is invoked prior to commands being executed.
+ * <p>
+ * Only valid commands (those which have no obvious errors according to the
+ * received input and this instance's configuration) are passed into the
+ * hook. The hook may mark a command with a result of any value other than
+ * {@link Result#NOT_ATTEMPTED} to block its execution.
+ * <p>
+ * The hook may be called with an empty command collection if the current
+ * set is completely invalid.
+ *
+ * @param h
+ * the hook instance; may be null to disable the hook.
+ */
+ public void setPreReceiveHook(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.
+ * <p>
+ * 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 all of the command received by the current request. */
+ public List<ReceiveCommand> getAllCommands() {
+ return Collections.unmodifiableList(commands);
+ }
+
+ /**
+ * Send an error message to the client, if it supports receiving them.
+ * <p>
+ * If the client doesn't support receiving messages, the message will be
+ * discarded, with no other indication to the caller or to the client.
+ * <p>
+ * {@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) {
+ sendMessage("error", what);
+ }
+
+ /**
+ * Send a message to the client, if it supports receiving them.
+ * <p>
+ * 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) {
+ sendMessage("remote", what);
+ }
+
+ private void sendMessage(final String type, final String what) {
+ if (msgs != null)
+ msgs.println(type + ": " + what);
+ }
+
+ /**
+ * 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;
+
+ pckIn = new PacketLineIn(rawIn);
+ pckOut = new PacketLineOut(rawOut);
+ if (messages != null) {
+ msgs = new PrintWriter(new BufferedWriter(
+ new OutputStreamWriter(messages, Constants.CHARSET),
+ 8192)) {
+ @Override
+ public void println() {
+ print('\n');
+ }
+ };
+ }
+
+ enabledCapablities = new HashSet<String>();
+ commands = new ArrayList<ReceiveCommand>();
+
+ service();
+ } finally {
+ try {
+ if (msgs != null) {
+ msgs.flush();
+ }
+ } finally {
+ rawIn = null;
+ rawOut = null;
+ pckIn = null;
+ pckOut = null;
+ msgs = null;
+ refs = null;
+ enabledCapablities = null;
+ commands = null;
+ }
+ }
+ }
+
+ private void service() throws IOException {
+ sendAdvertisedRefs();
+ recvCommands();
+ if (!commands.isEmpty()) {
+ enableCapabilities();
+
+ if (needPack()) {
+ try {
+ receivePack();
+ if (isCheckReceivedObjects())
+ checkConnectivity();
+ unpackError = null;
+ } catch (IOException err) {
+ unpackError = err;
+ } catch (RuntimeException err) {
+ unpackError = err;
+ } catch (Error err) {
+ unpackError = err;
+ }
+ }
+
+ if (unpackError == null) {
+ validateCommands();
+ executeCommands();
+ }
+
+ if (reportStatus) {
+ sendStatusReport(true, new Reporter() {
+ void sendString(final String s) throws IOException {
+ pckOut.writeString(s + "\n");
+ }
+ });
+ pckOut.end();
+ } else if (msgs != null) {
+ sendStatusReport(false, new Reporter() {
+ void sendString(final String s) throws IOException {
+ msgs.println(s);
+ }
+ });
+ msgs.flush();
+ }
+
+ postReceive.onPostReceive(this, filterCommands(Result.OK));
+ }
+ }
+
+ private void sendAdvertisedRefs() throws IOException {
+ refs = db.getAllRefs();
+
+ final StringBuilder m = new StringBuilder(100);
+ final char[] idtmp = new char[2 * Constants.OBJECT_ID_LENGTH];
+ final Iterator<Ref> i = RefComparator.sort(refs.values()).iterator();
+ {
+ if (i.hasNext()) {
+ final Ref r = i.next();
+ format(m, idtmp, r.getObjectId(), r.getOrigName());
+ } else {
+ format(m, idtmp, ObjectId.zeroId(), "capabilities^{}");
+ }
+ m.append('\0');
+ m.append(' ');
+ m.append(CAPABILITY_DELETE_REFS);
+ m.append(' ');
+ m.append(CAPABILITY_REPORT_STATUS);
+ m.append(' ');
+ writeAdvertisedRef(m);
+ }
+
+ while (i.hasNext()) {
+ final Ref r = i.next();
+ format(m, idtmp, r.getObjectId(), r.getOrigName());
+ writeAdvertisedRef(m);
+ }
+ pckOut.end();
+ }
+
+ private void format(final StringBuilder m, final char[] idtmp,
+ final ObjectId id, final String name) {
+ m.setLength(0);
+ id.copyTo(idtmp, m);
+ m.append(' ');
+ m.append(name);
+ }
+
+ private void writeAdvertisedRef(final StringBuilder m) throws IOException {
+ m.append('\n');
+ pckOut.writeString(m.toString());
+ }
+
+ private void recvCommands() throws IOException {
+ for (;;) {
+ String line;
+ try {
+ line = pckIn.readStringNoLF();
+ } catch (EOFException eof) {
+ if (commands.isEmpty())
+ return;
+ throw eof;
+ }
+
+ if (commands.isEmpty()) {
+ final int nul = line.indexOf('\0');
+ if (nul >= 0) {
+ for (String c : line.substring(nul + 1).split(" "))
+ enabledCapablities.add(c);
+ line = line.substring(0, nul);
+ }
+ }
+
+ if (line.length() == 0)
+ break;
+ if (line.length() < 83) {
+ final String m = "error: invalid protocol: wanted 'old new ref'";
+ 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);
+ cmd.setRef(refs.get(cmd.getRefName()));
+ commands.add(cmd);
+ }
+ }
+
+ private void enableCapabilities() {
+ reportStatus = enabledCapablities.contains(CAPABILITY_REPORT_STATUS);
+ }
+
+ private boolean needPack() {
+ for (final ReceiveCommand cmd : commands) {
+ if (cmd.getType() != ReceiveCommand.Type.DELETE)
+ return true;
+ }
+ return false;
+ }
+
+ private void receivePack() throws IOException {
+ final IndexPack ip = IndexPack.create(db, rawIn);
+ ip.setFixThin(true);
+ ip.setObjectChecking(isCheckReceivedObjects());
+ ip.index(NullProgressMonitor.INSTANCE);
+ ip.renameAndOpenPack();
+ }
+
+ private void checkConnectivity() throws IOException {
+ final ObjectWalk ow = new ObjectWalk(db);
+ 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 Ref ref : refs.values())
+ ow.markUninteresting(ow.parseAny(ref.getObjectId()));
+ ow.checkConnectivity();
+ }
+
+ 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 an
+ // update command for a ref we advertised to it.
+ //
+ cmd.setResult(Result.REJECTED_OTHER_REASON, "ref exists");
+ 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,
+ "invalid old id sent");
+ 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, "no such ref");
+ 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,
+ "invalid old id sent");
+ 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, "funny refname");
+ }
+ }
+ }
+
+ private void executeCommands() {
+ preReceive.onPreReceive(this, filterCommands(Result.NOT_ATTEMPTED));
+ for (final ReceiveCommand cmd : filterCommands(Result.NOT_ATTEMPTED))
+ execute(cmd);
+ }
+
+ private void execute(final ReceiveCommand cmd) {
+ try {
+ final RefUpdate ru = db.updateRef(cmd.getRefName());
+ switch (cmd.getType()) {
+ case DELETE:
+ if (!ObjectId.zeroId().equals(cmd.getOldId())) {
+ // We can only do a CAS style delete if the client
+ // didn't bork its delete request by sending the
+ // wrong zero id rather than the advertised one.
+ //
+ ru.setExpectedOldObjectId(cmd.getOldId());
+ }
+ ru.setForceUpdate(true);
+ status(cmd, ru.delete(walk));
+ break;
+
+ case CREATE:
+ case UPDATE:
+ case UPDATE_NONFASTFORWARD:
+ ru.setForceUpdate(isAllowNonFastForwards());
+ ru.setExpectedOldObjectId(cmd.getOldId());
+ ru.setNewObjectId(cmd.getNewId());
+ ru.setRefLogMessage("push", true);
+ status(cmd, ru.update(walk));
+ break;
+ }
+ } catch (IOException err) {
+ cmd.setResult(Result.REJECTED_OTHER_REASON, "lock error: "
+ + err.getMessage());
+ }
+ }
+
+ private void status(final ReceiveCommand cmd, final RefUpdate.Result result) {
+ switch (result) {
+ case NOT_ATTEMPTED:
+ cmd.setResult(Result.NOT_ATTEMPTED);
+ break;
+
+ case LOCK_FAILURE:
+ case IO_FAILURE:
+ cmd.setResult(Result.LOCK_FAILURE);
+ break;
+
+ case NO_CHANGE:
+ case NEW:
+ case FORCED:
+ case FAST_FORWARD:
+ cmd.setResult(Result.OK);
+ break;
+
+ case REJECTED:
+ cmd.setResult(Result.REJECTED_NONFASTFORWARD);
+ break;
+
+ case REJECTED_CURRENT_BRANCH:
+ cmd.setResult(Result.REJECTED_CURRENT_BRANCH);
+ break;
+
+ default:
+ cmd.setResult(Result.REJECTED_OTHER_REASON, result.name());
+ break;
+ }
+ }
+
+ private List<ReceiveCommand> filterCommands(final Result want) {
+ final List<ReceiveCommand> r = new ArrayList<ReceiveCommand>(commands
+ .size());
+ for (final ReceiveCommand cmd : commands) {
+ if (cmd.getResult() == want)
+ r.add(cmd);
+ }
+ return r;
+ }
+
+ 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() == 2 * Constants.OBJECT_ID_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;
+ }
+}--
1.6.1.rc4.301.g5497a