diff options
10 files changed, 463 insertions, 223 deletions
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java index e9462eeb4c..06970a7693 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java @@ -96,6 +96,8 @@ public class GitFilter extends MetaFilter { private ReceivePackFactory<HttpServletRequest> receivePackFactory = new DefaultReceivePackFactory(); + private ReceivePackErrorHandler receivePackErrorHandler; + private final List<Filter> uploadPackFilters = new LinkedList<>(); private final List<Filter> receivePackFilters = new LinkedList<>(); @@ -190,6 +192,17 @@ public class GitFilter extends MetaFilter { } /** + * Set a custom error handler for git-receive-pack. + * + * @param h + * A custom error handler for git-receive-pack. + */ + public void setReceivePackErrorHandler(ReceivePackErrorHandler h) { + assertNotInitialized(); + this.receivePackErrorHandler = h; + } + + /** * Add receive-pack filter * * @param filter @@ -233,7 +246,7 @@ public class GitFilter extends MetaFilter { b = b.through(new ReceivePackServlet.Factory(receivePackFactory)); for (Filter f : receivePackFilters) b = b.through(f); - b.with(new ReceivePackServlet()); + b.with(new ReceivePackServlet(receivePackErrorHandler)); } ServletBinder refs = serve("*/" + Constants.INFO_REFS); diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java index 5e09d012d7..5077e83b36 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java @@ -48,8 +48,6 @@ import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import static org.eclipse.jgit.http.server.ServletUtils.ATTRIBUTE_HANDLER; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SIDE_BAND_64K; -import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND; -import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K; import static org.eclipse.jgit.transport.SideBandOutputStream.CH_ERROR; import static org.eclipse.jgit.transport.SideBandOutputStream.SMALL_BUF; @@ -64,14 +62,12 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jgit.internal.transport.parser.FirstCommand; -import org.eclipse.jgit.internal.transport.parser.FirstWant; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.transport.PacketLineIn; import org.eclipse.jgit.transport.PacketLineOut; import org.eclipse.jgit.transport.ReceivePack; import org.eclipse.jgit.transport.RequestNotYetReadException; import org.eclipse.jgit.transport.SideBandOutputStream; -import org.eclipse.jgit.transport.UploadPack; /** * Utility functions for handling the Git-over-HTTP protocol. @@ -220,44 +216,15 @@ public class GitSmartHttpTools { private static void sendUploadPackError(HttpServletRequest req, HttpServletResponse res, String textForGit) throws IOException { + // Do not use sideband. Sideband is acceptable only while packfile is + // being sent. Other places, like acknowledgement section, do not + // support sideband. Use an error packet. ByteArrayOutputStream buf = new ByteArrayOutputStream(128); PacketLineOut pckOut = new PacketLineOut(buf); - - boolean sideband; - UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER); - if (up != null) { - try { - sideband = up.isSideBand(); - } catch (RequestNotYetReadException e) { - sideband = isUploadPackSideBand(req); - } - } else - sideband = isUploadPackSideBand(req); - - if (sideband) - writeSideBand(buf, textForGit); - else - writePacket(pckOut, textForGit); + writePacket(pckOut, textForGit); send(req, res, UPLOAD_PACK_RESULT_TYPE, buf.toByteArray()); } - private static boolean isUploadPackSideBand(HttpServletRequest req) { - try { - // The client may be in a state where they have sent the sideband - // capability and are expecting a response in the sideband, but we might - // not have an UploadPack, or it might not have read any of the request. - // So, cheat and read the first line. - String line = new PacketLineIn(req.getInputStream()).readString(); - FirstWant parsed = FirstWant.fromLine(line); - return (parsed.getCapabilities().contains(OPTION_SIDE_BAND) - || parsed.getCapabilities().contains(OPTION_SIDE_BAND_64K)); - } catch (IOException e) { - // Probably the connection is closed and a subsequent write will fail, but - // try it just in case. - return false; - } - } - private static void sendReceivePackError(HttpServletRequest req, HttpServletResponse res, String textForGit) throws IOException { ByteArrayOutputStream buf = new ByteArrayOutputStream(128); @@ -308,7 +275,7 @@ public class GitSmartHttpTools { private static void writePacket(PacketLineOut pckOut, String textForGit) throws IOException { - pckOut.writeString("error: " + textForGit); + pckOut.writeString("ERR " + textForGit); } private static void send(HttpServletRequest req, HttpServletResponse res, diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackErrorHandler.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackErrorHandler.java new file mode 100644 index 0000000000..ee66cb102f --- /dev/null +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackErrorHandler.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019, Google LLC and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.http.server; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jgit.transport.ReceivePack; +import org.eclipse.jgit.transport.ServiceMayNotContinueException; + +/** + * Handle git-receive-pack errors. + * + * <p> + * This is an entry point for customizing an error handler for git-receive-pack. + * Right before calling {@link ReceivePack#receiveWithExceptionPropagation}, + * JGit will call this handler if specified through {@link GitFilter}. The + * implementation of this handler is responsible for calling + * {@link ReceivePackRunnable} and handling exceptions for clients. + * + * <p> + * If a custom handler is not specified, JGit will use the default error + * handler. + * + * @since 5.6 + */ +public interface ReceivePackErrorHandler { + /** + * @param req + * The HTTP request + * @param rsp + * The HTTP response + * @param r + * A continuation that handles a git-receive-pack request. + * @throws IOException + */ + void receive(HttpServletRequest req, HttpServletResponse rsp, + ReceivePackRunnable r) throws IOException; + + /** Process a git-receive-pack request. */ + public interface ReceivePackRunnable { + /** + * See {@link ReceivePack#receiveWithExceptionPropagation}. + * + * @throws ServiceMayNotContinueException + * @throws IOException + */ + void receive() throws ServiceMayNotContinueException, IOException; + } + +} diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java index aed36560a8..eb130d0a28 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java @@ -71,6 +71,7 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.UnpackException; @@ -161,6 +162,13 @@ class ReceivePackServlet extends HttpServlet { } } + @Nullable + private final ReceivePackErrorHandler handler; + + ReceivePackServlet(@Nullable ReceivePackErrorHandler handler) { + this.handler = handler; + } + /** {@inheritDoc} */ @Override public void doPost(final HttpServletRequest req, @@ -178,34 +186,42 @@ class ReceivePackServlet extends HttpServlet { }; ReceivePack rp = (ReceivePack) req.getAttribute(ATTRIBUTE_HANDLER); - try { - rp.setBiDirectionalPipe(false); - rsp.setContentType(RECEIVE_PACK_RESULT_TYPE); - - rp.receive(getInputStream(req), out, null); - out.close(); - } catch (CorruptObjectException e ) { - // This should be already reported to the client. - getServletContext().log(MessageFormat.format( - HttpServerText.get().receivedCorruptObject, - e.getMessage(), - ServletUtils.identify(rp.getRepository()))); - consumeRequestBody(req); - out.close(); - - } catch (UnpackException | PackProtocolException e) { - // This should be already reported to the client. - log(rp.getRepository(), e.getCause()); - consumeRequestBody(req); - out.close(); - - } catch (Throwable e) { - log(rp.getRepository(), e); - if (!rsp.isCommitted()) { - rsp.reset(); - sendError(req, rsp, SC_INTERNAL_SERVER_ERROR); + rp.setBiDirectionalPipe(false); + rsp.setContentType(RECEIVE_PACK_RESULT_TYPE); + + if (handler != null) { + handler.receive(req, rsp, () -> { + rp.receiveWithExceptionPropagation(getInputStream(req), out, + null); + out.close(); + }); + } else { + try { + rp.receive(getInputStream(req), out, null); + out.close(); + } catch (CorruptObjectException e ) { + // This should be already reported to the client. + getServletContext().log(MessageFormat.format( + HttpServerText.get().receivedCorruptObject, + e.getMessage(), + ServletUtils.identify(rp.getRepository()))); + consumeRequestBody(req); + out.close(); + + } catch (UnpackException | PackProtocolException e) { + // This should be already reported to the client. + log(rp.getRepository(), e.getCause()); + consumeRequestBody(req); + out.close(); + + } catch (Throwable e) { + log(rp.getRepository(), e); + if (!rsp.isCommitted()) { + rsp.reset(); + sendError(req, rsp, SC_INTERNAL_SERVER_ERROR); + } + return; } - return; } } diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java index 54561e0cfc..6baab5ddd7 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java @@ -70,7 +70,9 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; + import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.http.server.UploadPackErrorHandler.UploadPackRunnable; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.InternalHttpServerGlue; @@ -212,7 +214,8 @@ class UploadPackServlet extends HttpServlet { rsp.setContentType(UPLOAD_PACK_RESULT_TYPE); try { - up.upload(getInputStream(req), out, null); + up.uploadWithExceptionPropagation(getInputStream(req), out, + null); out.close(); } catch (ServiceMayNotContinueException e) { if (e.isOutput()) { @@ -245,7 +248,9 @@ class UploadPackServlet extends HttpServlet { log(up.getRepository(), e); if (!rsp.isCommitted()) { rsp.reset(); - sendError(req, rsp, SC_INTERNAL_SERVER_ERROR); + String msg = e instanceof PackProtocolException ? e.getMessage() + : null; + sendError(req, rsp, SC_INTERNAL_SERVER_ERROR, msg); } } } diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java index 99aa06b17c..b23fd28547 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java @@ -1214,7 +1214,7 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { Collections.<ObjectId> emptySet()); fail("Successfully served ref with value " + c.getRef(master)); } catch (TransportException err) { - assertEquals("internal server error", err.getMessage()); + assertEquals("Internal server error", err.getMessage()); } } finally { noRefServer.tearDown(); 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 89ac2fe96d..daf7b9fb7a 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 @@ -495,8 +495,7 @@ public class ReceivePackAdvertiseRefsHookTest extends LocalDiskRepositoryTestCas receive(rp, inBuf, outBuf); fail("Expected UnpackException"); } catch (UnpackException failed) { - Throwable err = failed.getCause(); - assertTrue(err instanceof IOException); + // Expected } final PacketLineIn r = asPacketLineIn(outBuf); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommandErrorHandler.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommandErrorHandler.java new file mode 100644 index 0000000000..d9a148622b --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommandErrorHandler.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2019, Google LLC and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.transport; + +import java.io.IOException; +import java.util.List; + +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.transport.ReceiveCommand.Result; + +/** + * Exception handler for processing {@link ReceiveCommand}. + * + * @since 5.7 + */ +public interface ReceiveCommandErrorHandler { + /** + * Handle an exception thrown while validating the new commit ID. + * + * @param cmd + * offending command + * @param e + * exception thrown + */ + default void handleNewIdValidationException(ReceiveCommand cmd, + IOException e) { + cmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd.getNewId().name()); + } + + /** + * Handle an exception thrown while validating the old commit ID. + * + * @param cmd + * offending command + * @param e + * exception thrown + */ + default void handleOldIdValidationException(ReceiveCommand cmd, + IOException e) { + cmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd.getOldId().name()); + } + + /** + * Handle an exception thrown while checking if the update is fast-forward. + * + * @param cmd + * offending command + * @param e + * exception thrown + */ + default void handleFastForwardCheckException(ReceiveCommand cmd, + IOException e) { + if (e instanceof MissingObjectException) { + cmd.setResult(Result.REJECTED_MISSING_OBJECT, e.getMessage()); + } else { + cmd.setResult(Result.REJECTED_OTHER_REASON); + } + } + + /** + * Handle an exception thrown while checking if the update is fast-forward. + * + * @param cmds + * commands being processed + * @param e + * exception thrown + */ + default void handleBatchRefUpdateException(List<ReceiveCommand> cmds, + IOException e) { + for (ReceiveCommand cmd : cmds) { + if (cmd.getResult() == Result.NOT_ATTEMPTED) { + cmd.reject(e); + } + } + } +} 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 16fbbd42f3..6de3848d8e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java @@ -295,6 +295,12 @@ public class ReceivePack { /** Hook to validate the update commands before execution. */ private PreReceiveHook preReceive; + private ReceiveCommandErrorHandler receiveCommandErrorHandler = new ReceiveCommandErrorHandler() { + // Use the default implementation. + }; + + private UnpackErrorHandler unpackErrorHandler = new DefaultUnpackErrorHandler(); + /** Hook to report on the commands after execution. */ private PostReceiveHook postReceive; @@ -1021,6 +1027,17 @@ public class ReceivePack { } /** + * Set an error handler for {@link ReceiveCommand}. + * + * @param receiveCommandErrorHandler + * @since 5.7 + */ + public void setReceiveCommandErrorHandler( + ReceiveCommandErrorHandler receiveCommandErrorHandler) { + this.receiveCommandErrorHandler = receiveCommandErrorHandler; + } + + /** * Send an error message to the client. * <p> * If any error messages are sent before the references are advertised to @@ -1217,8 +1234,13 @@ public class ReceivePack { * * @throws java.io.IOException * an error occurred during unpacking or connectivity checking. + * @throws LargeObjectException + * an large object needs to be opened for the check. + * @throws SubmoduleValidationException + * fails to validate the submodule. */ - protected void receivePackAndCheckConnectivity() throws IOException { + protected void receivePackAndCheckConnectivity() throws IOException, + LargeObjectException, SubmoduleValidationException { receivePack(); if (needCheckConnectivity()) { checkSubmodules(); @@ -1368,15 +1390,9 @@ public class ReceivePack { if (hasCommands()) { readPostCommands(pck); } - } catch (PackProtocolException e) { + } catch (Throwable t) { discardCommands(); - fatalError(e.getMessage()); - throw e; - } catch (InputOverLimitIOException e) { - String msg = JGitText.get().tooManyCommands; - discardCommands(); - fatalError(msg); - throw new PackProtocolException(msg); + throw t; } } @@ -1535,7 +1551,8 @@ public class ReceivePack { || !getClientShallowCommits().isEmpty(); } - private void checkSubmodules() throws IOException { + private void checkSubmodules() throws IOException, LargeObjectException, + SubmoduleValidationException { ObjectDatabase odb = db.getObjectDatabase(); if (objectChecker == null) { return; @@ -1544,12 +1561,8 @@ public class ReceivePack { 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); - } + SubmoduleValidator.assertValidGitModulesFile( + new String(blob.getBytes(), UTF_8)); } } @@ -1730,16 +1743,16 @@ public class ReceivePack { try { oldObj = walk.parseAny(cmd.getOldId()); } catch (IOException e) { - cmd.setResult(Result.REJECTED_MISSING_OBJECT, - cmd.getOldId().name()); + receiveCommandErrorHandler + .handleOldIdValidationException(cmd, e); continue; } try { newObj = walk.parseAny(cmd.getNewId()); } catch (IOException e) { - cmd.setResult(Result.REJECTED_MISSING_OBJECT, - cmd.getNewId().name()); + receiveCommandErrorHandler + .handleNewIdValidationException(cmd, e); continue; } @@ -1747,16 +1760,14 @@ public class ReceivePack { && newObj instanceof RevCommit) { try { if (walk.isMergedInto((RevCommit) oldObj, - (RevCommit) newObj)) + (RevCommit) newObj)) { cmd.setTypeFastForwardUpdate(); - else - cmd.setType( - ReceiveCommand.Type.UPDATE_NONFASTFORWARD); - } catch (MissingObjectException e) { - cmd.setResult(Result.REJECTED_MISSING_OBJECT, - e.getMessage()); + } else { + cmd.setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD); + } } catch (IOException e) { - cmd.setResult(Result.REJECTED_OTHER_REASON); + receiveCommandErrorHandler + .handleFastForwardCheckException(cmd, e); } } else { cmd.setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD); @@ -1835,109 +1846,122 @@ public class ReceivePack { try { batch.setPushCertificate(getPushCertificate()); batch.execute(walk, updating); - } catch (IOException err) { - for (ReceiveCommand cmd : toApply) { - if (cmd.getResult() == Result.NOT_ATTEMPTED) - cmd.reject(err); - } + } catch (IOException e) { + receiveCommandErrorHandler.handleBatchRefUpdateException(toApply, + e); } } /** * 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. * @since 5.6 */ - private 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$ + private void sendStatusReport(Throwable unpackError) throws IOException { + Reporter out = new Reporter() { + @Override + void sendString(String s) throws IOException { + if (reportStatus) { + pckOut.writeString(s + "\n"); //$NON-NLS-1$ + } else if (msgOut != null) { + msgOut.write(Constants.encode(s + "\n")); //$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; + try { + if (unpackError != null) { + out.sendString("unpack error " + unpackError.getMessage()); //$NON-NLS-1$ + if (reportStatus) { + for (ReceiveCommand cmd : commands) { + out.sendString("ng " + cmd.getRefName() //$NON-NLS-1$ + + " n/a (unpacker error)"); //$NON-NLS-1$ + } + } + return; } - 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 (reportStatus) { + out.sendString("unpack ok"); //$NON-NLS-1$ + } + for (ReceiveCommand cmd : commands) { + if (cmd.getResult() == Result.OK) { + if (reportStatus) { + out.sendString("ok " + cmd.getRefName()); //$NON-NLS-1$ + } + continue; + } + + final StringBuilder r = new StringBuilder(); + if (reportStatus) { + 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 (!reportStatus) { + r.append(")"); //$NON-NLS-1$ + } + out.sendString(r.toString()); + } + } finally { + if (reportStatus) { + pckOut.end(); } - if (!forClient) - r.append(")"); //$NON-NLS-1$ - out.sendString(r.toString()); } } @@ -2123,6 +2147,15 @@ public class ReceivePack { } /** + * @param unpackErrorHandler + * the unpackErrorHandler to set + * @since 5.7 + */ + public void setUnpackErrorHandler(UnpackErrorHandler unpackErrorHandler) { + this.unpackErrorHandler = unpackErrorHandler; + } + + /** * Set whether this class will report command failures as warning messages * before sending the command results. * @@ -2161,6 +2194,49 @@ public class ReceivePack { init(input, output, messages); try { service(); + } catch (PackProtocolException e) { + fatalError(e.getMessage()); + throw e; + } catch (InputOverLimitIOException e) { + String msg = JGitText.get().tooManyCommands; + fatalError(msg); + throw new PackProtocolException(msg); + } finally { + try { + close(); + } finally { + release(); + } + } + } + + /** + * Execute the receive task on the socket. + * + * <p> + * Same as {@link #receive}, but the exceptions are not reported to the + * client yet. + * + * @param input + * raw input to read client commands and pack data from. Caller + * must ensure the input is buffered, otherwise read performance + * may suffer. + * @param output + * response back to the Git network client. Caller must ensure + * the output is buffered, otherwise write performance may + * suffer. + * @param messages + * secondary "notice" channel to send additional messages out + * through. When run over SSH this should be tied back to the + * standard error channel of the command execution. For most + * other network connections this should be null. + * @throws java.io.IOException + */ + public void receiveWithExceptionPropagation(InputStream input, + OutputStream output, OutputStream messages) throws IOException { + init(input, output, messages); + try { + service(); } finally { try { close(); @@ -2178,19 +2254,23 @@ public class ReceivePack { getAdvertisedOrDefaultRefs(); if (hasError()) return; + recvCommands(); + if (hasCommands()) { - Throwable unpackError = null; - if (needPack()) { - try { - receivePackAndCheckConnectivity(); - } catch (IOException | RuntimeException | Error err) { - unpackError = err; + try (PostReceiveExecutor e = new PostReceiveExecutor()) { + if (needPack()) { + try { + receivePackAndCheckConnectivity(); + } catch (IOException | RuntimeException + | SubmoduleValidationException | Error err) { + unlockPack(); + unpackErrorHandler.handleUnpackException(err); + throw new UnpackException(err); + } } - } - try { - if (unpackError == null) { + try { setAtomic(isCapabilityEnabled(CAPABILITY_ATOMIC)); validateCommands(); @@ -2204,39 +2284,12 @@ public class ReceivePack { failPendingCommands(); } executeCommands(); + } finally { + unlockPack(); } - } finally { - unlockPack(); - } - - if (reportStatus) { - sendStatusReport(true, unpackError, new Reporter() { - @Override - void sendString(String s) throws IOException { - pckOut.writeString(s + "\n"); //$NON-NLS-1$ - } - }); - pckOut.end(); - } else if (msgOut != null) { - sendStatusReport(false, unpackError, new Reporter() { - @Override - void sendString(String s) throws IOException { - msgOut.write(Constants.encode(s + "\n")); //$NON-NLS-1$ - } - }); - } - if (unpackError != null) { - // we already know which exception to throw. Ignore - // potential additional exceptions raised in postReceiveHooks - try { - postReceive.onPostReceive(this, filterCommands(Result.OK)); - } catch (Throwable e) { - // empty - } - throw new UnpackException(unpackError); + sendStatusReport(null); } - postReceive.onPostReceive(this, filterCommands(Result.OK)); autoGc(); } } @@ -2273,4 +2326,19 @@ public class ReceivePack { } return new ReceiveCommand(oldId, newId, name); } + + private class PostReceiveExecutor implements AutoCloseable { + @Override + public void close() { + postReceive.onPostReceive(ReceivePack.this, + filterCommands(Result.OK)); + } + } + + private class DefaultUnpackErrorHandler implements UnpackErrorHandler { + @Override + public void handleUnpackException(Throwable t) throws IOException { + sendStatusReport(t); + } + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UnpackErrorHandler.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UnpackErrorHandler.java new file mode 100644 index 0000000000..12c9a76214 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UnpackErrorHandler.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019, Google LLC and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.transport; + +import java.io.IOException; + +/** + * Exception handler for processing an incoming pack file. + * + * @since 5.7 + */ +public interface UnpackErrorHandler { + /** + * Handle an exception thrown while unpacking the pack file. + * + * @param t + * exception thrown + * @throws IOException + * thrown when failed to write an error back to the client. + */ + void handleUnpackException(Throwable t) throws IOException; +} |