From 3e9a5f993b808f0027760054fd862d7f149d07d8 Mon Sep 17 00:00:00 2001 From: Masaya Suzuki Date: Tue, 23 Jul 2019 12:54:43 -0700 Subject: [PATCH] transport: Merge BaseReceivePack into ReceivePack Move the BaseReceivePack implementation back into ReceivePack. This is a backward-incompatible change. For example, BaseReceivePack.FirstLine no longer exists and cannot be referenced. However, most of the code should just work by replacing BaseReceivePack with ReceivePack. Although this is an API change, it only affects callers using JGit as a server, and there are very few of those in the wild. Change-Id: I1ce92869435d5eebb7d671be44561e69c6233134 Signed-off-by: Masaya Suzuki --- .../transport/PushCertificateParserTest.java | 2 +- .../ReceivePackAdvertiseRefsHookTest.java | 2 +- ...eivePackTest.java => ReceivePackTest.java} | 8 +- .../transport/AbstractAdvertiseRefsHook.java | 2 +- .../jgit/transport/AdvertiseRefsHook.java | 8 +- .../transport/AdvertiseRefsHookChain.java | 4 +- .../jgit/transport/BaseReceivePack.java | 1997 ---------------- .../jgit/transport/PushCertificateParser.java | 2 +- .../jgit/transport/ReceiveCommand.java | 8 +- .../eclipse/jgit/transport/ReceivePack.java | 2010 ++++++++++++++++- 10 files changed, 1940 insertions(+), 2103 deletions(-) rename org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/{BaseReceivePackTest.java => ReceivePackTest.java} (94%) delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateParserTest.java index 4bf26b628..5db4563ba 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateParserTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateParserTest.java @@ -145,7 +145,7 @@ public class PushCertificateParserTest { ObjectId newId = ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); String line = oldId.name() + " " + newId.name() + " refs/heads/master"; - ReceiveCommand cmd = BaseReceivePack.parseCommand(line); + ReceiveCommand cmd = ReceivePack.parseCommand(line); parser.addCommand(cmd); parser.addCommand(line); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java index 5d208eea7..89ac2fe96 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java @@ -174,7 +174,7 @@ public class ReceivePackAdvertiseRefsHookTest extends LocalDiskRepositoryTestCas ReceivePack rp = super.createReceivePack(dst); rp.setAdvertiseRefsHook(new AdvertiseRefsHook() { @Override - public void advertiseRefs(BaseReceivePack rp2) + public void advertiseRefs(ReceivePack rp2) throws ServiceMayNotContinueException { rp.setAdvertisedRefs(rp.getRepository().getAllRefs(), null); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BaseReceivePackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackTest.java similarity index 94% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BaseReceivePackTest.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackTest.java index 7578c6e3e..156746d79 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BaseReceivePackTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackTest.java @@ -49,14 +49,14 @@ import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.lib.ObjectId; import org.junit.Test; -/** Tests for base receive-pack utilities. */ -public class BaseReceivePackTest { +/** Tests for receive-pack utilities. */ +public class ReceivePackTest { @Test public void parseCommand() throws Exception { String o = "0000000000000000000000000000000000000000"; String n = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; String r = "refs/heads/master"; - ReceiveCommand cmd = BaseReceivePack.parseCommand(o + " " + n + " " + r); + ReceiveCommand cmd = ReceivePack.parseCommand(o + " " + n + " " + r); assertEquals(ObjectId.zeroId(), cmd.getOldId()); assertEquals("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", cmd.getNewId().name()); @@ -76,7 +76,7 @@ public class BaseReceivePackTest { private void assertParseCommandFails(String input) { try { - BaseReceivePack.parseCommand(input); + ReceivePack.parseCommand(input); fail(); } catch (PackProtocolException e) { // Expected. 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 4bf0d2692..ed900121b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java @@ -67,7 +67,7 @@ public abstract class AbstractAdvertiseRefsHook implements AdvertiseRefsHook { /** {@inheritDoc} */ @Override - public void advertiseRefs(BaseReceivePack receivePack) + public void advertiseRefs(ReceivePack 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 8512f2d9f..b771e99c0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java @@ -51,8 +51,8 @@ public interface AdvertiseRefsHook { /** * A simple hook that advertises the default refs. *

- * The method implementations do nothing to preserve the default behavior; see - * {@link UploadPack#setAdvertisedRefs(java.util.Map)} and + * 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)}. */ AdvertiseRefsHook DEFAULT = new AdvertiseRefsHook() { @@ -62,7 +62,7 @@ public interface AdvertiseRefsHook { } @Override - public void advertiseRefs(BaseReceivePack receivePack) { + public void advertiseRefs(ReceivePack receivePack) { // Do nothing. } }; @@ -90,6 +90,6 @@ public interface AdvertiseRefsHook { * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * abort; the message will be sent to the user. */ - void advertiseRefs(BaseReceivePack receivePack) + void advertiseRefs(ReceivePack 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 12238a1f7..54c19783e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java @@ -53,7 +53,7 @@ import java.util.List; * modify the results of the previous hooks in the chain by calling * {@link org.eclipse.jgit.transport.UploadPack#getAdvertisedRefs()}, or * {@link org.eclipse.jgit.transport.ReceivePack#getAdvertisedRefs()} or - * {@link org.eclipse.jgit.transport.BaseReceivePack#getAdvertisedObjects()}. + * {@link org.eclipse.jgit.transport.ReceivePack#getAdvertisedObjects()}. */ public class AdvertiseRefsHookChain implements AdvertiseRefsHook { private final AdvertiseRefsHook[] hooks; @@ -82,7 +82,7 @@ public class AdvertiseRefsHookChain implements AdvertiseRefsHook { /** {@inheritDoc} */ @Override - public void advertiseRefs(BaseReceivePack rp) + public void advertiseRefs(ReceivePack 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 deleted file mode 100644 index ae0c22068..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java +++ /dev/null @@ -1,1997 +0,0 @@ -/* - * 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 java.nio.charset.StandardCharsets.UTF_8; -import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC; -import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_DELETE_REFS; -import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_OFS_DELTA; -import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_OPTIONS; -import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_QUIET; -import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS; -import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SIDE_BAND_64K; -import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT; -import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA; -import static org.eclipse.jgit.transport.SideBandOutputStream.CH_ERROR; -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.annotations.Nullable; -import org.eclipse.jgit.errors.InvalidObjectIdException; -import org.eclipse.jgit.errors.LargeObjectException; -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.errors.PackProtocolException; -import org.eclipse.jgit.errors.TooLargePackException; -import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.internal.storage.file.PackLock; -import org.eclipse.jgit.internal.submodule.SubmoduleValidator; -import org.eclipse.jgit.internal.submodule.SubmoduleValidator.SubmoduleValidationException; -import org.eclipse.jgit.internal.transport.parser.FirstCommand; -import org.eclipse.jgit.lib.AnyObjectId; -import org.eclipse.jgit.lib.BatchRefUpdate; -import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.GitmoduleEntry; -import org.eclipse.jgit.lib.NullProgressMonitor; -import org.eclipse.jgit.lib.ObjectChecker; -import org.eclipse.jgit.lib.ObjectDatabase; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectIdSubclassMap; -import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.ObjectLoader; -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.transport.PacketLineIn.InputOverLimitIOException; -import org.eclipse.jgit.transport.ReceiveCommand.Result; -import org.eclipse.jgit.util.io.InterruptTimer; -import org.eclipse.jgit.util.io.LimitedInputStream; -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. - * - * @deprecated Use {@link FirstCommand} instead. - */ - @Deprecated - public static class FirstLine { - private final FirstCommand command; - - /** - * Parse the first line of a receive-pack request. - * - * @param line - * line from the client. - */ - public FirstLine(String line) { - command = FirstCommand.fromLine(line); - } - - /** @return non-capabilities part of the line. */ - public String getLine() { - return command.getLine(); - } - - /** @return capabilities parsed from the line. */ - public Set getCapabilities() { - return command.getCapabilities(); - } - } - - /** Database we write the stored objects into. */ - final Repository db; - - /** Revision traversal support over {@link #db}. */ - 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; - - /** Expecting data after the pack footer */ - private boolean expectDataAfterPackFooter; - - /** Should an incoming transfer validate objects? */ - private ObjectChecker objectChecker; - - /** Should an incoming transfer permit create requests? */ - private boolean allowCreates; - - /** Should an incoming transfer permit delete requests? */ - private boolean allowAnyDeletes; - private boolean allowBranchDeletes; - - /** Should an incoming transfer permit non-fast-forward requests? */ - private boolean allowNonFastForwards; - - /** Should an incoming transfer permit push options? **/ - private boolean allowPushOptions; - - /** - * Should the requested ref updates be performed as a single atomic - * transaction? - */ - private boolean atomic; - - private boolean allowOfsDelta; - private boolean allowQuiet = true; - - /** 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. */ - 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; - private SideBandOutputStream errOut; - - /** 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. */ - Map refs; - - /** All SHA-1s shown to the client, which can be possible edges. */ - Set advertisedHaves; - - /** Capabilities requested by the client. */ - private Set enabledCapabilities; - String userAgent; - private Set clientShallowCommits; - private List commands; - private long maxCommandBytes; - private long maxDiscardBytes; - - private StringBuilder advertiseError; - - /** If {@link BasePackPushConnection#CAPABILITY_SIDE_BAND_64K} is enabled. */ - private boolean sideBand; - - private boolean quiet; - - /** Lock around the received pack file, while updating refs. */ - private PackLock packLock; - - private boolean checkReferencedIsReachable; - - /** Git object size limit */ - private long maxObjectSizeLimit; - - /** Total pack size limit */ - private long maxPackSizeLimit = -1; - - /** The size of the received pack, including index size */ - private Long packSize; - - private PushCertificateParser pushCertificateParser; - private SignedPushConfig signedPushConfig; - PushCertificate pushCert; - private ReceivedPackStatistics stats; - - /** - * Get the push certificate used to verify the pusher's identity. - *

- * Only valid after commands are read from the wire. - * - * @return the parsed certificate, or null if push certificates are disabled - * or no cert was presented by the client. - * @since 4.1 - * @deprecated use {@link ReceivePack#getPushCertificate}. - */ - @Deprecated - public abstract PushCertificate getPushCertificate(); - - /** - * Set the push certificate used to verify the pusher's identity. - *

- * Should only be called if reconstructing an instance without going through - * the normal {@link #recvCommands()} flow. - * - * @param cert - * the push certificate to set. - * @since 4.1 - * @deprecated use {@link ReceivePack#setPushCertificate(PushCertificate)}. - */ - @Deprecated - public abstract void setPushCertificate(PushCertificate cert); - - /** - * Create a new pack receive for an open repository. - * - * @param into - * the destination repository. - */ - protected BaseReceivePack(Repository into) { - db = into; - walk = new RevWalk(db); - walk.setRetainBody(false); - - TransferConfig tc = db.getConfig().get(TransferConfig.KEY); - objectChecker = tc.newReceiveObjectChecker(); - - ReceiveConfig rc = db.getConfig().get(ReceiveConfig::new); - allowCreates = rc.allowCreates; - allowAnyDeletes = true; - allowBranchDeletes = rc.allowDeletes; - allowNonFastForwards = rc.allowNonFastForwards; - allowOfsDelta = rc.allowOfsDelta; - allowPushOptions = rc.allowPushOptions; - maxCommandBytes = rc.maxCommandBytes; - maxDiscardBytes = rc.maxDiscardBytes; - advertiseRefsHook = AdvertiseRefsHook.DEFAULT; - refFilter = RefFilter.DEFAULT; - advertisedHaves = new HashSet<>(); - clientShallowCommits = new HashSet<>(); - signedPushConfig = rc.signedPush; - } - - /** Configuration for receive operations. */ - protected static class ReceiveConfig { - final boolean allowCreates; - final boolean allowDeletes; - final boolean allowNonFastForwards; - final boolean allowOfsDelta; - final boolean allowPushOptions; - final long maxCommandBytes; - final long maxDiscardBytes; - final SignedPushConfig signedPush; - - ReceiveConfig(Config config) { - allowCreates = true; - allowDeletes = !config.getBoolean("receive", "denydeletes", false); //$NON-NLS-1$ //$NON-NLS-2$ - allowNonFastForwards = !config.getBoolean("receive", //$NON-NLS-1$ - "denynonfastforwards", false); //$NON-NLS-1$ - allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset", //$NON-NLS-1$ //$NON-NLS-2$ - true); - allowPushOptions = config.getBoolean("receive", "pushoptions", //$NON-NLS-1$ //$NON-NLS-2$ - false); - maxCommandBytes = config.getLong("receive", //$NON-NLS-1$ - "maxCommandBytes", //$NON-NLS-1$ - 3 << 20); - maxDiscardBytes = config.getLong("receive", //$NON-NLS-1$ - "maxCommandDiscardBytes", //$NON-NLS-1$ - -1); - signedPush = SignedPushConfig.KEY.parse(config); - } - } - - /** - * 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. - } - } - } - } - - /** - * Get the process name used for pack lock messages. - * - * @return the process name used for pack lock messages. - */ - protected abstract String getLockMessageProcessName(); - - /** - * Get the repository this receive completes into. - * - * @return the repository this receive completes into. - * @deprecated use {@link ReceivePack#getRepository} - */ - @Deprecated - public abstract Repository getRepository(); - - /** - * Get the RevWalk instance used by this connection. - * - * @return the RevWalk instance used by this connection. - * @deprecated use {@link ReceivePack#getRevWalk} - */ - @Deprecated - public abstract RevWalk getRevWalk(); - - /** - * 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. - * @deprecated use {@link ReceivePack#getAdvertisedRefs} - */ - @Deprecated - public abstract Map getAdvertisedRefs(); - - /** - * Set the refs advertised by this ReceivePack. - *

- * Intended to be called from a - * {@link org.eclipse.jgit.transport.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. - * @deprecated use {@link ReceivePack#setAdvertisedRefs} - */ - @Deprecated - public abstract void setAdvertisedRefs(Map allRefs, Set additionalHaves); - - /** - * 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; - } - - /** - * Whether this instance will validate all referenced, but not supplied by - * the client, objects are reachable from another reference. - * - * @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 org.eclipse.jgit.transport.AdvertiseRefsHook} or - * {@link org.eclipse.jgit.transport.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; - } - - /** - * Whether this class expects a bi-directional pipe opened between the - * client and itself. - * - * @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; - } - - /** - * Whether 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. - * - * @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(boolean twoWay) { - biDirectionalPipe = twoWay; - } - - /** - * Whether there is data expected after the pack footer. - * - * @return {@code true} if there is data expected after the pack footer. - */ - public boolean isExpectDataAfterPackFooter() { - return expectDataAfterPackFooter; - } - - /** - * Whether there is additional data in InputStream after pack. - * - * @param e - * {@code true} if there is additional data in InputStream after - * pack. - */ - public void setExpectDataAfterPackFooter(boolean e) { - expectDataAfterPackFooter = e; - } - - /** - * Whether this instance will verify received objects are formatted - * correctly. - * - * @return {@code 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 objectChecker != null; - } - - /** - * Whether to enable checking received objects - * - * @param check - * {@code true} to enable checking received objects; false to - * assume all received objects are valid. - * @see #setObjectChecker(ObjectChecker) - */ - public void setCheckReceivedObjects(boolean check) { - if (check && objectChecker == null) - setObjectChecker(new ObjectChecker()); - else if (!check && objectChecker != null) - setObjectChecker(null); - } - - /** - * Set the object checking instance to verify each received object with - * - * @param impl - * if non-null the object checking instance to verify each - * received object with; null to disable object checking. - * @since 3.4 - */ - public void setObjectChecker(ObjectChecker impl) { - objectChecker = impl; - } - - /** - * Whether the client can request refs to be created. - * - * @return {@code true} if the client can request refs to be created. - */ - public boolean isAllowCreates() { - return allowCreates; - } - - /** - * Whether to permit create ref commands to be processed. - * - * @param canCreate - * {@code true} to permit create ref commands to be processed. - */ - public void setAllowCreates(boolean canCreate) { - allowCreates = canCreate; - } - - /** - * Whether the client can request refs to be deleted. - * - * @return {@code true} if the client can request refs to be deleted. - */ - public boolean isAllowDeletes() { - return allowAnyDeletes; - } - - /** - * Whether to permit delete ref commands to be processed. - * - * @param canDelete - * {@code true} to permit delete ref commands to be processed. - */ - public void setAllowDeletes(boolean canDelete) { - allowAnyDeletes = canDelete; - } - - /** - * Whether the client can delete from {@code refs/heads/}. - * - * @return {@code true} if the client can delete from {@code refs/heads/}. - * @since 3.6 - */ - public boolean isAllowBranchDeletes() { - return allowBranchDeletes; - } - - /** - * Configure whether to permit deletion of branches from the - * {@code refs/heads/} namespace. - * - * @param canDelete - * {@code true} to permit deletion of branches from the - * {@code refs/heads/} namespace. - * @since 3.6 - */ - public void setAllowBranchDeletes(boolean canDelete) { - allowBranchDeletes = canDelete; - } - - /** - * Whether the client can request non-fast-forward updates of a ref, - * possibly making objects unreachable. - * - * @return {@code true} if the client can request non-fast-forward updates - * of a ref, possibly making objects unreachable. - */ - public boolean isAllowNonFastForwards() { - return allowNonFastForwards; - } - - /** - * Configure whether to permit the client to ask for non-fast-forward - * updates of an existing ref. - * - * @param canRewind - * {@code true} to permit the client to ask for non-fast-forward - * updates of an existing ref. - */ - public void setAllowNonFastForwards(boolean canRewind) { - allowNonFastForwards = canRewind; - } - - /** - * Whether the client's commands should be performed as a single atomic - * transaction. - * - * @return {@code true} if the client's commands should be performed as a - * single atomic transaction. - * @since 4.4 - */ - public boolean isAtomic() { - return atomic; - } - - /** - * Configure whether to perform the client's commands as a single atomic - * transaction. - * - * @param atomic - * {@code true} to perform the client's commands as a single - * atomic transaction. - * @since 4.4 - */ - public void setAtomic(boolean atomic) { - this.atomic = atomic; - } - - /** - * Get identity of the user making the changes in the reflog. - * - * @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(PersonIdent pi) { - refLogIdent = pi; - } - - /** - * Get the hook used while advertising the refs to the client - * - * @return the hook used while advertising the refs to the client - */ - public AdvertiseRefsHook getAdvertiseRefsHook() { - return advertiseRefsHook; - } - - /** - * Get the filter used while advertising the refs to the client - * - * @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 org.eclipse.jgit.transport.AdvertiseRefsHook} chooses to - * call {@link #setAdvertisedRefs(Map,Set)}, only refs set by this hook - * and selected by the {@link org.eclipse.jgit.transport.RefFilter} - * will be shown to the client. Clients may still attempt to create or - * update a reference not advertised by the configured - * {@link org.eclipse.jgit.transport.AdvertiseRefsHook}. These attempts - * should be rejected by a matching - * {@link org.eclipse.jgit.transport.PreReceiveHook}. - * - * @param advertiseRefsHook - * the hook; may be null to show all refs. - */ - public void setAdvertiseRefsHook(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 org.eclipse.jgit.transport.AdvertiseRefsHook} (if applicable). - * - * @param refFilter - * the filter; may be null to show all refs. - */ - public void setRefFilter(RefFilter refFilter) { - this.refFilter = refFilter != null ? refFilter : RefFilter.DEFAULT; - } - - /** - * Get timeout (in seconds) before aborting an IO operation. - * - * @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(int seconds) { - timeout = seconds; - } - - /** - * Set the maximum number of command bytes to read from the client. - * - * @param limit - * command limit in bytes; if 0 there is no limit. - * @since 4.7 - */ - public void setMaxCommandBytes(long limit) { - maxCommandBytes = limit; - } - - /** - * Set the maximum number of command bytes to discard from the client. - *

- * Discarding remaining bytes allows this instance to consume the rest of - * the command block and send a human readable over-limit error via the - * side-band channel. If the client sends an excessive number of bytes this - * limit kicks in and the instance disconnects, resulting in a non-specific - * 'pipe closed', 'end of stream', or similar generic error at the client. - *

- * When the limit is set to {@code -1} the implementation will default to - * the larger of {@code 3 * maxCommandBytes} or {@code 3 MiB}. - * - * @param limit - * discard limit in bytes; if 0 there is no limit; if -1 the - * implementation tries to set a reasonable default. - * @since 4.7 - */ - public void setMaxCommandDiscardBytes(long limit) { - maxDiscardBytes = limit; - } - - /** - * 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(long limit) { - maxObjectSizeLimit = limit; - } - - /** - * Set the maximum allowed pack size. - *

- * A pack exceeding this size will be rejected. - * - * @param limit - * the pack size limit, in bytes - * @since 3.3 - */ - public void setMaxPackSizeLimit(long limit) { - if (limit < 0) - throw new IllegalArgumentException(MessageFormat.format( - JGitText.get().receivePackInvalidLimit, Long.valueOf(limit))); - maxPackSizeLimit = limit; - } - - /** - * Check whether the client expects a side-band stream. - * - * @return true if the client has advertised a side-band capability, false - * otherwise. - * @throws org.eclipse.jgit.transport.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 { - checkRequestWasRead(); - return enabledCapabilities.contains(CAPABILITY_SIDE_BAND_64K); - } - - /** - * Whether clients may request avoiding noisy progress messages. - * - * @return true if clients may request avoiding noisy progress messages. - * @since 4.0 - */ - public boolean isAllowQuiet() { - return allowQuiet; - } - - /** - * Configure if clients may request the server skip noisy messages. - * - * @param allow - * true to allow clients to request quiet behavior; false to - * refuse quiet behavior and send messages anyway. This may be - * necessary if processing is slow and the client-server network - * connection can timeout. - * @since 4.0 - */ - public void setAllowQuiet(boolean allow) { - allowQuiet = allow; - } - - /** - * Whether the server supports receiving push options. - * - * @return true if the server supports receiving push options. - * @since 4.5 - */ - public boolean isAllowPushOptions() { - return allowPushOptions; - } - - /** - * Configure if the server supports receiving push options. - * - * @param allow - * true to optionally accept option strings from the client. - * @since 4.5 - */ - public void setAllowPushOptions(boolean allow) { - allowPushOptions = allow; - } - - /** - * True if the client wants less verbose output. - * - * @return true if the client has requested the server to be less verbose. - * @throws org.eclipse.jgit.transport.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. - * @since 4.0 - */ - public boolean isQuiet() throws RequestNotYetReadException { - checkRequestWasRead(); - return quiet; - } - - /** - * Set the configuration for push certificate verification. - * - * @param cfg - * new configuration; if this object is null or its {@link - * SignedPushConfig#getCertNonceSeed()} is null, push certificate - * verification will be disabled. - * @since 4.1 - */ - public void setSignedPushConfig(SignedPushConfig cfg) { - signedPushConfig = cfg; - } - - private PushCertificateParser getPushCertificateParser() { - if (pushCertificateParser == null) { - pushCertificateParser = new PushCertificateParser(db, signedPushConfig); - } - return pushCertificateParser; - } - - /** - * Get the user agent of the client. - *

- * If the client is new enough to use {@code agent=} capability that value - * will be returned. Older HTTP clients may also supply their version using - * the HTTP {@code User-Agent} header. The capability overrides the HTTP - * header if both are available. - *

- * When an HTTP request has been received this method returns the HTTP - * {@code User-Agent} header value until capabilities have been parsed. - * - * @return user agent supplied by the client. Available only if the client - * is new enough to advertise its user agent. - * @since 4.0 - */ - public String getPeerUserAgent() { - return UserAgent.getAgent(enabledCapabilities, userAgent); - } - - /** - * Get all of the command received by the current request. - * - * @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 org.eclipse.jgit.transport.PreReceiveHook}s should always try to - * use - * {@link org.eclipse.jgit.transport.ReceiveCommand#setResult(Result, String)} - * with a result status of - * {@link org.eclipse.jgit.transport.ReceiveCommand.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(String what) { - if (refs == null) { - if (advertiseError == null) - advertiseError = new StringBuilder(); - advertiseError.append(what).append('\n'); - } else { - msgOutWrapper.write(Constants.encode("error: " + what + "\n")); //$NON-NLS-1$ //$NON-NLS-2$ - } - } - - private void fatalError(String msg) { - if (errOut != null) { - try { - errOut.write(Constants.encode(msg)); - errOut.flush(); - } catch (IOException e) { - // Ignore write failures - } - } else { - sendError(msg); - } - } - - /** - * 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(String what) { - msgOutWrapper.write(Constants.encode(what + "\n")); //$NON-NLS-1$ - } - - /** - * Get an underlying stream for sending messages to the client. - * - * @return an underlying stream for sending messages to the client. - */ - public OutputStream getMessageOutputStream() { - return msgOutWrapper; - } - - /** - * Get whether or not a pack has been received. - * - * This can be called before calling {@link #getPackSize()} to avoid causing - * {@code IllegalStateException} when the pack size was not set because no - * pack was received. - * - * @return true if a pack has been received. - * @since 5.6 - */ - public boolean hasReceivedPack() { - return packSize != null; - } - - /** - * Get the size of the received pack file including the index size. - * - * This can only be called if the pack is already received. - * - * @return the size of the received pack including index size - * @throws java.lang.IllegalStateException - * if called before the pack has been received - * @since 3.3 - */ - public long getPackSize() { - if (packSize != null) - return packSize.longValue(); - throw new IllegalStateException(JGitText.get().packSizeNotSetYet); - } - - /** - * Get the commits from the client's shallow file. - * - * @return if the client is a shallow repository, the list of edge commits - * that define the client's shallow boundary. Empty set if the client - * is earlier than Git 1.9, or is a full clone. - * @since 3.5 - */ - protected Set getClientShallowCommits() { - return clientShallowCommits; - } - - /** - * Whether any commands to be executed have been read. - * - * @return {@code true} if any commands to be executed have been read. - */ - protected boolean hasCommands() { - return !commands.isEmpty(); - } - - /** - * Whether an error occurred that should be advertised. - * - * @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"); //$NON-NLS-1$ - 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<>(); - } - - /** - * Get advertised refs, or the default if not explicitly advertised. - * - * @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 java.io.IOException - * an error occurred during unpacking or connectivity checking. - */ - protected void receivePackAndCheckConnectivity() throws IOException { - receivePack(); - if (needCheckConnectivity()) { - checkSubmodules(); - checkConnectivity(); - } - parser = null; - } - - /** - * Unlock the pack written by this object. - * - * @throws java.io.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 java.io.IOException - * the formatter failed to write an advertisement. - * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException - * the hook denied advertisement. - */ - public void sendAdvertisedRefs(RefAdvertiser adv) - throws IOException, ServiceMayNotContinueException { - if (advertiseError != null) { - adv.writeOne("ERR " + advertiseError); //$NON-NLS-1$ - return; - } - - try { - advertiseRefsHook.advertiseRefs(this); - } catch (ServiceMayNotContinueException fail) { - if (fail.getMessage() != null) { - adv.writeOne("ERR " + fail.getMessage()); //$NON-NLS-1$ - fail.setOutput(); - } - throw fail; - } - - adv.init(db); - adv.advertiseCapability(CAPABILITY_SIDE_BAND_64K); - adv.advertiseCapability(CAPABILITY_DELETE_REFS); - adv.advertiseCapability(CAPABILITY_REPORT_STATUS); - if (allowQuiet) - adv.advertiseCapability(CAPABILITY_QUIET); - String nonce = getPushCertificateParser().getAdvertiseNonce(); - if (nonce != null) { - adv.advertiseCapability(nonce); - } - if (db.getRefDatabase().performsAtomicTransactions()) - adv.advertiseCapability(CAPABILITY_ATOMIC); - if (allowOfsDelta) - adv.advertiseCapability(CAPABILITY_OFS_DELTA); - if (allowPushOptions) { - adv.advertiseCapability(CAPABILITY_PUSH_OPTIONS); - } - adv.advertiseCapability(OPTION_AGENT, UserAgent.get()); - adv.send(getAdvertisedOrDefaultRefs().values()); - for (ObjectId obj : advertisedHaves) - adv.advertiseHave(obj); - if (adv.isEmpty()) - adv.advertiseId(ObjectId.zeroId(), "capabilities^{}"); //$NON-NLS-1$ - adv.end(); - } - - /** - * Returns the statistics on the received pack if available. This should be - * called after {@link #receivePack} is called. - * - * @return ReceivedPackStatistics - * @since 4.6 - */ - @Nullable - public ReceivedPackStatistics getReceivedPackStatistics() { - return stats; - } - - /** - * Receive a list of commands from the input. - * - * @throws java.io.IOException - */ - protected void recvCommands() throws IOException { - PacketLineIn pck = maxCommandBytes > 0 - ? new PacketLineIn(rawIn, maxCommandBytes) - : pckIn; - PushCertificateParser certParser = getPushCertificateParser(); - boolean firstPkt = true; - try { - for (;;) { - String line; - try { - line = pck.readString(); - } catch (EOFException eof) { - if (commands.isEmpty()) - return; - throw eof; - } - if (PacketLineIn.isEnd(line)) { - break; - } - - if (line.length() >= 48 && line.startsWith("shallow ")) { //$NON-NLS-1$ - parseShallow(line.substring(8, 48)); - continue; - } - - if (firstPkt) { - firstPkt = false; - FirstCommand firstLine = FirstCommand.fromLine(line); - enabledCapabilities = firstLine.getCapabilities(); - line = firstLine.getLine(); - enableCapabilities(); - - if (line.equals(GitProtocolConstants.OPTION_PUSH_CERT)) { - certParser.receiveHeader(pck, !isBiDirectionalPipe()); - continue; - } - } - - if (line.equals(PushCertificateParser.BEGIN_SIGNATURE)) { - certParser.receiveSignature(pck); - continue; - } - - ReceiveCommand cmd = parseCommand(line); - if (cmd.getRefName().equals(Constants.HEAD)) { - cmd.setResult(Result.REJECTED_CURRENT_BRANCH); - } else { - cmd.setRef(refs.get(cmd.getRefName())); - } - commands.add(cmd); - if (certParser.enabled()) { - certParser.addCommand(cmd); - } - } - pushCert = certParser.build(); - if (hasCommands()) { - readPostCommands(pck); - } - } catch (PackProtocolException e) { - discardCommands(); - fatalError(e.getMessage()); - throw e; - } catch (InputOverLimitIOException e) { - String msg = JGitText.get().tooManyCommands; - discardCommands(); - fatalError(msg); - throw new PackProtocolException(msg); - } - } - - private void discardCommands() { - if (sideBand) { - long max = maxDiscardBytes; - if (max < 0) { - max = Math.max(3 * maxCommandBytes, 3L << 20); - } - try { - new PacketLineIn(rawIn, max).discardUntilEnd(); - } catch (IOException e) { - // Ignore read failures attempting to discard. - } - } - } - - private void parseShallow(String idStr) throws PackProtocolException { - ObjectId id; - try { - id = ObjectId.fromString(idStr); - } catch (InvalidObjectIdException e) { - throw new PackProtocolException(e.getMessage(), e); - } - clientShallowCommits.add(id); - } - - static ReceiveCommand parseCommand(String line) throws PackProtocolException { - if (line == null || line.length() < 83) { - throw new PackProtocolException( - JGitText.get().errorInvalidProtocolWantedOldNewRef); - } - String oldStr = line.substring(0, 40); - String newStr = line.substring(41, 81); - ObjectId oldId, newId; - try { - oldId = ObjectId.fromString(oldStr); - newId = ObjectId.fromString(newStr); - } catch (InvalidObjectIdException e) { - throw new PackProtocolException( - JGitText.get().errorInvalidProtocolWantedOldNewRef, e); - } - String name = line.substring(82); - if (!Repository.isValidRefName(name)) { - throw new PackProtocolException( - JGitText.get().errorInvalidProtocolWantedOldNewRef); - } - return new ReceiveCommand(oldId, newId, name); - } - - /** - * @param in - * request stream. - * @throws IOException - * request line cannot be read. - */ - void readPostCommands(PacketLineIn in) throws IOException { - // Do nothing by default. - } - - /** - * Enable capabilities based on a previously read capabilities line. - */ - protected void enableCapabilities() { - sideBand = isCapabilityEnabled(CAPABILITY_SIDE_BAND_64K); - quiet = allowQuiet && isCapabilityEnabled(CAPABILITY_QUIET); - if (sideBand) { - OutputStream out = rawOut; - - rawOut = new SideBandOutputStream(CH_DATA, MAX_BUF, out); - msgOut = new SideBandOutputStream(CH_PROGRESS, MAX_BUF, out); - errOut = new SideBandOutputStream(CH_ERROR, MAX_BUF, out); - - pckOut = new PacketLineOut(rawOut); - pckOut.setFlushOnEnd(false); - } - } - - /** - * Check if the peer requested a capability. - * - * @param name - * protocol name identifying the capability. - * @return true if the peer requested the capability to be enabled. - */ - protected boolean isCapabilityEnabled(String name) { - return enabledCapabilities.contains(name); - } - - void checkRequestWasRead() { - if (enabledCapabilities == null) - throw new RequestNotYetReadException(); - } - - /** - * Whether a pack is expected based on the list of commands. - * - * @return {@code true} if a pack is expected based on the list of commands. - */ - protected boolean needPack() { - for (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 && !quiet) - resolving = new SideBandProgressMonitor(msgOut); - - try (ObjectInserter ins = db.newObjectInserter()) { - String lockMsg = "jgit receive-pack"; //$NON-NLS-1$ - if (getRefLogIdent() != null) - lockMsg += " from " + getRefLogIdent().toExternalString(); //$NON-NLS-1$ - - parser = ins.newPackParser(packInputStream()); - parser.setAllowThin(true); - parser.setNeedNewObjectIds(checkReferencedIsReachable); - parser.setNeedBaseObjectIds(checkReferencedIsReachable); - parser.setCheckEofAfterPackFooter(!biDirectionalPipe - && !isExpectDataAfterPackFooter()); - parser.setExpectDataAfterPackFooter(isExpectDataAfterPackFooter()); - parser.setObjectChecker(objectChecker); - parser.setLockMessage(lockMsg); - parser.setMaxObjectSizeLimit(maxObjectSizeLimit); - packLock = parser.parse(receiving, resolving); - packSize = Long.valueOf(parser.getPackSize()); - stats = parser.getReceivedPackStatistics(); - ins.flush(); - } - - if (timeoutIn != null) - timeoutIn.setTimeout(timeout * 1000); - } - - private InputStream packInputStream() { - InputStream packIn = rawIn; - if (maxPackSizeLimit >= 0) { - packIn = new LimitedInputStream(packIn, maxPackSizeLimit) { - @Override - protected void limitExceeded() throws TooLargePackException { - throw new TooLargePackException(limit); - } - }; - } - return packIn; - } - - private boolean needCheckConnectivity() { - return isCheckReceivedObjects() - || isCheckReferencedObjectsAreReachable() - || !getClientShallowCommits().isEmpty(); - } - - private void checkSubmodules() - throws IOException { - ObjectDatabase odb = db.getObjectDatabase(); - if (objectChecker == null) { - return; - } - for (GitmoduleEntry entry : objectChecker.getGitsubmodules()) { - AnyObjectId blobId = entry.getBlobId(); - ObjectLoader blob = odb.open(blobId, Constants.OBJ_BLOB); - - try { - SubmoduleValidator.assertValidGitModulesFile( - new String(blob.getBytes(), UTF_8)); - } catch (LargeObjectException | SubmoduleValidationException e) { - throw new IOException(e); - } - } - } - - private void checkConnectivity() throws IOException { - ObjectIdSubclassMap baseObjects = null; - ObjectIdSubclassMap providedObjects = null; - ProgressMonitor checking = NullProgressMonitor.INSTANCE; - if (sideBand && !quiet) { - SideBandProgressMonitor m = new SideBandProgressMonitor(msgOut); - m.setDelayStart(750, TimeUnit.MILLISECONDS); - checking = m; - } - - if (checkReferencedIsReachable) { - baseObjects = parser.getBaseObjectIds(); - providedObjects = parser.getNewObjectIds(); - } - parser = null; - - try (ObjectWalk ow = new ObjectWalk(db)) { - if (baseObjects != null) { - ow.sort(RevSort.TOPO); - if (!baseObjects.isEmpty()) - ow.sort(RevSort.BOUNDARY, true); - } - - for (ReceiveCommand cmd : commands) { - if (cmd.getResult() != Result.NOT_ATTEMPTED) - continue; - if (cmd.getType() == ReceiveCommand.Type.DELETE) - continue; - ow.markStart(ow.parseAny(cmd.getNewId())); - } - for (ObjectId have : advertisedHaves) { - RevObject o = ow.parseAny(have); - ow.markUninteresting(o); - - if (baseObjects != null && !baseObjects.isEmpty()) { - o = ow.peel(o); - if (o instanceof RevCommit) - o = ((RevCommit) o).getTree(); - if (o instanceof RevTree) - ow.markUninteresting(o); - } - } - - checking.beginTask(JGitText.get().countingObjects, - ProgressMonitor.UNKNOWN); - RevCommit c; - while ((c = ow.next()) != null) { - checking.update(1); - if (providedObjects != null // - && !c.has(RevFlag.UNINTERESTING) // - && !providedObjects.contains(c)) - throw new MissingObjectException(c, Constants.TYPE_COMMIT); - } - - RevObject o; - while ((o = ow.nextObject()) != null) { - checking.update(1); - if (o.has(RevFlag.UNINTERESTING)) { - continue; - } - - if (providedObjects != null) { - if (providedObjects.contains(o)) { - continue; - } - throw new MissingObjectException(o, o.getType()); - } - - if (o instanceof RevBlob && !db.getObjectDatabase().has(o)) - throw new MissingObjectException(o, Constants.TYPE_BLOB); - } - checking.endTask(); - - if (baseObjects != null) { - 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 (ReceiveCommand cmd : commands) { - final Ref ref = cmd.getRef(); - if (cmd.getResult() != Result.NOT_ATTEMPTED) - continue; - - RevObject newObj = null; - if (cmd.getType() == ReceiveCommand.Type.CREATE - || cmd.getType() == ReceiveCommand.Type.UPDATE) { - try { - newObj = walk.parseAny(cmd.getNewId()); - } catch (IOException e) { - cmd.setResult(Result.REJECTED_MISSING_OBJECT, - cmd.getNewId().name()); - continue; - } - if (cmd.getRefName().startsWith(Constants.R_HEADS) - && !(newObj instanceof RevCommit)) { - cmd.setResult(Result.REJECTED_OTHER_REASON, - JGitText.get().nonCommitToHeads); - continue; - } - } - - if (cmd.getType() == ReceiveCommand.Type.DELETE) { - if (!isAllowDeletes()) { - // Deletes are not supported on this repository. - cmd.setResult(Result.REJECTED_NODELETE); - continue; - } - if (!isAllowBranchDeletes() - && ref.getName().startsWith(Constants.R_HEADS)) { - // Branches cannot be deleted, but other refs can. - 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, - JGitText.get().refAlreadyExists); - continue; - } - } - - if (cmd.getType() == ReceiveCommand.Type.DELETE && ref != null) { - ObjectId id = ref.getObjectId(); - if (id == null) { - id = ObjectId.zeroId(); - } - if (!ObjectId.zeroId().equals(cmd.getOldId()) - && !id.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; - } - ObjectId id = ref.getObjectId(); - if (id == null) { - // We cannot update unborn branch - cmd.setResult(Result.REJECTED_OTHER_REASON, - JGitText.get().cannotUpdateUnbornBranch); - continue; - } - - if (!id.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; - try { - oldObj = walk.parseAny(cmd.getOldId()); - } catch (IOException e) { - cmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd - .getOldId().name()); - continue; - } - - if (oldObj instanceof RevCommit && newObj instanceof RevCommit) { - try { - if (walk.isMergedInto((RevCommit) oldObj, - (RevCommit) newObj)) - cmd.setTypeFastForwardUpdate(); - else - 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.getType() == ReceiveCommand.Type.UPDATE_NONFASTFORWARD - && !isAllowNonFastForwards()) { - cmd.setResult(Result.REJECTED_NONFASTFORWARD); - continue; - } - } - - if (!cmd.getRefName().startsWith(Constants.R_REFS) - || !Repository.isValidRefName(cmd.getRefName())) { - cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().funnyRefname); - } - } - } - - /** - * Whether any commands have been rejected so far. - * - * @return if any commands have been rejected so far. - * @since 3.6 - */ - protected boolean anyRejects() { - for (ReceiveCommand cmd : commands) { - if (cmd.getResult() != Result.NOT_ATTEMPTED && cmd.getResult() != Result.OK) - return true; - } - return false; - } - - /** - * Set the result to fail for any command that was not processed yet. - * - * @since 3.6 - */ - protected void failPendingCommands() { - ReceiveCommand.abort(commands); - } - - /** - * 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(Result want) { - return ReceiveCommand.filter(commands, want); - } - - /** - * Execute commands to update references. - */ - protected void executeCommands() { - List toApply = filterCommands(Result.NOT_ATTEMPTED); - if (toApply.isEmpty()) - return; - - ProgressMonitor updating = NullProgressMonitor.INSTANCE; - if (sideBand) { - SideBandProgressMonitor pm = new SideBandProgressMonitor(msgOut); - pm.setDelayStart(250, TimeUnit.MILLISECONDS); - updating = pm; - } - - BatchRefUpdate batch = db.getRefDatabase().newBatchUpdate(); - batch.setAllowNonFastForwards(isAllowNonFastForwards()); - batch.setAtomic(isAtomic()); - batch.setRefLogIdent(getRefLogIdent()); - batch.setRefLogMessage("push", true); //$NON-NLS-1$ - batch.addCommand(toApply); - try { - batch.setPushCertificate(getPushCertificate()); - batch.execute(walk, updating); - } catch (IOException err) { - for (ReceiveCommand cmd : toApply) { - if (cmd.getResult() == Result.NOT_ATTEMPTED) - cmd.reject(err); - } - } - } - - /** - * 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 java.io.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()); //$NON-NLS-1$ - if (forClient) { - for (ReceiveCommand cmd : commands) { - out.sendString("ng " + cmd.getRefName() //$NON-NLS-1$ - + " n/a (unpacker error)"); //$NON-NLS-1$ - } - } - return; - } - - if (forClient) - out.sendString("unpack ok"); //$NON-NLS-1$ - for (ReceiveCommand cmd : commands) { - if (cmd.getResult() == Result.OK) { - if (forClient) - out.sendString("ok " + cmd.getRefName()); //$NON-NLS-1$ - continue; - } - - final StringBuilder r = new StringBuilder(); - if (forClient) - r.append("ng ").append(cmd.getRefName()).append(" "); //$NON-NLS-1$ //$NON-NLS-2$ - else - r.append(" ! [rejected] ").append(cmd.getRefName()).append(" ("); //$NON-NLS-1$ //$NON-NLS-2$ - - switch (cmd.getResult()) { - case NOT_ATTEMPTED: - r.append("server bug; ref not processed"); //$NON-NLS-1$ - break; - - case REJECTED_NOCREATE: - r.append("creation prohibited"); //$NON-NLS-1$ - break; - - case REJECTED_NODELETE: - r.append("deletion prohibited"); //$NON-NLS-1$ - break; - - case REJECTED_NONFASTFORWARD: - r.append("non-fast forward"); //$NON-NLS-1$ - break; - - case REJECTED_CURRENT_BRANCH: - r.append("branch is currently checked out"); //$NON-NLS-1$ - break; - - case REJECTED_MISSING_OBJECT: - if (cmd.getMessage() == null) - r.append("missing object(s)"); //$NON-NLS-1$ - else if (cmd.getMessage().length() == Constants.OBJECT_ID_STRING_LENGTH) { - r.append("object "); //$NON-NLS-1$ - r.append(cmd.getMessage()); - r.append(" missing"); //$NON-NLS-1$ - } else - r.append(cmd.getMessage()); - break; - - case REJECTED_OTHER_REASON: - if (cmd.getMessage() == null) - r.append("unspecified reason"); //$NON-NLS-1$ - else - r.append(cmd.getMessage()); - break; - - case LOCK_FAILURE: - r.append("failed to lock"); //$NON-NLS-1$ - break; - - case OK: - // We shouldn't have reached this case (see 'ok' case above). - continue; - } - if (!forClient) - r.append(")"); //$NON-NLS-1$ - out.sendString(r.toString()); - } - } - - /** - * Close and flush (if necessary) the underlying streams. - * - * @throws java.io.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 java.io.IOException - * the pack could not be unlocked. - */ - protected void release() throws IOException { - walk.close(); - unlockPack(); - timeoutIn = null; - rawIn = null; - rawOut = null; - msgOut = null; - pckIn = null; - pckOut = null; - refs = null; - // Keep the capabilities. If responses are sent after this release - // we need to remember at least whether sideband communication has to be - // used - 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/PushCertificateParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java index 89c1a9378..3653879c7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java @@ -43,7 +43,7 @@ package org.eclipse.jgit.transport; -import static org.eclipse.jgit.transport.BaseReceivePack.parseCommand; +import static org.eclipse.jgit.transport.ReceivePack.parseCommand; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_CERT; import java.io.EOFException; 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 a9a995cd3..29c01fea7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java @@ -65,7 +65,7 @@ import org.eclipse.jgit.revwalk.RevWalk; /** * A command being processed by - * {@link org.eclipse.jgit.transport.BaseReceivePack}. + * {@link org.eclipse.jgit.transport.ReceivePack}. *

* This command instance roughly translates to the server side representation of * the {@link org.eclipse.jgit.transport.RemoteRefUpdate} created by the client. @@ -290,7 +290,7 @@ public class ReceiveCommand { /** * Create a new command for - * {@link org.eclipse.jgit.transport.BaseReceivePack}. + * {@link org.eclipse.jgit.transport.ReceivePack}. * * @param oldId * the expected old object id; must not be null. Use @@ -334,7 +334,7 @@ public class ReceiveCommand { /** * Create a new command for - * {@link org.eclipse.jgit.transport.BaseReceivePack}. + * {@link org.eclipse.jgit.transport.ReceivePack}. * * @param oldId * the old object id; must not be null. Use @@ -770,7 +770,7 @@ public class ReceiveCommand { * receive-pack session. * @since 2.0 */ - public void execute(BaseReceivePack rp) { + public void execute(ReceivePack rp) { try { String expTarget = getOldSymref(); boolean detach = getNewSymref() != null 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 d6adf1e0d..359cf70ea 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java @@ -43,132 +43,1972 @@ package org.eclipse.jgit.transport; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.lib.Constants.HEAD; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC; +import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_DELETE_REFS; +import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_OFS_DELTA; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_OPTIONS; +import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_QUIET; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS; +import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SIDE_BAND_64K; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT; +import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA; +import static org.eclipse.jgit.transport.SideBandOutputStream.CH_ERROR; +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.annotations.Nullable; +import org.eclipse.jgit.errors.InvalidObjectIdException; +import org.eclipse.jgit.errors.LargeObjectException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.PackProtocolException; +import org.eclipse.jgit.errors.TooLargePackException; import org.eclipse.jgit.errors.UnpackException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.file.PackLock; +import org.eclipse.jgit.internal.submodule.SubmoduleValidator; +import org.eclipse.jgit.internal.submodule.SubmoduleValidator.SubmoduleValidationException; +import org.eclipse.jgit.internal.transport.parser.FirstCommand; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.GitmoduleEntry; import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectChecker; +import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdSubclassMap; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectLoader; +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.transport.PacketLineIn.InputOverLimitIOException; 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.LimitedInputStream; +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 extends BaseReceivePack { +public class ReceivePack { + /** + * Data in the first line of a request, the line itself plus capabilities. + * + * @deprecated Use {@link FirstCommand} instead. + */ + @Deprecated + public static class FirstLine { + private final FirstCommand command; + + /** + * Parse the first line of a receive-pack request. + * + * @param line + * line from the client. + */ + public FirstLine(String line) { + command = FirstCommand.fromLine(line); + } + + /** @return non-capabilities part of the line. */ + public String getLine() { + return command.getLine(); + } + + /** @return capabilities parsed from the line. */ + public Set getCapabilities() { + return command.getCapabilities(); + } + } + + /** 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; + + /** Expecting data after the pack footer */ + private boolean expectDataAfterPackFooter; + + /** Should an incoming transfer validate objects? */ + private ObjectChecker objectChecker; + + /** Should an incoming transfer permit create requests? */ + private boolean allowCreates; + + /** Should an incoming transfer permit delete requests? */ + private boolean allowAnyDeletes; + + private boolean allowBranchDeletes; + + /** Should an incoming transfer permit non-fast-forward requests? */ + private boolean allowNonFastForwards; + + /** Should an incoming transfer permit push options? **/ + private boolean allowPushOptions; + + /** + * Should the requested ref updates be performed as a single atomic + * transaction? + */ + private boolean atomic; + + private boolean allowOfsDelta; + + private boolean allowQuiet = true; + + /** 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. */ + private InputStream rawIn; + + /** Raw output stream. */ + private OutputStream rawOut; + + /** Optional message output stream. */ + private OutputStream msgOut; + + private SideBandOutputStream errOut; + + /** Packet line input stream around {@link #rawIn}. */ + private PacketLineIn pckIn; + + /** Packet line output stream around {@link #rawOut}. */ + private PacketLineOut pckOut; + + private final MessageOutputWrapper msgOutWrapper = new MessageOutputWrapper(); + + 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; + + String userAgent; + + private Set clientShallowCommits; + + private List commands; + + private long maxCommandBytes; + + private long maxDiscardBytes; + + private StringBuilder advertiseError; + + /** + * If {@link BasePackPushConnection#CAPABILITY_SIDE_BAND_64K} is enabled. + */ + private boolean sideBand; + + private boolean quiet; + + /** Lock around the received pack file, while updating refs. */ + private PackLock packLock; + + private boolean checkReferencedIsReachable; + + /** Git object size limit */ + private long maxObjectSizeLimit; + + /** Total pack size limit */ + private long maxPackSizeLimit = -1; + + /** The size of the received pack, including index size */ + private Long packSize; + + private PushCertificateParser pushCertificateParser; + + private SignedPushConfig signedPushConfig; + + private PushCertificate pushCert; + + private ReceivedPackStatistics stats; + /** Hook to validate the update commands before execution. */ private PreReceiveHook preReceive; - /** Hook to report on the commands after execution. */ - private PostReceiveHook postReceive; + /** Hook to report on the commands after execution. */ + private PostReceiveHook postReceive; + + /** If {@link BasePackPushConnection#CAPABILITY_REPORT_STATUS} is enabled. */ + private boolean reportStatus; + + /** Whether the client intends to use push options. */ + private boolean usePushOptions; + private List pushOptions; + + /** + * Create a new pack receive for an open repository. + * + * @param into + * the destination repository. + */ + public ReceivePack(Repository into) { + db = into; + walk = new RevWalk(db); + walk.setRetainBody(false); + + TransferConfig tc = db.getConfig().get(TransferConfig.KEY); + objectChecker = tc.newReceiveObjectChecker(); + + ReceiveConfig rc = db.getConfig().get(ReceiveConfig::new); + allowCreates = rc.allowCreates; + allowAnyDeletes = true; + allowBranchDeletes = rc.allowDeletes; + allowNonFastForwards = rc.allowNonFastForwards; + allowOfsDelta = rc.allowOfsDelta; + allowPushOptions = rc.allowPushOptions; + maxCommandBytes = rc.maxCommandBytes; + maxDiscardBytes = rc.maxDiscardBytes; + advertiseRefsHook = AdvertiseRefsHook.DEFAULT; + refFilter = RefFilter.DEFAULT; + advertisedHaves = new HashSet<>(); + clientShallowCommits = new HashSet<>(); + signedPushConfig = rc.signedPush; + preReceive = PreReceiveHook.NULL; + postReceive = PostReceiveHook.NULL; + } + + /** Configuration for receive operations. */ + private static class ReceiveConfig { + final boolean allowCreates; + + final boolean allowDeletes; + + final boolean allowNonFastForwards; + + final boolean allowOfsDelta; + + final boolean allowPushOptions; + + final long maxCommandBytes; + + final long maxDiscardBytes; + + final SignedPushConfig signedPush; + + ReceiveConfig(Config config) { + allowCreates = true; + allowDeletes = !config.getBoolean("receive", "denydeletes", false); //$NON-NLS-1$ //$NON-NLS-2$ + allowNonFastForwards = !config.getBoolean("receive", //$NON-NLS-1$ + "denynonfastforwards", false); //$NON-NLS-1$ + allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset", //$NON-NLS-1$ //$NON-NLS-2$ + true); + allowPushOptions = config.getBoolean("receive", "pushoptions", //$NON-NLS-1$ //$NON-NLS-2$ + false); + maxCommandBytes = config.getLong("receive", //$NON-NLS-1$ + "maxCommandBytes", //$NON-NLS-1$ + 3 << 20); + maxDiscardBytes = config.getLong("receive", //$NON-NLS-1$ + "maxCommandDiscardBytes", //$NON-NLS-1$ + -1); + signedPush = SignedPushConfig.KEY.parse(config); + } + } + + /** + * 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. + } + } + } + } + + /** + * Get the process name used for pack lock messages. + * + * @return the process name used for pack lock messages. + */ + protected String getLockMessageProcessName() { + return "jgit receive-pack"; //$NON-NLS-1$ + } + + /** + * Get the repository this receive completes into. + * + * @return the repository this receive completes into. + */ + public Repository getRepository() { + return db; + } + + /** + * Get the RevWalk instance used by this connection. + * + * @return the RevWalk instance used by this connection. + */ + public 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 Map getAdvertisedRefs() { + return refs; + } + + /** + * Set the refs advertised by this ReceivePack. + *

+ * Intended to be called from a + * {@link org.eclipse.jgit.transport.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); + advertisedHaves.clear(); + + Ref head = refs.get(HEAD); + if (head != null && head.isSymbolic()) { + refs.remove(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; + } + + /** + * Whether this instance will validate all referenced, but not supplied by + * the client, objects are reachable from another reference. + * + * @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 org.eclipse.jgit.transport.AdvertiseRefsHook} or + * {@link org.eclipse.jgit.transport.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; + } + + /** + * Whether this class expects a bi-directional pipe opened between the + * client and itself. + * + * @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; + } + + /** + * Whether 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. + * + * @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(boolean twoWay) { + biDirectionalPipe = twoWay; + } + + /** + * Whether there is data expected after the pack footer. + * + * @return {@code true} if there is data expected after the pack footer. + */ + public boolean isExpectDataAfterPackFooter() { + return expectDataAfterPackFooter; + } + + /** + * Whether there is additional data in InputStream after pack. + * + * @param e + * {@code true} if there is additional data in InputStream after + * pack. + */ + public void setExpectDataAfterPackFooter(boolean e) { + expectDataAfterPackFooter = e; + } + + /** + * Whether this instance will verify received objects are formatted + * correctly. + * + * @return {@code 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 objectChecker != null; + } + + /** + * Whether to enable checking received objects + * + * @param check + * {@code true} to enable checking received objects; false to + * assume all received objects are valid. + * @see #setObjectChecker(ObjectChecker) + */ + public void setCheckReceivedObjects(boolean check) { + if (check && objectChecker == null) + setObjectChecker(new ObjectChecker()); + else if (!check && objectChecker != null) + setObjectChecker(null); + } + + /** + * Set the object checking instance to verify each received object with + * + * @param impl + * if non-null the object checking instance to verify each + * received object with; null to disable object checking. + * @since 3.4 + */ + public void setObjectChecker(ObjectChecker impl) { + objectChecker = impl; + } + + /** + * Whether the client can request refs to be created. + * + * @return {@code true} if the client can request refs to be created. + */ + public boolean isAllowCreates() { + return allowCreates; + } + + /** + * Whether to permit create ref commands to be processed. + * + * @param canCreate + * {@code true} to permit create ref commands to be processed. + */ + public void setAllowCreates(boolean canCreate) { + allowCreates = canCreate; + } + + /** + * Whether the client can request refs to be deleted. + * + * @return {@code true} if the client can request refs to be deleted. + */ + public boolean isAllowDeletes() { + return allowAnyDeletes; + } + + /** + * Whether to permit delete ref commands to be processed. + * + * @param canDelete + * {@code true} to permit delete ref commands to be processed. + */ + public void setAllowDeletes(boolean canDelete) { + allowAnyDeletes = canDelete; + } + + /** + * Whether the client can delete from {@code refs/heads/}. + * + * @return {@code true} if the client can delete from {@code refs/heads/}. + * @since 3.6 + */ + public boolean isAllowBranchDeletes() { + return allowBranchDeletes; + } + + /** + * Configure whether to permit deletion of branches from the + * {@code refs/heads/} namespace. + * + * @param canDelete + * {@code true} to permit deletion of branches from the + * {@code refs/heads/} namespace. + * @since 3.6 + */ + public void setAllowBranchDeletes(boolean canDelete) { + allowBranchDeletes = canDelete; + } + + /** + * Whether the client can request non-fast-forward updates of a ref, + * possibly making objects unreachable. + * + * @return {@code true} if the client can request non-fast-forward updates + * of a ref, possibly making objects unreachable. + */ + public boolean isAllowNonFastForwards() { + return allowNonFastForwards; + } + + /** + * Configure whether to permit the client to ask for non-fast-forward + * updates of an existing ref. + * + * @param canRewind + * {@code true} to permit the client to ask for non-fast-forward + * updates of an existing ref. + */ + public void setAllowNonFastForwards(boolean canRewind) { + allowNonFastForwards = canRewind; + } + + /** + * Whether the client's commands should be performed as a single atomic + * transaction. + * + * @return {@code true} if the client's commands should be performed as a + * single atomic transaction. + * @since 4.4 + */ + public boolean isAtomic() { + return atomic; + } + + /** + * Configure whether to perform the client's commands as a single atomic + * transaction. + * + * @param atomic + * {@code true} to perform the client's commands as a single + * atomic transaction. + * @since 4.4 + */ + public void setAtomic(boolean atomic) { + this.atomic = atomic; + } + + /** + * Get identity of the user making the changes in the reflog. + * + * @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(PersonIdent pi) { + refLogIdent = pi; + } + + /** + * Get the hook used while advertising the refs to the client + * + * @return the hook used while advertising the refs to the client + */ + public AdvertiseRefsHook getAdvertiseRefsHook() { + return advertiseRefsHook; + } + + /** + * Get the filter used while advertising the refs to the client + * + * @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 org.eclipse.jgit.transport.AdvertiseRefsHook} chooses to + * call {@link #setAdvertisedRefs(Map,Set)}, only refs set by this hook + * and selected by the {@link org.eclipse.jgit.transport.RefFilter} + * will be shown to the client. Clients may still attempt to create or + * update a reference not advertised by the configured + * {@link org.eclipse.jgit.transport.AdvertiseRefsHook}. These attempts + * should be rejected by a matching + * {@link org.eclipse.jgit.transport.PreReceiveHook}. + * + * @param advertiseRefsHook + * the hook; may be null to show all refs. + */ + public void setAdvertiseRefsHook(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 org.eclipse.jgit.transport.AdvertiseRefsHook} (if applicable). + * + * @param refFilter + * the filter; may be null to show all refs. + */ + public void setRefFilter(RefFilter refFilter) { + this.refFilter = refFilter != null ? refFilter : RefFilter.DEFAULT; + } + + /** + * Get timeout (in seconds) before aborting an IO operation. + * + * @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(int seconds) { + timeout = seconds; + } + + /** + * Set the maximum number of command bytes to read from the client. + * + * @param limit + * command limit in bytes; if 0 there is no limit. + * @since 4.7 + */ + public void setMaxCommandBytes(long limit) { + maxCommandBytes = limit; + } + + /** + * Set the maximum number of command bytes to discard from the client. + *

+ * Discarding remaining bytes allows this instance to consume the rest of + * the command block and send a human readable over-limit error via the + * side-band channel. If the client sends an excessive number of bytes this + * limit kicks in and the instance disconnects, resulting in a non-specific + * 'pipe closed', 'end of stream', or similar generic error at the client. + *

+ * When the limit is set to {@code -1} the implementation will default to + * the larger of {@code 3 * maxCommandBytes} or {@code 3 MiB}. + * + * @param limit + * discard limit in bytes; if 0 there is no limit; if -1 the + * implementation tries to set a reasonable default. + * @since 4.7 + */ + public void setMaxCommandDiscardBytes(long limit) { + maxDiscardBytes = limit; + } + + /** + * 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(long limit) { + maxObjectSizeLimit = limit; + } + + /** + * Set the maximum allowed pack size. + *

+ * A pack exceeding this size will be rejected. + * + * @param limit + * the pack size limit, in bytes + * @since 3.3 + */ + public void setMaxPackSizeLimit(long limit) { + if (limit < 0) + throw new IllegalArgumentException( + MessageFormat.format(JGitText.get().receivePackInvalidLimit, + Long.valueOf(limit))); + maxPackSizeLimit = limit; + } + + /** + * Check whether the client expects a side-band stream. + * + * @return true if the client has advertised a side-band capability, false + * otherwise. + * @throws org.eclipse.jgit.transport.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 { + checkRequestWasRead(); + return enabledCapabilities.contains(CAPABILITY_SIDE_BAND_64K); + } + + /** + * Whether clients may request avoiding noisy progress messages. + * + * @return true if clients may request avoiding noisy progress messages. + * @since 4.0 + */ + public boolean isAllowQuiet() { + return allowQuiet; + } + + /** + * Configure if clients may request the server skip noisy messages. + * + * @param allow + * true to allow clients to request quiet behavior; false to + * refuse quiet behavior and send messages anyway. This may be + * necessary if processing is slow and the client-server network + * connection can timeout. + * @since 4.0 + */ + public void setAllowQuiet(boolean allow) { + allowQuiet = allow; + } + + /** + * Whether the server supports receiving push options. + * + * @return true if the server supports receiving push options. + * @since 4.5 + */ + public boolean isAllowPushOptions() { + return allowPushOptions; + } + + /** + * Configure if the server supports receiving push options. + * + * @param allow + * true to optionally accept option strings from the client. + * @since 4.5 + */ + public void setAllowPushOptions(boolean allow) { + allowPushOptions = allow; + } + + /** + * True if the client wants less verbose output. + * + * @return true if the client has requested the server to be less verbose. + * @throws org.eclipse.jgit.transport.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. + * @since 4.0 + */ + public boolean isQuiet() throws RequestNotYetReadException { + checkRequestWasRead(); + return quiet; + } + + /** + * Set the configuration for push certificate verification. + * + * @param cfg + * new configuration; if this object is null or its + * {@link SignedPushConfig#getCertNonceSeed()} is null, push + * certificate verification will be disabled. + * @since 4.1 + */ + public void setSignedPushConfig(SignedPushConfig cfg) { + signedPushConfig = cfg; + } + + private PushCertificateParser getPushCertificateParser() { + if (pushCertificateParser == null) { + pushCertificateParser = new PushCertificateParser(db, + signedPushConfig); + } + return pushCertificateParser; + } + + /** + * Get the user agent of the client. + *

+ * If the client is new enough to use {@code agent=} capability that value + * will be returned. Older HTTP clients may also supply their version using + * the HTTP {@code User-Agent} header. The capability overrides the HTTP + * header if both are available. + *

+ * When an HTTP request has been received this method returns the HTTP + * {@code User-Agent} header value until capabilities have been parsed. + * + * @return user agent supplied by the client. Available only if the client + * is new enough to advertise its user agent. + * @since 4.0 + */ + public String getPeerUserAgent() { + return UserAgent.getAgent(enabledCapabilities, userAgent); + } + + /** + * Get all of the command received by the current request. + * + * @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 org.eclipse.jgit.transport.PreReceiveHook}s should always try to + * use + * {@link org.eclipse.jgit.transport.ReceiveCommand#setResult(Result, String)} + * with a result status of + * {@link org.eclipse.jgit.transport.ReceiveCommand.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(String what) { + if (refs == null) { + if (advertiseError == null) + advertiseError = new StringBuilder(); + advertiseError.append(what).append('\n'); + } else { + msgOutWrapper.write(Constants.encode("error: " + what + "\n")); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + private void fatalError(String msg) { + if (errOut != null) { + try { + errOut.write(Constants.encode(msg)); + errOut.flush(); + } catch (IOException e) { + // Ignore write failures + } + } else { + sendError(msg); + } + } + + /** + * 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(String what) { + msgOutWrapper.write(Constants.encode(what + "\n")); //$NON-NLS-1$ + } + + /** + * Get an underlying stream for sending messages to the client. + * + * @return an underlying stream for sending messages to the client. + */ + public OutputStream getMessageOutputStream() { + return msgOutWrapper; + } + + /** + * Get whether or not a pack has been received. + * + * This can be called before calling {@link #getPackSize()} to avoid causing + * {@code IllegalStateException} when the pack size was not set because no + * pack was received. + * + * @return true if a pack has been received. + * @since 5.6 + */ + public boolean hasReceivedPack() { + return packSize != null; + } + + /** + * Get the size of the received pack file including the index size. + * + * This can only be called if the pack is already received. + * + * @return the size of the received pack including index size + * @throws java.lang.IllegalStateException + * if called before the pack has been received + * @since 3.3 + */ + public long getPackSize() { + if (packSize != null) + return packSize.longValue(); + throw new IllegalStateException(JGitText.get().packSizeNotSetYet); + } + + /** + * Get the commits from the client's shallow file. + * + * @return if the client is a shallow repository, the list of edge commits + * that define the client's shallow boundary. Empty set if the + * client is earlier than Git 1.9, or is a full clone. + * @since 3.5 + */ + protected Set getClientShallowCommits() { + return clientShallowCommits; + } + + /** + * Whether any commands to be executed have been read. + * + * @return {@code true} if any commands to be executed have been read. + */ + protected boolean hasCommands() { + return !commands.isEmpty(); + } + + /** + * Whether an error occurred that should be advertised. + * + * @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"); //$NON-NLS-1$ + 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<>(); + } + + /** + * Get advertised refs, or the default if not explicitly advertised. + * + * @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 java.io.IOException + * an error occurred during unpacking or connectivity checking. + */ + protected void receivePackAndCheckConnectivity() throws IOException { + receivePack(); + if (needCheckConnectivity()) { + checkSubmodules(); + checkConnectivity(); + } + parser = null; + } + + /** + * Unlock the pack written by this object. + * + * @throws java.io.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 java.io.IOException + * the formatter failed to write an advertisement. + * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException + * the hook denied advertisement. + */ + public void sendAdvertisedRefs(RefAdvertiser adv) + throws IOException, ServiceMayNotContinueException { + if (advertiseError != null) { + adv.writeOne("ERR " + advertiseError); //$NON-NLS-1$ + return; + } + + try { + advertiseRefsHook.advertiseRefs(this); + } catch (ServiceMayNotContinueException fail) { + if (fail.getMessage() != null) { + adv.writeOne("ERR " + fail.getMessage()); //$NON-NLS-1$ + fail.setOutput(); + } + throw fail; + } + + adv.init(db); + adv.advertiseCapability(CAPABILITY_SIDE_BAND_64K); + adv.advertiseCapability(CAPABILITY_DELETE_REFS); + adv.advertiseCapability(CAPABILITY_REPORT_STATUS); + if (allowQuiet) + adv.advertiseCapability(CAPABILITY_QUIET); + String nonce = getPushCertificateParser().getAdvertiseNonce(); + if (nonce != null) { + adv.advertiseCapability(nonce); + } + if (db.getRefDatabase().performsAtomicTransactions()) + adv.advertiseCapability(CAPABILITY_ATOMIC); + if (allowOfsDelta) + adv.advertiseCapability(CAPABILITY_OFS_DELTA); + if (allowPushOptions) { + adv.advertiseCapability(CAPABILITY_PUSH_OPTIONS); + } + adv.advertiseCapability(OPTION_AGENT, UserAgent.get()); + adv.send(getAdvertisedOrDefaultRefs().values()); + for (ObjectId obj : advertisedHaves) + adv.advertiseHave(obj); + if (adv.isEmpty()) + adv.advertiseId(ObjectId.zeroId(), "capabilities^{}"); //$NON-NLS-1$ + adv.end(); + } + + /** + * Returns the statistics on the received pack if available. This should be + * called after {@link #receivePack} is called. + * + * @return ReceivedPackStatistics + * @since 4.6 + */ + @Nullable + public ReceivedPackStatistics getReceivedPackStatistics() { + return stats; + } + + /** + * Receive a list of commands from the input. + * + * @throws java.io.IOException + */ + protected void recvCommands() throws IOException { + PacketLineIn pck = maxCommandBytes > 0 + ? new PacketLineIn(rawIn, maxCommandBytes) + : pckIn; + PushCertificateParser certParser = getPushCertificateParser(); + boolean firstPkt = true; + try { + for (;;) { + String line; + try { + line = pck.readString(); + } catch (EOFException eof) { + if (commands.isEmpty()) + return; + throw eof; + } + if (PacketLineIn.isEnd(line)) { + break; + } + + if (line.length() >= 48 && line.startsWith("shallow ")) { //$NON-NLS-1$ + parseShallow(line.substring(8, 48)); + continue; + } + + if (firstPkt) { + firstPkt = false; + FirstCommand firstLine = FirstCommand.fromLine(line); + enabledCapabilities = firstLine.getCapabilities(); + line = firstLine.getLine(); + enableCapabilities(); + + if (line.equals(GitProtocolConstants.OPTION_PUSH_CERT)) { + certParser.receiveHeader(pck, !isBiDirectionalPipe()); + continue; + } + } + + if (line.equals(PushCertificateParser.BEGIN_SIGNATURE)) { + certParser.receiveSignature(pck); + continue; + } - /** If {@link BasePackPushConnection#CAPABILITY_REPORT_STATUS} is enabled. */ - private boolean reportStatus; + ReceiveCommand cmd = ReceivePack.parseCommand(line); + if (cmd.getRefName().equals(Constants.HEAD)) { + cmd.setResult(Result.REJECTED_CURRENT_BRANCH); + } else { + cmd.setRef(refs.get(cmd.getRefName())); + } + commands.add(cmd); + if (certParser.enabled()) { + certParser.addCommand(cmd); + } + } + pushCert = certParser.build(); + if (hasCommands()) { + readPostCommands(pck); + } + } catch (PackProtocolException e) { + discardCommands(); + fatalError(e.getMessage()); + throw e; + } catch (InputOverLimitIOException e) { + String msg = JGitText.get().tooManyCommands; + discardCommands(); + fatalError(msg); + throw new PackProtocolException(msg); + } + } - /** Whether the client intends to use push options. */ - private boolean usePushOptions; - private List pushOptions; + private void discardCommands() { + if (sideBand) { + long max = maxDiscardBytes; + if (max < 0) { + max = Math.max(3 * maxCommandBytes, 3L << 20); + } + try { + new PacketLineIn(rawIn, max).discardUntilEnd(); + } catch (IOException e) { + // Ignore read failures attempting to discard. + } + } + } + + private void parseShallow(String idStr) throws PackProtocolException { + ObjectId id; + try { + id = ObjectId.fromString(idStr); + } catch (InvalidObjectIdException e) { + throw new PackProtocolException(e.getMessage(), e); + } + clientShallowCommits.add(id); + } /** - * Create a new pack receive for an open repository. + * @param in + * request stream. + * @throws IOException + * request line cannot be read. + */ + void readPostCommands(PacketLineIn in) throws IOException { + if (usePushOptions) { + pushOptions = new ArrayList<>(4); + for (;;) { + String option = in.readString(); + if (PacketLineIn.isEnd(option)) { + break; + } + pushOptions.add(option); + } + } + } + + /** + * Enable capabilities based on a previously read capabilities line. + */ + protected void enableCapabilities() { + reportStatus = isCapabilityEnabled(CAPABILITY_REPORT_STATUS); + usePushOptions = isCapabilityEnabled(CAPABILITY_PUSH_OPTIONS); + sideBand = isCapabilityEnabled(CAPABILITY_SIDE_BAND_64K); + quiet = allowQuiet && isCapabilityEnabled(CAPABILITY_QUIET); + if (sideBand) { + OutputStream out = rawOut; + + rawOut = new SideBandOutputStream(CH_DATA, MAX_BUF, out); + msgOut = new SideBandOutputStream(CH_PROGRESS, MAX_BUF, out); + errOut = new SideBandOutputStream(CH_ERROR, MAX_BUF, out); + + pckOut = new PacketLineOut(rawOut); + pckOut.setFlushOnEnd(false); + } + } + + /** + * Check if the peer requested a capability. * - * @param into - * the destination repository. + * @param name + * protocol name identifying the capability. + * @return true if the peer requested the capability to be enabled. */ - public ReceivePack(Repository into) { - super(into); - preReceive = PreReceiveHook.NULL; - postReceive = PostReceiveHook.NULL; + protected boolean isCapabilityEnabled(String name) { + return enabledCapabilities.contains(name); + } + + void checkRequestWasRead() { + if (enabledCapabilities == null) + throw new RequestNotYetReadException(); } /** - * Get the repository this receive completes into. + * Whether a pack is expected based on the list of commands. * - * @return the repository this receive completes into. + * @return {@code true} if a pack is expected based on the list of commands. */ - @Override - public final Repository getRepository() { - return db; + protected boolean needPack() { + for (ReceiveCommand cmd : commands) { + if (cmd.getType() != ReceiveCommand.Type.DELETE) + return true; + } + return false; } /** - * Get the RevWalk instance used by this connection. + * Receive a pack from the input and store it in the repository. * - * @return the RevWalk instance used by this connection. + * @throws IOException + * an error occurred reading or indexing the pack. */ - @Override - public final RevWalk getRevWalk() { - return walk; + 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 && !quiet) + resolving = new SideBandProgressMonitor(msgOut); + + try (ObjectInserter ins = db.newObjectInserter()) { + String lockMsg = "jgit receive-pack"; //$NON-NLS-1$ + if (getRefLogIdent() != null) + lockMsg += " from " + getRefLogIdent().toExternalString(); //$NON-NLS-1$ + + parser = ins.newPackParser(packInputStream()); + parser.setAllowThin(true); + parser.setNeedNewObjectIds(checkReferencedIsReachable); + parser.setNeedBaseObjectIds(checkReferencedIsReachable); + parser.setCheckEofAfterPackFooter( + !biDirectionalPipe && !isExpectDataAfterPackFooter()); + parser.setExpectDataAfterPackFooter(isExpectDataAfterPackFooter()); + parser.setObjectChecker(objectChecker); + parser.setLockMessage(lockMsg); + parser.setMaxObjectSizeLimit(maxObjectSizeLimit); + packLock = parser.parse(receiving, resolving); + packSize = Long.valueOf(parser.getPackSize()); + stats = parser.getReceivedPackStatistics(); + ins.flush(); + } + + if (timeoutIn != null) + timeoutIn.setTimeout(timeout * 1000); + } + + private InputStream packInputStream() { + InputStream packIn = rawIn; + if (maxPackSizeLimit >= 0) { + packIn = new LimitedInputStream(packIn, maxPackSizeLimit) { + @Override + protected void limitExceeded() throws TooLargePackException { + throw new TooLargePackException(limit); + } + }; + } + return packIn; + } + + private boolean needCheckConnectivity() { + return isCheckReceivedObjects() + || isCheckReferencedObjectsAreReachable() + || !getClientShallowCommits().isEmpty(); + } + + private void checkSubmodules() throws IOException { + ObjectDatabase odb = db.getObjectDatabase(); + if (objectChecker == null) { + return; + } + for (GitmoduleEntry entry : objectChecker.getGitsubmodules()) { + AnyObjectId blobId = entry.getBlobId(); + ObjectLoader blob = odb.open(blobId, Constants.OBJ_BLOB); + + try { + SubmoduleValidator.assertValidGitModulesFile( + new String(blob.getBytes(), UTF_8)); + } catch (LargeObjectException | SubmoduleValidationException e) { + throw new IOException(e); + } + } + } + + private void checkConnectivity() throws IOException { + ObjectIdSubclassMap baseObjects = null; + ObjectIdSubclassMap providedObjects = null; + ProgressMonitor checking = NullProgressMonitor.INSTANCE; + if (sideBand && !quiet) { + SideBandProgressMonitor m = new SideBandProgressMonitor(msgOut); + m.setDelayStart(750, TimeUnit.MILLISECONDS); + checking = m; + } + + if (checkReferencedIsReachable) { + baseObjects = parser.getBaseObjectIds(); + providedObjects = parser.getNewObjectIds(); + } + parser = null; + + try (ObjectWalk ow = new ObjectWalk(db)) { + if (baseObjects != null) { + ow.sort(RevSort.TOPO); + if (!baseObjects.isEmpty()) + ow.sort(RevSort.BOUNDARY, true); + } + + for (ReceiveCommand cmd : commands) { + if (cmd.getResult() != Result.NOT_ATTEMPTED) + continue; + if (cmd.getType() == ReceiveCommand.Type.DELETE) + continue; + ow.markStart(ow.parseAny(cmd.getNewId())); + } + for (ObjectId have : advertisedHaves) { + RevObject o = ow.parseAny(have); + ow.markUninteresting(o); + + if (baseObjects != null && !baseObjects.isEmpty()) { + o = ow.peel(o); + if (o instanceof RevCommit) + o = ((RevCommit) o).getTree(); + if (o instanceof RevTree) + ow.markUninteresting(o); + } + } + + checking.beginTask(JGitText.get().countingObjects, + ProgressMonitor.UNKNOWN); + RevCommit c; + while ((c = ow.next()) != null) { + checking.update(1); + if (providedObjects != null // + && !c.has(RevFlag.UNINTERESTING) // + && !providedObjects.contains(c)) + throw new MissingObjectException(c, Constants.TYPE_COMMIT); + } + + RevObject o; + while ((o = ow.nextObject()) != null) { + checking.update(1); + if (o.has(RevFlag.UNINTERESTING)) + continue; + + if (providedObjects != null) { + if (providedObjects.contains(o)) + continue; + else + throw new MissingObjectException(o, o.getType()); + } + + if (o instanceof RevBlob && !db.getObjectDatabase().has(o)) + throw new MissingObjectException(o, Constants.TYPE_BLOB); + } + checking.endTask(); + + if (baseObjects != null) { + for (ObjectId id : baseObjects) { + o = ow.parseAny(id); + if (!o.has(RevFlag.UNINTERESTING)) + throw new MissingObjectException(o, o.getType()); + } + } + } } /** - * Get refs which were advertised to the client. + * Validate the command list. + */ + protected void validateCommands() { + for (ReceiveCommand cmd : commands) { + final Ref ref = cmd.getRef(); + if (cmd.getResult() != Result.NOT_ATTEMPTED) + continue; + + if (cmd.getType() == ReceiveCommand.Type.DELETE) { + if (!isAllowDeletes()) { + // Deletes are not supported on this repository. + cmd.setResult(Result.REJECTED_NODELETE); + continue; + } + if (!isAllowBranchDeletes() + && ref.getName().startsWith(Constants.R_HEADS)) { + // Branches cannot be deleted, but other refs can. + 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, + JGitText.get().refAlreadyExists); + continue; + } + } + + if (cmd.getType() == ReceiveCommand.Type.DELETE && ref != null) { + ObjectId id = ref.getObjectId(); + if (id == null) { + id = ObjectId.zeroId(); + } + if (!ObjectId.zeroId().equals(cmd.getOldId()) + && !id.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; + } + ObjectId id = ref.getObjectId(); + if (id == null) { + // We cannot update unborn branch + cmd.setResult(Result.REJECTED_OTHER_REASON, + JGitText.get().cannotUpdateUnbornBranch); + continue; + } + + if (!id.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.setTypeFastForwardUpdate(); + else + 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.getType() == ReceiveCommand.Type.UPDATE_NONFASTFORWARD + && !isAllowNonFastForwards()) { + cmd.setResult(Result.REJECTED_NONFASTFORWARD); + continue; + } + } + + if (!cmd.getRefName().startsWith(Constants.R_REFS) + || !Repository.isValidRefName(cmd.getRefName())) { + cmd.setResult(Result.REJECTED_OTHER_REASON, + JGitText.get().funnyRefname); + } + } + } + + /** + * Whether any commands have been rejected so far. * - * @return all refs which were advertised to the client, or null if - * {@link #setAdvertisedRefs(Map, Set)} has not been called yet. + * @return if any commands have been rejected so far. + * @since 3.6 */ - @Override - public final Map getAdvertisedRefs() { - return refs; + protected boolean anyRejects() { + for (ReceiveCommand cmd : commands) { + if (cmd.getResult() != Result.NOT_ATTEMPTED + && cmd.getResult() != Result.OK) + return true; + } + return false; } /** - * Set the refs advertised by this ReceivePack. - *

- * Intended to be called from a - * {@link org.eclipse.jgit.transport.PreReceiveHook}. + * Set the result to fail for any command that was not processed yet. * - * @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. + * @since 3.6 */ - @Override - public void setAdvertisedRefs(Map allRefs, Set additionalHaves) { - refs = allRefs != null ? allRefs : db.getAllRefs(); - refs = refFilter.filter(refs); - advertisedHaves.clear(); + protected void failPendingCommands() { + ReceiveCommand.abort(commands); + } - Ref head = refs.get(HEAD); - if (head != null && head.isSymbolic()) { - refs.remove(HEAD); + /** + * 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(Result want) { + return ReceiveCommand.filter(commands, want); + } + + /** + * Execute commands to update references. + */ + protected void executeCommands() { + List toApply = filterCommands(Result.NOT_ATTEMPTED); + if (toApply.isEmpty()) + return; + + ProgressMonitor updating = NullProgressMonitor.INSTANCE; + if (sideBand) { + SideBandProgressMonitor pm = new SideBandProgressMonitor(msgOut); + pm.setDelayStart(250, TimeUnit.MILLISECONDS); + updating = pm; } - for (Ref ref : refs.values()) { - if (ref.getObjectId() != null) { - advertisedHaves.add(ref.getObjectId()); + BatchRefUpdate batch = db.getRefDatabase().newBatchUpdate(); + batch.setAllowNonFastForwards(isAllowNonFastForwards()); + batch.setAtomic(isAtomic()); + batch.setRefLogIdent(getRefLogIdent()); + batch.setRefLogMessage("push", true); //$NON-NLS-1$ + batch.addCommand(toApply); + try { + batch.setPushCertificate(getPushCertificate()); + batch.execute(walk, updating); + } catch (IOException err) { + for (ReceiveCommand cmd : toApply) { + if (cmd.getResult() == Result.NOT_ATTEMPTED) + cmd.reject(err); } } - if (additionalHaves != null) { - advertisedHaves.addAll(additionalHaves); - } else { - advertisedHaves.addAll(db.getAdditionalHaves()); + } + + /** + * 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 java.io.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()); //$NON-NLS-1$ + if (forClient) { + for (ReceiveCommand cmd : commands) { + out.sendString("ng " + cmd.getRefName() //$NON-NLS-1$ + + " n/a (unpacker error)"); //$NON-NLS-1$ + } + } + return; + } + + if (forClient) + out.sendString("unpack ok"); //$NON-NLS-1$ + for (ReceiveCommand cmd : commands) { + if (cmd.getResult() == Result.OK) { + if (forClient) + out.sendString("ok " + cmd.getRefName()); //$NON-NLS-1$ + continue; + } + + final StringBuilder r = new StringBuilder(); + if (forClient) + r.append("ng ").append(cmd.getRefName()).append(" "); //$NON-NLS-1$ //$NON-NLS-2$ + else + r.append(" ! [rejected] ").append(cmd.getRefName()) //$NON-NLS-1$ + .append(" ("); //$NON-NLS-1$ + + switch (cmd.getResult()) { + case NOT_ATTEMPTED: + r.append("server bug; ref not processed"); //$NON-NLS-1$ + break; + + case REJECTED_NOCREATE: + r.append("creation prohibited"); //$NON-NLS-1$ + break; + + case REJECTED_NODELETE: + r.append("deletion prohibited"); //$NON-NLS-1$ + break; + + case REJECTED_NONFASTFORWARD: + r.append("non-fast forward"); //$NON-NLS-1$ + break; + + case REJECTED_CURRENT_BRANCH: + r.append("branch is currently checked out"); //$NON-NLS-1$ + break; + + case REJECTED_MISSING_OBJECT: + if (cmd.getMessage() == null) + r.append("missing object(s)"); //$NON-NLS-1$ + else if (cmd.getMessage() + .length() == Constants.OBJECT_ID_STRING_LENGTH) { + r.append("object "); //$NON-NLS-1$ + r.append(cmd.getMessage()); + r.append(" missing"); //$NON-NLS-1$ + } else + r.append(cmd.getMessage()); + break; + + case REJECTED_OTHER_REASON: + if (cmd.getMessage() == null) + r.append("unspecified reason"); //$NON-NLS-1$ + else + r.append(cmd.getMessage()); + break; + + case LOCK_FAILURE: + r.append("failed to lock"); //$NON-NLS-1$ + break; + + case OK: + // We shouldn't have reached this case (see 'ok' case above). + continue; + } + if (!forClient) + r.append(")"); //$NON-NLS-1$ + out.sendString(r.toString()); + } + } + + /** + * Close and flush (if necessary) the underlying streams. + * + * @throws java.io.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 java.io.IOException + * the pack could not be unlocked. + */ + protected void release() throws IOException { + walk.close(); + unlockPack(); + timeoutIn = null; + rawIn = null; + rawOut = null; + msgOut = null; + pckIn = null; + pckOut = null; + refs = null; + // Keep the capabilities. If responses are sent after this release + // we need to remember at least whether sideband communication has to be + // used + 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; + } + /** * Get the push certificate used to verify the pusher's identity. *

@@ -178,7 +2018,6 @@ public class ReceivePack extends BaseReceivePack { * or no cert was presented by the client. * @since 4.1 */ - @Override public PushCertificate getPushCertificate() { return pushCert; } @@ -193,7 +2032,6 @@ public class ReceivePack extends BaseReceivePack { * the push certificate to set. * @since 4.1 */ - @Override public void setPushCertificate(PushCertificate cert) { pushCert = cert; } @@ -334,28 +2172,6 @@ public class ReceivePack extends BaseReceivePack { } } - /** {@inheritDoc} */ - @Override - protected void enableCapabilities() { - reportStatus = isCapabilityEnabled(CAPABILITY_REPORT_STATUS); - usePushOptions = isCapabilityEnabled(CAPABILITY_PUSH_OPTIONS); - super.enableCapabilities(); - } - - @Override - void readPostCommands(PacketLineIn in) throws IOException { - if (usePushOptions) { - pushOptions = new ArrayList<>(4); - for (;;) { - String option = in.readString(); - if (PacketLineIn.isEnd(option)) { - break; - } - pushOptions.add(option); - } - } - } - private void service() throws IOException { if (isBiDirectionalPipe()) { sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut)); @@ -437,9 +2253,27 @@ public class ReceivePack extends BaseReceivePack { repo.autoGC(NullProgressMonitor.INSTANCE); } - /** {@inheritDoc} */ - @Override - protected String getLockMessageProcessName() { - return "jgit receive-pack"; //$NON-NLS-1$ + static ReceiveCommand parseCommand(String line) + throws PackProtocolException { + if (line == null || line.length() < 83) { + throw new PackProtocolException( + JGitText.get().errorInvalidProtocolWantedOldNewRef); + } + String oldStr = line.substring(0, 40); + String newStr = line.substring(41, 81); + ObjectId oldId, newId; + try { + oldId = ObjectId.fromString(oldStr); + newId = ObjectId.fromString(newStr); + } catch (InvalidObjectIdException e) { + throw new PackProtocolException( + JGitText.get().errorInvalidProtocolWantedOldNewRef, e); + } + String name = line.substring(82); + if (!Repository.isValidRefName(name)) { + throw new PackProtocolException( + JGitText.get().errorInvalidProtocolWantedOldNewRef); + } + return new ReceiveCommand(oldId, newId, name); } }