Merge branch 'push-sideband' into stable-0.7

* push-sideband:
  Reuse the line buffer between strings in PacketLineIn
  http.server: Use TemporaryBuffer and compress some responses
  Reduce multi-level buffered streams in transport code
  Fix smart HTTP client buffer alignment
  Use "ERR message" for early ReceivePack problems
  Catch and report "ERR message" during remote advertisements
  Wait for EOF on stderr before finishing SSH channel
  Capture non-progress side band #2 messages and put in result
  ReceivePack: Enable side-band-64k capability for status reports
  Use more restrictive patterns for sideband progress scraping
  Prefix remote progress tasks with "remote: "
  Decode side-band channel number as unsigned integer
  Refactor SideBandInputStream construction
  Refactor SideBandOutputStream to be buffered

Change-Id: Ic9689e64e8c87971f2fd402cb619082309d5587f
This commit is contained in:
Shawn O. Pearce 2010-03-12 17:00:50 -08:00
commit 23bd331cb2
40 changed files with 1489 additions and 455 deletions

View File

@ -44,9 +44,9 @@
package org.eclipse.jgit.http.server; package org.eclipse.jgit.http.server;
import static org.eclipse.jgit.http.server.ServletUtils.getRepository; import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
import static org.eclipse.jgit.http.server.ServletUtils.send;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
@ -69,20 +69,18 @@ public void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException { final HttpServletResponse rsp) throws IOException {
// Assume a dumb client and send back the dumb client // Assume a dumb client and send back the dumb client
// version of the info/refs file. // version of the info/refs file.
final byte[] raw = dumbHttp(req);
rsp.setContentType(HttpSupport.TEXT_PLAIN); rsp.setContentType(HttpSupport.TEXT_PLAIN);
rsp.setCharacterEncoding(Constants.CHARACTER_ENCODING); rsp.setCharacterEncoding(Constants.CHARACTER_ENCODING);
send(raw, req, rsp);
}
private byte[] dumbHttp(final HttpServletRequest req) throws IOException {
final Repository db = getRepository(req); final Repository db = getRepository(req);
final RevWalk walk = new RevWalk(db); final RevWalk walk = new RevWalk(db);
final RevFlag ADVERTISED = walk.newFlag("ADVERTISED"); final RevFlag ADVERTISED = walk.newFlag("ADVERTISED");
final StringBuilder out = new StringBuilder();
final OutputStreamWriter out = new OutputStreamWriter(
new SmartOutputStream(req, rsp), Constants.CHARSET);
final RefAdvertiser adv = new RefAdvertiser() { final RefAdvertiser adv = new RefAdvertiser() {
@Override @Override
protected void writeOne(final CharSequence line) { protected void writeOne(final CharSequence line) throws IOException {
// Whoever decided that info/refs should use a different // Whoever decided that info/refs should use a different
// delimiter than the native git:// protocol shouldn't // delimiter than the native git:// protocol shouldn't
// be allowed to design this sort of stuff. :-( // be allowed to design this sort of stuff. :-(
@ -100,6 +98,6 @@ protected void end() {
Map<String, Ref> refs = db.getAllRefs(); Map<String, Ref> refs = db.getAllRefs();
refs.remove(Constants.HEAD); refs.remove(Constants.HEAD);
adv.send(refs); adv.send(refs);
return out.toString().getBytes(Constants.CHARACTER_ENCODING); out.close();
} }
} }

View File

@ -50,9 +50,7 @@
import static org.eclipse.jgit.http.server.ServletUtils.getInputStream; import static org.eclipse.jgit.http.server.ServletUtils.getInputStream;
import static org.eclipse.jgit.http.server.ServletUtils.getRepository; import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -104,11 +102,14 @@ public void doPost(final HttpServletRequest req,
} }
final Repository db = getRepository(req); final Repository db = getRepository(req);
final ByteArrayOutputStream out = new ByteArrayOutputStream();
try { try {
final ReceivePack rp = receivePackFactory.create(req, db); final ReceivePack rp = receivePackFactory.create(req, db);
rp.setBiDirectionalPipe(false); rp.setBiDirectionalPipe(false);
rsp.setContentType(RSP_TYPE);
final SmartOutputStream out = new SmartOutputStream(req, rsp);
rp.receive(getInputStream(req), out, null); rp.receive(getInputStream(req), out, null);
out.close();
} catch (ServiceNotAuthorizedException e) { } catch (ServiceNotAuthorizedException e) {
rsp.sendError(SC_UNAUTHORIZED); rsp.sendError(SC_UNAUTHORIZED);
@ -123,20 +124,5 @@ public void doPost(final HttpServletRequest req,
rsp.sendError(SC_INTERNAL_SERVER_ERROR); rsp.sendError(SC_INTERNAL_SERVER_ERROR);
return; return;
} }
reply(rsp, out.toByteArray());
}
private void reply(final HttpServletResponse rsp, final byte[] result)
throws IOException {
rsp.setContentType(RSP_TYPE);
rsp.setContentLength(result.length);
final OutputStream os = rsp.getOutputStream();
try {
os.write(result);
os.flush();
} finally {
os.close();
}
} }
} }

View File

@ -189,7 +189,7 @@ private static byte[] sendInit(byte[] content,
return content; return content;
} }
private static boolean acceptsGzipEncoding(final HttpServletRequest req) { static boolean acceptsGzipEncoding(final HttpServletRequest req) {
final String accepts = req.getHeader(HDR_ACCEPT_ENCODING); final String accepts = req.getHeader(HDR_ACCEPT_ENCODING);
return accepts != null && 0 <= accepts.indexOf(ENCODING_GZIP); return accepts != null && 0 <= accepts.indexOf(ENCODING_GZIP);
} }

View File

@ -0,0 +1,130 @@
/*
* Copyright (C) 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.http.server;
import static org.eclipse.jgit.http.server.ServletUtils.acceptsGzipEncoding;
import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP;
import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.GZIPOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.util.TemporaryBuffer;
/**
* Buffers a response, trying to gzip it if the user agent supports that.
* <p>
* If the response overflows the buffer, gzip is skipped and the response is
* streamed to the client as its produced, most likely using HTTP/1.1 chunked
* encoding. This is useful for servlets that produce mixed-mode content, where
* smaller payloads are primarily pure text that compresses well, while much
* larger payloads are heavily compressed binary data. {@link UploadPackServlet}
* is one such servlet.
*/
class SmartOutputStream extends TemporaryBuffer {
private static final int LIMIT = 32 * 1024;
private final HttpServletRequest req;
private final HttpServletResponse rsp;
private boolean startedOutput;
SmartOutputStream(final HttpServletRequest req,
final HttpServletResponse rsp) {
super(LIMIT);
this.req = req;
this.rsp = rsp;
}
@Override
protected OutputStream overflow() throws IOException {
startedOutput = true;
return rsp.getOutputStream();
}
public void close() throws IOException {
super.close();
if (!startedOutput) {
// If output hasn't started yet, the entire thing fit into our
// buffer. Try to use a proper Content-Length header, and also
// deflate the response with gzip if it will be smaller.
TemporaryBuffer out = this;
if (256 < out.length() && acceptsGzipEncoding(req)) {
TemporaryBuffer gzbuf = new TemporaryBuffer.Heap(LIMIT);
try {
GZIPOutputStream gzip = new GZIPOutputStream(gzbuf);
out.writeTo(gzip, null);
gzip.close();
if (gzbuf.length() < out.length()) {
out = gzbuf;
rsp.setHeader(HDR_CONTENT_ENCODING, ENCODING_GZIP);
}
} catch (IOException err) {
// Most likely caused by overflowing the buffer, meaning
// its larger if it were compressed. Discard compressed
// copy and use the original.
}
}
// The Content-Length cannot overflow when cast to an int, our
// hardcoded LIMIT constant above assures us we wouldn't store
// more than 2 GiB of content in memory.
rsp.setContentLength((int) out.length());
final OutputStream os = rsp.getOutputStream();
try {
out.writeTo(os, null);
os.flush();
} finally {
os.close();
}
}
}
}

View File

@ -46,9 +46,7 @@
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import static org.eclipse.jgit.http.server.ServletUtils.getRepository; import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
import static org.eclipse.jgit.http.server.ServletUtils.send;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import javax.servlet.Filter; import javax.servlet.Filter;
@ -90,13 +88,14 @@ public void doFilter(ServletRequest request, ServletResponse response,
final HttpServletResponse rsp = (HttpServletResponse) response; final HttpServletResponse rsp = (HttpServletResponse) response;
try { try {
final Repository db = getRepository(req); final Repository db = getRepository(req);
final ByteArrayOutputStream buf = new ByteArrayOutputStream(); rsp.setContentType("application/x-" + svc + "-advertisement");
final SmartOutputStream buf = new SmartOutputStream(req, rsp);
final PacketLineOut out = new PacketLineOut(buf); final PacketLineOut out = new PacketLineOut(buf);
out.writeString("# service=" + svc + "\n"); out.writeString("# service=" + svc + "\n");
out.end(); out.end();
advertise(req, db, new PacketLineOutRefAdvertiser(out)); advertise(req, db, new PacketLineOutRefAdvertiser(out));
rsp.setContentType("application/x-" + svc + "-advertisement"); buf.close();
send(buf.toByteArray(), req, rsp);
} catch (ServiceNotAuthorizedException e) { } catch (ServiceNotAuthorizedException e) {
rsp.sendError(SC_UNAUTHORIZED); rsp.sendError(SC_UNAUTHORIZED);

View File

@ -106,7 +106,10 @@ public void doPost(final HttpServletRequest req,
final UploadPack up = uploadPackFactory.create(req, db); final UploadPack up = uploadPackFactory.create(req, db);
up.setBiDirectionalPipe(false); up.setBiDirectionalPipe(false);
rsp.setContentType(RSP_TYPE); rsp.setContentType(RSP_TYPE);
up.upload(getInputStream(req), rsp.getOutputStream(), null);
final SmartOutputStream out = new SmartOutputStream(req, rsp);
up.upload(getInputStream(req), out, null);
out.close();
} catch (ServiceNotAuthorizedException e) { } catch (ServiceNotAuthorizedException e) {
rsp.reset(); rsp.reset();

View File

@ -0,0 +1,151 @@
/*
* Copyright (C) 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.http.test;
import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jgit.errors.RemoteRepositoryException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.http.server.GitServlet;
import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory;
import org.eclipse.jgit.http.server.resolver.RepositoryResolver;
import org.eclipse.jgit.http.server.resolver.ServiceNotAuthorizedException;
import org.eclipse.jgit.http.server.resolver.ServiceNotEnabledException;
import org.eclipse.jgit.http.test.util.HttpTestCase;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryConfig;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.eclipse.jgit.transport.Transport;
import org.eclipse.jgit.transport.URIish;
public class AdvertiseErrorTest extends HttpTestCase {
private Repository remoteRepository;
private URIish remoteURI;
protected void setUp() throws Exception {
super.setUp();
final TestRepository src = createTestRepository();
final String srcName = src.getRepository().getDirectory().getName();
ServletContextHandler app = server.addContext("/git");
GitServlet gs = new GitServlet();
gs.setRepositoryResolver(new RepositoryResolver() {
public Repository open(HttpServletRequest req, String name)
throws RepositoryNotFoundException,
ServiceNotEnabledException {
if (!name.equals(srcName))
throw new RepositoryNotFoundException(name);
final Repository db = src.getRepository();
db.incrementOpen();
return db;
}
});
gs.setReceivePackFactory(new DefaultReceivePackFactory() {
public ReceivePack create(HttpServletRequest req, Repository db)
throws ServiceNotEnabledException,
ServiceNotAuthorizedException {
ReceivePack rp = super.create(req, db);
rp.sendError("message line 1");
rp.sendError("no soup for you!");
rp.sendError("come back next year!");
return rp;
}
});
app.addServlet(new ServletHolder(gs), "/*");
server.setUp();
remoteRepository = src.getRepository();
remoteURI = toURIish(app, srcName);
RepositoryConfig cfg = remoteRepository.getConfig();
cfg.setBoolean("http", null, "receivepack", true);
cfg.save();
}
public void testPush_CreateBranch() throws Exception {
final TestRepository src = createTestRepository();
final RevBlob Q_txt = src.blob("new text");
final RevCommit Q = src.commit().add("Q", Q_txt).create();
final Repository db = src.getRepository();
final String dstName = Constants.R_HEADS + "new.branch";
final Transport t = Transport.open(db, remoteURI);
try {
final String srcExpr = Q.name();
final boolean forceUpdate = false;
final String localName = null;
final ObjectId oldId = null;
RemoteRefUpdate update = new RemoteRefUpdate(src.getRepository(),
srcExpr, dstName, forceUpdate, localName, oldId);
try {
t.push(NullProgressMonitor.INSTANCE, Collections
.singleton(update));
fail("push completed without throwing exception");
} catch (RemoteRepositoryException error) {
assertEquals(remoteURI + ": message line 1\n" //
+ "no soup for you!\n" //
+ "come back next year!", //
error.getMessage());
}
} finally {
t.close();
}
}
}

View File

@ -0,0 +1,174 @@
/*
* Copyright (C) 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.http.test;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.http.server.GitServlet;
import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory;
import org.eclipse.jgit.http.server.resolver.RepositoryResolver;
import org.eclipse.jgit.http.server.resolver.ServiceNotAuthorizedException;
import org.eclipse.jgit.http.server.resolver.ServiceNotEnabledException;
import org.eclipse.jgit.http.test.util.AccessEvent;
import org.eclipse.jgit.http.test.util.HttpTestCase;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryConfig;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.PreReceiveHook;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.eclipse.jgit.transport.Transport;
import org.eclipse.jgit.transport.URIish;
public class HookMessageTest extends HttpTestCase {
private Repository remoteRepository;
private URIish remoteURI;
protected void setUp() throws Exception {
super.setUp();
final TestRepository src = createTestRepository();
final String srcName = src.getRepository().getDirectory().getName();
ServletContextHandler app = server.addContext("/git");
GitServlet gs = new GitServlet();
gs.setRepositoryResolver(new RepositoryResolver() {
public Repository open(HttpServletRequest req, String name)
throws RepositoryNotFoundException,
ServiceNotEnabledException {
if (!name.equals(srcName))
throw new RepositoryNotFoundException(name);
final Repository db = src.getRepository();
db.incrementOpen();
return db;
}
});
gs.setReceivePackFactory(new DefaultReceivePackFactory() {
public ReceivePack create(HttpServletRequest req, Repository db)
throws ServiceNotEnabledException,
ServiceNotAuthorizedException {
ReceivePack recv = super.create(req, db);
recv.setPreReceiveHook(new PreReceiveHook() {
public void onPreReceive(ReceivePack rp,
Collection<ReceiveCommand> commands) {
rp.sendMessage("message line 1");
rp.sendError("no soup for you!");
rp.sendMessage("come back next year!");
}
});
return recv;
}
});
app.addServlet(new ServletHolder(gs), "/*");
server.setUp();
remoteRepository = src.getRepository();
remoteURI = toURIish(app, srcName);
RepositoryConfig cfg = remoteRepository.getConfig();
cfg.setBoolean("http", null, "receivepack", true);
cfg.save();
}
public void testPush_CreateBranch() throws Exception {
final TestRepository src = createTestRepository();
final RevBlob Q_txt = src.blob("new text");
final RevCommit Q = src.commit().add("Q", Q_txt).create();
final Repository db = src.getRepository();
final String dstName = Constants.R_HEADS + "new.branch";
Transport t;
PushResult result;
t = Transport.open(db, remoteURI);
try {
final String srcExpr = Q.name();
final boolean forceUpdate = false;
final String localName = null;
final ObjectId oldId = null;
RemoteRefUpdate update = new RemoteRefUpdate(src.getRepository(),
srcExpr, dstName, forceUpdate, localName, oldId);
result = t.push(NullProgressMonitor.INSTANCE, Collections
.singleton(update));
} finally {
t.close();
}
assertTrue(remoteRepository.hasObject(Q_txt));
assertNotNull("has " + dstName, remoteRepository.getRef(dstName));
assertEquals(Q, remoteRepository.getRef(dstName).getObjectId());
fsck(remoteRepository, Q);
List<AccessEvent> requests = getRequests();
assertEquals(2, requests.size());
AccessEvent service = requests.get(1);
assertEquals("POST", service.getMethod());
assertEquals(join(remoteURI, "git-receive-pack"), service.getPath());
assertEquals(200, service.getStatus());
assertEquals("message line 1\n" //
+ "error: no soup for you!\n" //
+ "come back next year!\n", //
result.getMessages());
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2009-2010, Google Inc. * Copyright (C) 2010, Google Inc.
* and other copyright owners as documented in the project's IP log. * and other copyright owners as documented in the project's IP log.
* *
* This program and the accompanying materials are made available * This program and the accompanying materials are made available
@ -253,8 +253,6 @@ public void testInitialClone_Small() throws Exception {
assertEquals(200, service.getStatus()); assertEquals(200, service.getStatus());
assertEquals("application/x-git-upload-pack-result", service assertEquals("application/x-git-upload-pack-result", service
.getResponseHeader(HDR_CONTENT_TYPE)); .getResponseHeader(HDR_CONTENT_TYPE));
assertNull("no compression (never compressed)", service
.getResponseHeader(HDR_CONTENT_ENCODING));
} }
public void testFetchUpdateExisting() throws Exception { public void testFetchUpdateExisting() throws Exception {

View File

@ -43,9 +43,11 @@
package org.eclipse.jgit.junit; package org.eclipse.jgit.junit;
import java.io.BufferedOutputStream;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -570,10 +572,10 @@ public void packAndPrune() throws Exception {
pw.preparePack(all, Collections.<ObjectId> emptySet()); pw.preparePack(all, Collections.<ObjectId> emptySet());
final ObjectId name = pw.computeName(); final ObjectId name = pw.computeName();
FileOutputStream out; OutputStream out;
final File pack = nameFor(odb, name, ".pack"); final File pack = nameFor(odb, name, ".pack");
out = new FileOutputStream(pack); out = new BufferedOutputStream(new FileOutputStream(pack));
try { try {
pw.writePack(out); pw.writePack(out);
} finally { } finally {
@ -582,7 +584,7 @@ public void packAndPrune() throws Exception {
pack.setReadOnly(); pack.setReadOnly();
final File idx = nameFor(odb, name, ".idx"); final File idx = nameFor(odb, name, ".idx");
out = new FileOutputStream(idx); out = new BufferedOutputStream(new FileOutputStream(idx));
try { try {
pw.writeIndex(out); pw.writeIndex(out);
} finally { } finally {

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (C) 2008, Charles O'Farrell <charleso@charleso.org> * Copyright (C) 2008, Charles O'Farrell <charleso@charleso.org>
* Copyright (C) 2008-2009, Google Inc. * Copyright (C) 2008-2010, Google Inc.
* Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* and other copyright owners as documented in the project's IP log. * and other copyright owners as documented in the project's IP log.
@ -79,6 +79,34 @@ protected void showFetchResult(final Transport tn, final FetchResult r) {
out.format(" %c %-17s %-10s -> %s", type, longType, src, dst); out.format(" %c %-17s %-10s -> %s", type, longType, src, dst);
out.println(); out.println();
} }
showRemoteMessages(r.getMessages());
}
static void showRemoteMessages(String pkt) {
while (0 < pkt.length()) {
final int lf = pkt.indexOf('\n');
final int cr = pkt.indexOf('\r');
final int s;
if (0 <= lf && 0 <= cr)
s = Math.min(lf, cr);
else if (0 <= lf)
s = lf;
else if (0 <= cr)
s = cr;
else {
System.err.println("remote: " + pkt);
break;
}
if (pkt.charAt(s) == '\r')
System.err.print("remote: " + pkt.substring(0, s) + "\r");
else
System.err.println("remote: " + pkt.substring(0, s));
pkt = pkt.substring(s + 1);
}
System.err.flush();
} }
private String longTypeOf(final TrackingRefUpdate u) { private String longTypeOf(final TrackingRefUpdate u) {

View File

@ -162,6 +162,7 @@ private void printPushResult(final URIish uri,
printRefUpdateResult(uri, result, rru); printRefUpdateResult(uri, result, rru);
} }
AbstractFetchCommand.showRemoteMessages(result.getMessages());
if (everythingUpToDate) if (everythingUpToDate)
out.println("Everything up-to-date"); out.println("Everything up-to-date");
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2009, Google Inc. * Copyright (C) 2009-2010, Google Inc.
* Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@dewire.com>
* and other copyright owners as documented in the project's IP log. * and other copyright owners as documented in the project's IP log.
* *
@ -44,9 +44,11 @@
package org.eclipse.jgit.lib; package org.eclipse.jgit.lib;
import java.io.BufferedOutputStream;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays; import java.util.Arrays;
import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@ -203,16 +205,16 @@ private File[] pack(final Repository src, final RevObject... list)
private static void write(final File[] files, final PackWriter pw) private static void write(final File[] files, final PackWriter pw)
throws IOException { throws IOException {
final long begin = files[0].getParentFile().lastModified(); final long begin = files[0].getParentFile().lastModified();
FileOutputStream out; OutputStream out;
out = new FileOutputStream(files[0]); out = new BufferedOutputStream(new FileOutputStream(files[0]));
try { try {
pw.writePack(out); pw.writePack(out);
} finally { } finally {
out.close(); out.close();
} }
out = new FileOutputStream(files[1]); out = new BufferedOutputStream(new FileOutputStream(files[1]));
try { try {
pw.writeIndex(out); pw.writeIndex(out);
} finally { } finally {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2009, Google Inc. * Copyright (C) 2009-2010, Google Inc.
* and other copyright owners as documented in the project's IP log. * and other copyright owners as documented in the project's IP log.
* *
* This program and the accompanying materials are made available * This program and the accompanying materials are made available
@ -119,8 +119,7 @@ public void testWritePacket2() throws IOException {
} }
public void testWritePacket3() throws IOException { public void testWritePacket3() throws IOException {
final int buflen = SideBandOutputStream.MAX_BUF final int buflen = SideBandOutputStream.MAX_BUF - 5;
- SideBandOutputStream.HDR_SIZE;
final byte[] buf = new byte[buflen]; final byte[] buf = new byte[buflen];
for (int i = 0; i < buf.length; i++) { for (int i = 0; i < buf.length; i++) {
buf[i] = (byte) i; buf[i] = (byte) i;
@ -137,23 +136,6 @@ public void testWritePacket3() throws IOException {
} }
} }
// writeChannelPacket
public void testWriteChannelPacket1() throws IOException {
out.writeChannelPacket(1, new byte[] { 'a' }, 0, 1);
assertBuffer("0006\001a");
}
public void testWriteChannelPacket2() throws IOException {
out.writeChannelPacket(2, new byte[] { 'b' }, 0, 1);
assertBuffer("0006\002b");
}
public void testWriteChannelPacket3() throws IOException {
out.writeChannelPacket(3, new byte[] { 'c' }, 0, 1);
assertBuffer("0006\003c");
}
// flush // flush
public void testFlush() throws IOException { public void testFlush() throws IOException {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2009, Google Inc. * Copyright (C) 2009-2010, Google Inc.
* and other copyright owners as documented in the project's IP log. * and other copyright owners as documented in the project's IP log.
* *
* This program and the accompanying materials are made available * This program and the accompanying materials are made available
@ -43,6 +43,13 @@
package org.eclipse.jgit.transport; package org.eclipse.jgit.transport;
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.HDR_SIZE;
import static org.eclipse.jgit.transport.SideBandOutputStream.MAX_BUF;
import static org.eclipse.jgit.transport.SideBandOutputStream.SMALL_BUF;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@ -58,62 +65,90 @@
public class SideBandOutputStreamTest extends TestCase { public class SideBandOutputStreamTest extends TestCase {
private ByteArrayOutputStream rawOut; private ByteArrayOutputStream rawOut;
private PacketLineOut pckOut;
protected void setUp() throws Exception { protected void setUp() throws Exception {
super.setUp(); super.setUp();
rawOut = new ByteArrayOutputStream(); rawOut = new ByteArrayOutputStream();
pckOut = new PacketLineOut(rawOut);
} }
public void testWrite_CH_DATA() throws IOException { public void testWrite_CH_DATA() throws IOException {
final SideBandOutputStream out; final SideBandOutputStream out;
out = new SideBandOutputStream(SideBandOutputStream.CH_DATA, pckOut); out = new SideBandOutputStream(CH_DATA, SMALL_BUF, rawOut);
out.write(new byte[] { 'a', 'b', 'c' }); out.write(new byte[] { 'a', 'b', 'c' });
out.flush();
assertBuffer("0008\001abc"); assertBuffer("0008\001abc");
} }
public void testWrite_CH_PROGRESS() throws IOException { public void testWrite_CH_PROGRESS() throws IOException {
final SideBandOutputStream out; final SideBandOutputStream out;
out = new SideBandOutputStream(SideBandOutputStream.CH_PROGRESS, pckOut); out = new SideBandOutputStream(CH_PROGRESS, SMALL_BUF, rawOut);
out.write(new byte[] { 'a', 'b', 'c' }); out.write(new byte[] { 'a', 'b', 'c' });
out.flush();
assertBuffer("0008\002abc"); assertBuffer("0008\002abc");
} }
public void testWrite_CH_ERROR() throws IOException { public void testWrite_CH_ERROR() throws IOException {
final SideBandOutputStream out; final SideBandOutputStream out;
out = new SideBandOutputStream(SideBandOutputStream.CH_ERROR, pckOut); out = new SideBandOutputStream(CH_ERROR, SMALL_BUF, rawOut);
out.write(new byte[] { 'a', 'b', 'c' }); out.write(new byte[] { 'a', 'b', 'c' });
out.flush();
assertBuffer("0008\003abc"); assertBuffer("0008\003abc");
} }
public void testWrite_Small() throws IOException { public void testWrite_Small() throws IOException {
final SideBandOutputStream out; final SideBandOutputStream out;
out = new SideBandOutputStream(SideBandOutputStream.CH_DATA, pckOut); out = new SideBandOutputStream(CH_DATA, SMALL_BUF, rawOut);
out.write('a'); out.write('a');
out.write('b'); out.write('b');
out.write('c'); out.write('c');
out.flush();
assertBuffer("0008\001abc");
}
public void testWrite_SmallBlocks1() throws IOException {
final SideBandOutputStream out;
out = new SideBandOutputStream(CH_DATA, 6, rawOut);
out.write('a');
out.write('b');
out.write('c');
out.flush();
assertBuffer("0006\001a0006\001b0006\001c"); assertBuffer("0006\001a0006\001b0006\001c");
} }
public void testWrite_SmallBlocks2() throws IOException {
final SideBandOutputStream out;
out = new SideBandOutputStream(CH_DATA, 6, rawOut);
out.write(new byte[] { 'a', 'b', 'c' });
out.flush();
assertBuffer("0006\001a0006\001b0006\001c");
}
public void testWrite_SmallBlocks3() throws IOException {
final SideBandOutputStream out;
out = new SideBandOutputStream(CH_DATA, 7, rawOut);
out.write('a');
out.write(new byte[] { 'b', 'c' });
out.flush();
assertBuffer("0007\001ab0006\001c");
}
public void testWrite_Large() throws IOException { public void testWrite_Large() throws IOException {
final int buflen = SideBandOutputStream.MAX_BUF final int buflen = MAX_BUF - HDR_SIZE;
- SideBandOutputStream.HDR_SIZE;
final byte[] buf = new byte[buflen]; final byte[] buf = new byte[buflen];
for (int i = 0; i < buf.length; i++) { for (int i = 0; i < buf.length; i++) {
buf[i] = (byte) i; buf[i] = (byte) i;
} }
final SideBandOutputStream out; final SideBandOutputStream out;
out = new SideBandOutputStream(SideBandOutputStream.CH_DATA, pckOut); out = new SideBandOutputStream(CH_DATA, MAX_BUF, rawOut);
out.write(buf); out.write(buf);
out.flush();
final byte[] act = rawOut.toByteArray(); final byte[] act = rawOut.toByteArray();
final String explen = Integer.toString(buf.length + 5, 16); final String explen = Integer.toString(buf.length + HDR_SIZE, 16);
assertEquals(5 + buf.length, act.length); assertEquals(HDR_SIZE + buf.length, act.length);
assertEquals(new String(act, 0, 4, "UTF-8"), explen); assertEquals(new String(act, 0, 4, "UTF-8"), explen);
assertEquals(1, act[4]); assertEquals(1, act[4]);
for (int i = 0, j = 5; i < buf.length; i++, j++) { for (int i = 0, j = HDR_SIZE; i < buf.length; i++, j++) {
assertEquals(buf[i], act[j]); assertEquals(buf[i], act[j]);
} }
} }
@ -132,17 +167,63 @@ public void flush() throws IOException {
} }
}; };
new SideBandOutputStream(SideBandOutputStream.CH_DATA, new SideBandOutputStream(CH_DATA, SMALL_BUF, mockout).flush();
new PacketLineOut(mockout)).flush();
assertEquals(0, flushCnt[0]);
new SideBandOutputStream(SideBandOutputStream.CH_ERROR,
new PacketLineOut(mockout)).flush();
assertEquals(1, flushCnt[0]); assertEquals(1, flushCnt[0]);
}
new SideBandOutputStream(SideBandOutputStream.CH_PROGRESS, public void testConstructor_RejectsBadChannel() {
new PacketLineOut(mockout)).flush(); try {
assertEquals(2, flushCnt[0]); new SideBandOutputStream(-1, MAX_BUF, rawOut);
fail("Accepted -1 channel number");
} catch (IllegalArgumentException e) {
assertEquals("channel -1 must be in range [0, 255]", e.getMessage());
}
try {
new SideBandOutputStream(0, MAX_BUF, rawOut);
fail("Accepted 0 channel number");
} catch (IllegalArgumentException e) {
assertEquals("channel 0 must be in range [0, 255]", e.getMessage());
}
try {
new SideBandOutputStream(256, MAX_BUF, rawOut);
fail("Accepted 256 channel number");
} catch (IllegalArgumentException e) {
assertEquals("channel 256 must be in range [0, 255]", e
.getMessage());
}
}
public void testConstructor_RejectsBadBufferSize() {
try {
new SideBandOutputStream(CH_DATA, -1, rawOut);
fail("Accepted -1 for buffer size");
} catch (IllegalArgumentException e) {
assertEquals("packet size -1 must be >= 5", e.getMessage());
}
try {
new SideBandOutputStream(CH_DATA, 0, rawOut);
fail("Accepted 0 for buffer size");
} catch (IllegalArgumentException e) {
assertEquals("packet size 0 must be >= 5", e.getMessage());
}
try {
new SideBandOutputStream(CH_DATA, 1, rawOut);
fail("Accepted 1 for buffer size");
} catch (IllegalArgumentException e) {
assertEquals("packet size 1 must be >= 5", e.getMessage());
}
try {
new SideBandOutputStream(CH_DATA, Integer.MAX_VALUE, rawOut);
fail("Accepted " + Integer.MAX_VALUE + " for buffer size");
} catch (IllegalArgumentException e) {
assertEquals("packet size " + Integer.MAX_VALUE
+ " must be <= 65520", e.getMessage());
}
} }
private void assertBuffer(final String exp) throws IOException { private void assertBuffer(final String exp) throws IOException {

View File

@ -0,0 +1,70 @@
/*
* Copyright (C) 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.errors;
import org.eclipse.jgit.transport.URIish;
/**
* Contains a message from the remote repository indicating a problem.
* <p>
* Some remote repositories may send customized error messages describing why
* they cannot be accessed. These messages are wrapped up in this exception and
* thrown to the caller of the transport operation.
*/
public class RemoteRepositoryException extends TransportException {
private static final long serialVersionUID = 1L;
/**
* Constructs a RemoteRepositoryException for a message.
*
* @param uri
* URI used for transport
* @param message
* message, exactly as supplied by the remote repository. May
* contain LFs (newlines) if the remote formatted it that way.
*/
public RemoteRepositoryException(URIish uri, String message) {
super(uri, message);
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008-2009, Google Inc. * Copyright (C) 2008-2010, Google Inc.
* Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
* and other copyright owners as documented in the project's IP log. * and other copyright owners as documented in the project's IP log.
* *
@ -44,7 +44,6 @@
package org.eclipse.jgit.lib; package org.eclipse.jgit.lib;
import java.io.BufferedOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.MessageDigest; import java.security.MessageDigest;
@ -97,7 +96,6 @@
* undefined behavior. * undefined behavior.
* </p> * </p>
*/ */
public class PackWriter { public class PackWriter {
/** /**
* Title of {@link ProgressMonitor} task used during counting objects to * Title of {@link ProgressMonitor} task used during counting objects to
@ -578,9 +576,8 @@ private List<ObjectToPack> sortByName() {
* </p> * </p>
* *
* @param packStream * @param packStream
* output stream of pack data. If the stream is not buffered it * output stream of pack data. The stream should be buffered by
* will be buffered by the writer. Caller is responsible for * the caller. The caller is responsible for closing the stream.
* closing the stream.
* @throws IOException * @throws IOException
* an error occurred reading a local object's data to include in * an error occurred reading a local object's data to include in
* the pack, or writing compressed object data to the output * the pack, or writing compressed object data to the output
@ -590,8 +587,6 @@ public void writePack(OutputStream packStream) throws IOException {
if (reuseDeltas || reuseObjects) if (reuseDeltas || reuseObjects)
searchForReuse(); searchForReuse();
if (!(packStream instanceof BufferedOutputStream))
packStream = new BufferedOutputStream(packStream);
out = new PackOutputStream(packStream); out = new PackOutputStream(packStream);
writeMonitor.beginTask(WRITING_OBJECTS_PROGRESS, getObjectsNumber()); writeMonitor.beginTask(WRITING_OBJECTS_PROGRESS, getObjectsNumber());
@ -599,7 +594,6 @@ public void writePack(OutputStream packStream) throws IOException {
writeObjects(); writeObjects();
writeChecksum(); writeChecksum();
out.flush();
windowCursor.release(); windowCursor.release();
writeMonitor.endTask(); writeMonitor.endTask();
} }

View File

@ -1,4 +1,5 @@
/* /*
* Copyright (C) 2010, Google Inc.
* Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
@ -45,6 +46,8 @@
package org.eclipse.jgit.transport; package org.eclipse.jgit.transport;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
@ -58,12 +61,13 @@
* @see BasePackConnection * @see BasePackConnection
* @see BaseFetchConnection * @see BaseFetchConnection
*/ */
abstract class BaseConnection implements Connection { public abstract class BaseConnection implements Connection {
private Map<String, Ref> advertisedRefs = Collections.emptyMap(); private Map<String, Ref> advertisedRefs = Collections.emptyMap();
private boolean startedOperation; private boolean startedOperation;
private Writer messageWriter;
public Map<String, Ref> getRefsMap() { public Map<String, Ref> getRefsMap() {
return advertisedRefs; return advertisedRefs;
} }
@ -76,6 +80,10 @@ public final Ref getRef(final String name) {
return advertisedRefs.get(name); return advertisedRefs.get(name);
} }
public String getMessages() {
return messageWriter != null ? messageWriter.toString() : "";
}
public abstract void close(); public abstract void close();
/** /**
@ -106,4 +114,29 @@ protected void markStartedOperation() throws TransportException {
"Only one operation call per connection is supported."); "Only one operation call per connection is supported.");
startedOperation = true; startedOperation = true;
} }
/**
* Get the writer that buffers messages from the remote side.
*
* @return writer to store messages from the remote.
*/
protected Writer getMessageWriter() {
if (messageWriter == null)
setMessageWriter(new StringWriter());
return messageWriter;
}
/**
* Set the writer that buffers messages from the remote side.
*
* @param writer
* the writer that messages will be delivered to. The writer's
* {@code toString()} method should be overridden to return the
* complete contents.
*/
protected void setMessageWriter(Writer writer) {
if (messageWriter != null)
throw new IllegalStateException("Writer already initialized");
messageWriter = writer;
}
} }

View File

@ -1,5 +1,4 @@
/* /*
* Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
* Copyright (C) 2008-2010, Google Inc. * Copyright (C) 2008-2010, Google Inc.
* Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
@ -47,8 +46,6 @@
package org.eclipse.jgit.transport; package org.eclipse.jgit.transport;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -59,6 +56,7 @@
import org.eclipse.jgit.errors.NoRemoteRepositoryException; import org.eclipse.jgit.errors.NoRemoteRepositoryException;
import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.errors.RemoteRepositoryException;
import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ObjectIdRef;
@ -96,10 +94,10 @@ abstract class BasePackConnection extends BaseConnection {
/** Timer to manage {@link #timeoutIn} and {@link #timeoutOut}. */ /** Timer to manage {@link #timeoutIn} and {@link #timeoutOut}. */
private InterruptTimer myTimer; private InterruptTimer myTimer;
/** Buffered input stream reading from the remote. */ /** Input stream reading from the remote. */
protected InputStream in; protected InputStream in;
/** Buffered output stream sending to the remote. */ /** Output stream sending to the remote. */
protected OutputStream out; protected OutputStream out;
/** Packet line decoder around {@link #in}. */ /** Packet line decoder around {@link #in}. */
@ -126,6 +124,17 @@ abstract class BasePackConnection extends BaseConnection {
uri = transport.uri; uri = transport.uri;
} }
/**
* Configure this connection with the directional pipes.
*
* @param myIn
* input stream to receive data from the peer. Caller must ensure
* the input is buffered, otherwise read performance may suffer.
* @param myOut
* output stream to transmit data to the peer. Caller must ensure
* the output is buffered, otherwise write performance may
* suffer.
*/
protected final void init(InputStream myIn, OutputStream myOut) { protected final void init(InputStream myIn, OutputStream myOut) {
final int timeout = transport.getTimeout(); final int timeout = transport.getTimeout();
if (timeout > 0) { if (timeout > 0) {
@ -139,16 +148,27 @@ protected final void init(InputStream myIn, OutputStream myOut) {
myOut = timeoutOut; myOut = timeoutOut;
} }
in = myIn instanceof BufferedInputStream ? myIn in = myIn;
: new BufferedInputStream(myIn, IndexPack.BUFFER_SIZE); out = myOut;
out = myOut instanceof BufferedOutputStream ? myOut
: new BufferedOutputStream(myOut);
pckIn = new PacketLineIn(in); pckIn = new PacketLineIn(in);
pckOut = new PacketLineOut(out); pckOut = new PacketLineOut(out);
outNeedsEnd = true; outNeedsEnd = true;
} }
/**
* Reads the advertised references through the initialized stream.
* <p>
* Subclass implementations may call this method only after setting up the
* input and output streams with {@link #init(InputStream, OutputStream)}.
* <p>
* If any errors occur, this connection is automatically closed by invoking
* {@link #close()} and the exception is wrapped (if necessary) and thrown
* as a {@link TransportException}.
*
* @throws TransportException
* the reference list could not be scanned.
*/
protected void readAdvertisedRefs() throws TransportException { protected void readAdvertisedRefs() throws TransportException {
try { try {
readAdvertisedRefsImpl(); readAdvertisedRefsImpl();
@ -179,6 +199,12 @@ private void readAdvertisedRefsImpl() throws IOException {
if (line == PacketLineIn.END) if (line == PacketLineIn.END)
break; break;
if (line.startsWith("ERR ")) {
// This is a customized remote service error.
// Users should be informed about it.
throw new RemoteRepositoryException(uri, line.substring(4));
}
if (avail.isEmpty()) { if (avail.isEmpty()) {
final int nul = line.indexOf('\0'); final int nul = line.indexOf('\0');
if (nul >= 0) { if (nul >= 0) {

View File

@ -46,6 +46,7 @@
package org.eclipse.jgit.transport; package org.eclipse.jgit.transport;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
@ -609,7 +610,11 @@ private void markCommon(final RevObject obj, final AckNackResult anr)
private void receivePack(final ProgressMonitor monitor) throws IOException { private void receivePack(final ProgressMonitor monitor) throws IOException {
final IndexPack ip; final IndexPack ip;
ip = IndexPack.create(local, sideband ? pckIn.sideband(monitor) : in); InputStream input = in;
if (sideband)
input = new SideBandInputStream(input, monitor, getMessageWriter());
ip = IndexPack.create(local, input);
ip.setFixThin(thinPack); ip.setFixThin(thinPack);
ip.setObjectChecking(transport.isCheckFetchedObjects()); ip.setObjectChecking(transport.isCheckFetchedObjects());
ip.index(monitor); ip.index(monitor);

View File

@ -86,12 +86,16 @@ class BasePackPushConnection extends BasePackConnection implements
static final String CAPABILITY_OFS_DELTA = "ofs-delta"; static final String CAPABILITY_OFS_DELTA = "ofs-delta";
static final String CAPABILITY_SIDE_BAND_64K = "side-band-64k";
private final boolean thinPack; private final boolean thinPack;
private boolean capableDeleteRefs; private boolean capableDeleteRefs;
private boolean capableReport; private boolean capableReport;
private boolean capableSideBand;
private boolean capableOfsDelta; private boolean capableOfsDelta;
private boolean sentCommand; private boolean sentCommand;
@ -143,8 +147,21 @@ protected void doPush(final ProgressMonitor monitor,
writeCommands(refUpdates.values(), monitor); writeCommands(refUpdates.values(), monitor);
if (writePack) if (writePack)
writePack(refUpdates, monitor); writePack(refUpdates, monitor);
if (sentCommand && capableReport) if (sentCommand) {
readStatusReport(refUpdates); if (capableReport)
readStatusReport(refUpdates);
if (capableSideBand) {
// Ensure the data channel is at EOF, so we know we have
// read all side-band data from all channels and have a
// complete copy of the messages (if any) buffered from
// the other data channels.
//
int b = in.read();
if (0 <= b)
throw new TransportException(uri, "expected EOF;"
+ " received '" + (char) b + "' instead");
}
}
} catch (TransportException e) { } catch (TransportException e) {
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
@ -156,7 +173,7 @@ protected void doPush(final ProgressMonitor monitor,
private void writeCommands(final Collection<RemoteRefUpdate> refUpdates, private void writeCommands(final Collection<RemoteRefUpdate> refUpdates,
final ProgressMonitor monitor) throws IOException { final ProgressMonitor monitor) throws IOException {
final String capabilities = enableCapabilities(); final String capabilities = enableCapabilities(monitor);
for (final RemoteRefUpdate rru : refUpdates) { for (final RemoteRefUpdate rru : refUpdates) {
if (!capableDeleteRefs && rru.isDelete()) { if (!capableDeleteRefs && rru.isDelete()) {
rru.setStatus(Status.REJECTED_NODELETE); rru.setStatus(Status.REJECTED_NODELETE);
@ -189,11 +206,18 @@ private void writeCommands(final Collection<RemoteRefUpdate> refUpdates,
outNeedsEnd = false; outNeedsEnd = false;
} }
private String enableCapabilities() { private String enableCapabilities(final ProgressMonitor monitor) {
final StringBuilder line = new StringBuilder(); final StringBuilder line = new StringBuilder();
capableReport = wantCapability(line, CAPABILITY_REPORT_STATUS); capableReport = wantCapability(line, CAPABILITY_REPORT_STATUS);
capableDeleteRefs = wantCapability(line, CAPABILITY_DELETE_REFS); capableDeleteRefs = wantCapability(line, CAPABILITY_DELETE_REFS);
capableOfsDelta = wantCapability(line, CAPABILITY_OFS_DELTA); capableOfsDelta = wantCapability(line, CAPABILITY_OFS_DELTA);
capableSideBand = wantCapability(line, CAPABILITY_SIDE_BAND_64K);
if (capableSideBand) {
in = new SideBandInputStream(in, monitor, getMessageWriter());
pckIn = new PacketLineIn(in);
}
if (line.length() > 0) if (line.length() > 0)
line.setCharAt(0, '\0'); line.setCharAt(0, '\0');
return line.toString(); return line.toString();
@ -220,6 +244,7 @@ private void writePack(final Map<String, RemoteRefUpdate> refUpdates,
writer.preparePack(newObjects, remoteObjects); writer.preparePack(newObjects, remoteObjects);
final long start = System.currentTimeMillis(); final long start = System.currentTimeMillis();
writer.writePack(out); writer.writePack(out);
out.flush();
packTransferTime = System.currentTimeMillis() - start; packTransferTime = System.currentTimeMillis() - start;
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008-2009, Google Inc. * Copyright (C) 2008-2010, Google Inc.
* and other copyright owners as documented in the project's IP log. * and other copyright owners as documented in the project's IP log.
* *
* This program and the accompanying materials are made available * This program and the accompanying materials are made available
@ -43,7 +43,6 @@
package org.eclipse.jgit.transport; package org.eclipse.jgit.transport;
import java.io.BufferedOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
@ -155,18 +154,15 @@ public void assume(final RevCommit c) {
* This method can only be called once per BundleWriter instance. * This method can only be called once per BundleWriter instance.
* *
* @param os * @param os
* the stream the bundle is written to. If the stream is not * the stream the bundle is written to. The stream should be
* buffered it will be buffered by the writer. Caller is * buffered by the caller. The caller is responsible for closing
* responsible for closing the stream. * the stream.
* @throws IOException * @throws IOException
* an error occurred reading a local object's data to include in * an error occurred reading a local object's data to include in
* the bundle, or writing compressed object data to the output * the bundle, or writing compressed object data to the output
* stream. * stream.
*/ */
public void writeBundle(OutputStream os) throws IOException { public void writeBundle(OutputStream os) throws IOException {
if (!(os instanceof BufferedOutputStream))
os = new BufferedOutputStream(os);
final HashSet<ObjectId> inc = new HashSet<ObjectId>(); final HashSet<ObjectId> inc = new HashSet<ObjectId>();
final HashSet<ObjectId> exc = new HashSet<ObjectId>(); final HashSet<ObjectId> exc = new HashSet<ObjectId>();
inc.addAll(include.values()); inc.addAll(include.values());

View File

@ -1,4 +1,5 @@
/* /*
* Copyright (C) 2010, Google Inc.
* Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* and other copyright owners as documented in the project's IP log. * and other copyright owners as documented in the project's IP log.
@ -104,7 +105,26 @@ public interface Connection {
* must close that network socket, disconnecting the two peers. If the * must close that network socket, disconnecting the two peers. If the
* remote repository is actually local (same system) this method must close * remote repository is actually local (same system) this method must close
* any open file handles used to read the "remote" repository. * any open file handles used to read the "remote" repository.
* <p>
* If additional messages were produced by the remote peer, these should
* still be retained in the connection instance for {@link #getMessages()}.
*/ */
public void close(); public void close();
/**
* Get the additional messages, if any, returned by the remote process.
* <p>
* These messages are most likely informational or error messages, sent by
* the remote peer, to help the end-user correct any problems that may have
* prevented the operation from completing successfully. Application UIs
* should try to show these in an appropriate context.
* <p>
* The message buffer is available after {@link #close()} has been called.
* Prior to closing the connection, the message buffer may be empty.
*
* @return the messages returned by the remote, most likely terminated by a
* newline (LF) character. The empty string is returned if the
* remote produced no additional messages.
*/
public String getMessages();
} }

View File

@ -146,7 +146,7 @@ else if (tagopt == TagOpt.FETCH_TAGS)
// Connection was used for object transfer. If we // Connection was used for object transfer. If we
// do another fetch we must open a new connection. // do another fetch we must open a new connection.
// //
closeConnection(); closeConnection(result);
} else { } else {
includedTags = false; includedTags = false;
} }
@ -170,7 +170,7 @@ else if (tagopt == TagOpt.FETCH_TAGS)
} }
} }
} finally { } finally {
closeConnection(); closeConnection(result);
} }
final RevWalk walk = new RevWalk(transport.local); final RevWalk walk = new RevWalk(transport.local);
@ -210,9 +210,10 @@ private void fetchObjects(final ProgressMonitor monitor)
"peer did not supply a complete object graph"); "peer did not supply a complete object graph");
} }
private void closeConnection() { private void closeConnection(final FetchResult result) {
if (conn != null) { if (conn != null) {
conn.close(); conn.close();
result.addMessages(conn.getMessages());
conn = null; conn = null;
} }
} }

View File

@ -1,4 +1,5 @@
/* /*
* Copyright (C) 2010, Google Inc.
* Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
* Copyright (C) 2007-2009, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2007-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
@ -65,6 +66,8 @@ public abstract class OperationResult {
final SortedMap<String, TrackingRefUpdate> updates = new TreeMap<String, TrackingRefUpdate>(); final SortedMap<String, TrackingRefUpdate> updates = new TreeMap<String, TrackingRefUpdate>();
StringBuilder messageBuffer;
/** /**
* Get the URI this result came from. * Get the URI this result came from.
* <p> * <p>
@ -136,4 +139,30 @@ void setAdvertisedRefs(final URIish u, final Map<String, Ref> ar) {
void add(final TrackingRefUpdate u) { void add(final TrackingRefUpdate u) {
updates.put(u.getLocalName(), u); updates.put(u.getLocalName(), u);
} }
/**
* Get the additional messages, if any, returned by the remote process.
* <p>
* These messages are most likely informational or error messages, sent by
* the remote peer, to help the end-user correct any problems that may have
* prevented the operation from completing successfully. Application UIs
* should try to show these in an appropriate context.
*
* @return the messages returned by the remote, most likely terminated by a
* newline (LF) character. The empty string is returned if the
* remote produced no additional messages.
*/
public String getMessages() {
return messageBuffer != null ? messageBuffer.toString() : "";
}
void addMessages(final String msg) {
if (msg != null && msg.length() > 0) {
if (messageBuffer == null)
messageBuffer = new StringBuilder();
messageBuffer.append(msg);
if (!msg.endsWith("\n"))
messageBuffer.append('\n');
}
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008-2009, Google Inc. * Copyright (C) 2008-2010, Google Inc.
* Copyright (C) 2008-2009, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2008-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* and other copyright owners as documented in the project's IP log. * and other copyright owners as documented in the project's IP log.
@ -51,7 +51,6 @@
import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.RawParseUtils;
@ -73,15 +72,11 @@ static enum AckNackResult {
private final InputStream in; private final InputStream in;
private final byte[] lenbuffer; private final byte[] lineBuffer;
PacketLineIn(final InputStream i) { PacketLineIn(final InputStream i) {
in = i; in = i;
lenbuffer = new byte[4]; lineBuffer = new byte[SideBandOutputStream.SMALL_BUF];
}
InputStream sideband(final ProgressMonitor pm) {
return new SideBandInputStream(this, in, pm);
} }
AckNackResult readACK(final MutableObjectId returnedId) throws IOException { AckNackResult readACK(final MutableObjectId returnedId) throws IOException {
@ -129,22 +124,27 @@ String readStringRaw() throws IOException {
len -= 4; // length header (4 bytes) len -= 4; // length header (4 bytes)
final byte[] raw = new byte[len]; byte[] raw;
if (len <= lineBuffer.length)
raw = lineBuffer;
else
raw = new byte[len];
IO.readFully(in, raw, 0, len); IO.readFully(in, raw, 0, len);
return RawParseUtils.decode(Constants.CHARSET, raw, 0, len); return RawParseUtils.decode(Constants.CHARSET, raw, 0, len);
} }
int readLength() throws IOException { int readLength() throws IOException {
IO.readFully(in, lenbuffer, 0, 4); IO.readFully(in, lineBuffer, 0, 4);
try { try {
final int len = RawParseUtils.parseHexInt16(lenbuffer, 0); final int len = RawParseUtils.parseHexInt16(lineBuffer, 0);
if (len != 0 && len < 4) if (len != 0 && len < 4)
throw new ArrayIndexOutOfBoundsException(); throw new ArrayIndexOutOfBoundsException();
return len; return len;
} catch (ArrayIndexOutOfBoundsException err) { } catch (ArrayIndexOutOfBoundsException err) {
throw new IOException("Invalid packet line header: " throw new IOException("Invalid packet line header: "
+ (char) lenbuffer[0] + (char) lenbuffer[1] + (char) lineBuffer[0] + (char) lineBuffer[1]
+ (char) lenbuffer[2] + (char) lenbuffer[3]); + (char) lineBuffer[2] + (char) lineBuffer[3]);
} }
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008-2009, Google Inc. * Copyright (C) 2008-2010, Google Inc.
* Copyright (C) 2008-2009, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2008-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* and other copyright owners as documented in the project's IP log. * and other copyright owners as documented in the project's IP log.
@ -105,14 +105,6 @@ public void writePacket(final byte[] packet) throws IOException {
out.write(packet); out.write(packet);
} }
void writeChannelPacket(final int channel, final byte[] buf, int off,
int len) throws IOException {
formatLength(len + 5);
lenbuffer[4] = (byte) channel;
out.write(lenbuffer, 0, 5);
out.write(buf, off, len);
}
/** /**
* Write a packet end marker, sometimes referred to as a flush command. * Write a packet end marker, sometimes referred to as a flush command.
* <p> * <p>
@ -149,6 +141,10 @@ public void flush() throws IOException {
'7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
private void formatLength(int w) { private void formatLength(int w) {
formatLength(lenbuffer, w);
}
static void formatLength(byte[] lenbuffer, int w) {
int o = 3; int o = 3;
while (o >= 0 && w != 0) { while (o >= 0 && w != 0) {
lenbuffer[o--] = hexchar[w & 0xf]; lenbuffer[o--] = hexchar[w & 0xf];

View File

@ -122,8 +122,12 @@ class PushProcess {
PushResult execute(final ProgressMonitor monitor) PushResult execute(final ProgressMonitor monitor)
throws NotSupportedException, TransportException { throws NotSupportedException, TransportException {
monitor.beginTask(PROGRESS_OPENING_CONNECTION, ProgressMonitor.UNKNOWN); monitor.beginTask(PROGRESS_OPENING_CONNECTION, ProgressMonitor.UNKNOWN);
final PushResult res = new PushResult();
connection = transport.openPush(); connection = transport.openPush();
try { try {
res.setAdvertisedRefs(transport.getURI(), connection.getRefsMap());
res.setRemoteUpdates(toPush);
monitor.endTask(); monitor.endTask();
final Map<String, RemoteRefUpdate> preprocessed = prepareRemoteUpdates(); final Map<String, RemoteRefUpdate> preprocessed = prepareRemoteUpdates();
@ -133,10 +137,16 @@ else if (!preprocessed.isEmpty())
connection.push(monitor, preprocessed); connection.push(monitor, preprocessed);
} finally { } finally {
connection.close(); connection.close();
res.addMessages(connection.getMessages());
} }
if (!transport.isDryRun()) if (!transport.isDryRun())
updateTrackingRefs(); updateTrackingRefs();
return prepareOperationResult(); for (final RemoteRefUpdate rru : toPush.values()) {
final TrackingRefUpdate tru = rru.getTrackingRefUpdate();
if (tru != null)
res.add(tru);
}
return res;
} }
private Map<String, RemoteRefUpdate> prepareRemoteUpdates() private Map<String, RemoteRefUpdate> prepareRemoteUpdates()
@ -226,17 +236,4 @@ private void updateTrackingRefs() {
} }
} }
} }
private PushResult prepareOperationResult() {
final PushResult result = new PushResult();
result.setAdvertisedRefs(transport.getURI(), connection.getRefsMap());
result.setRemoteUpdates(toPush);
for (final RemoteRefUpdate rru : toPush.values()) {
final TrackingRefUpdate tru = rru.getTrackingRefUpdate();
if (tru != null)
result.add(tru);
}
return result;
}
} }

View File

@ -43,13 +43,20 @@
package org.eclipse.jgit.transport; package org.eclipse.jgit.transport;
import java.io.BufferedWriter; import static org.eclipse.jgit.transport.BasePackPushConnection.CAPABILITY_DELETE_REFS;
import static org.eclipse.jgit.transport.BasePackPushConnection.CAPABILITY_OFS_DELTA;
import static org.eclipse.jgit.transport.BasePackPushConnection.CAPABILITY_REPORT_STATUS;
import static org.eclipse.jgit.transport.BasePackPushConnection.CAPABILITY_SIDE_BAND_64K;
import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA;
import static org.eclipse.jgit.transport.SideBandOutputStream.CH_PROGRESS;
import static org.eclipse.jgit.transport.SideBandOutputStream.MAX_BUF;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.Writer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
@ -84,12 +91,6 @@
* Implements the server side of a push connection, receiving objects. * Implements the server side of a push connection, receiving objects.
*/ */
public class ReceivePack { public class ReceivePack {
static final String CAPABILITY_REPORT_STATUS = BasePackPushConnection.CAPABILITY_REPORT_STATUS;
static final String CAPABILITY_DELETE_REFS = BasePackPushConnection.CAPABILITY_DELETE_REFS;
static final String CAPABILITY_OFS_DELTA = BasePackPushConnection.CAPABILITY_OFS_DELTA;
/** Database we write the stored objects into. */ /** Database we write the stored objects into. */
private final Repository db; private final Repository db;
@ -151,7 +152,7 @@ public class ReceivePack {
private PacketLineOut pckOut; private PacketLineOut pckOut;
private PrintWriter msgs; private Writer msgs;
private IndexPack ip; private IndexPack ip;
@ -164,12 +165,18 @@ public class ReceivePack {
/** Commands to execute, as received by the client. */ /** Commands to execute, as received by the client. */
private List<ReceiveCommand> commands; private List<ReceiveCommand> commands;
/** Error to display instead of advertising the references. */
private StringBuilder advertiseError;
/** An exception caught while unpacking and fsck'ing the objects. */ /** An exception caught while unpacking and fsck'ing the objects. */
private Throwable unpackError; private Throwable unpackError;
/** if {@link #enabledCapablities} has {@link #CAPABILITY_REPORT_STATUS} */ /** If {@link BasePackPushConnection#CAPABILITY_REPORT_STATUS} is enabled. */
private boolean reportStatus; private boolean reportStatus;
/** If {@link BasePackPushConnection#CAPABILITY_SIDE_BAND_64K} is enabled. */
private boolean sideBand;
/** Lock around the received pack file, while updating refs. */ /** Lock around the received pack file, while updating refs. */
private PackLock packLock; private PackLock packLock;
@ -469,10 +476,17 @@ public List<ReceiveCommand> getAllCommands() {
} }
/** /**
* Send an error message to the client, if it supports receiving them. * Send an error message to the client.
* <p> * <p>
* If the client doesn't support receiving messages, the message will be * If any error messages are sent before the references are advertised to
* discarded, with no other indication to the caller or to the client. * 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.
* <p>
* 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.
* <p> * <p>
* {@link PreReceiveHook}s should always try to use * {@link PreReceiveHook}s should always try to use
* {@link ReceiveCommand#setResult(Result, String)} with a result status of * {@link ReceiveCommand#setResult(Result, String)} with a result status of
@ -485,7 +499,18 @@ public List<ReceiveCommand> getAllCommands() {
* string must not end with an LF, and must not contain an LF. * string must not end with an LF, and must not contain an LF.
*/ */
public void sendError(final String what) { public void sendError(final String what) {
sendMessage("error", what); if (refs == null) {
if (advertiseError == null)
advertiseError = new StringBuilder();
advertiseError.append(what).append('\n');
} else {
try {
if (msgs != null)
msgs.write("error: " + what + "\n");
} catch (IOException e) {
// Ignore write failures.
}
}
} }
/** /**
@ -499,12 +524,12 @@ public void sendError(final String what) {
* string must not end with an LF, and must not contain an LF. * string must not end with an LF, and must not contain an LF.
*/ */
public void sendMessage(final String what) { public void sendMessage(final String what) {
sendMessage("remote", what); try {
} if (msgs != null)
msgs.write(what + "\n");
private void sendMessage(final String type, final String what) { } catch (IOException e) {
if (msgs != null) // Ignore write failures.
msgs.println(type + ": " + what); }
} }
/** /**
@ -544,16 +569,8 @@ public void receive(final InputStream input, final OutputStream output,
pckIn = new PacketLineIn(rawIn); pckIn = new PacketLineIn(rawIn);
pckOut = new PacketLineOut(rawOut); pckOut = new PacketLineOut(rawOut);
if (messages != null) { if (messages != null)
msgs = new PrintWriter(new BufferedWriter( msgs = new OutputStreamWriter(messages, Constants.CHARSET);
new OutputStreamWriter(messages, Constants.CHARSET),
8192)) {
@Override
public void println() {
print('\n');
}
};
}
enabledCapablities = new HashSet<String>(); enabledCapablities = new HashSet<String>();
commands = new ArrayList<ReceiveCommand>(); commands = new ArrayList<ReceiveCommand>();
@ -561,8 +578,19 @@ public void println() {
service(); service();
} finally { } finally {
try { try {
if (msgs != null) { if (pckOut != null)
pckOut.flush();
if (msgs != null)
msgs.flush(); msgs.flush();
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.
//
new PacketLineOut(output).end();
} }
} finally { } finally {
unlockPack(); unlockPack();
@ -591,6 +619,8 @@ private void service() throws IOException {
sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut)); sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
else else
refs = refFilter.filter(db.getAllRefs()); refs = refFilter.filter(db.getAllRefs());
if (advertiseError != null)
return;
recvCommands(); recvCommands();
if (!commands.isEmpty()) { if (!commands.isEmpty()) {
enableCapabilities(); enableCapabilities();
@ -626,10 +656,9 @@ void sendString(final String s) throws IOException {
} else if (msgs != null) { } else if (msgs != null) {
sendStatusReport(false, new Reporter() { sendStatusReport(false, new Reporter() {
void sendString(final String s) throws IOException { void sendString(final String s) throws IOException {
msgs.println(s); msgs.write(s + "\n");
} }
}); });
msgs.flush();
} }
postReceive.onPostReceive(this, filterCommands(Result.OK)); postReceive.onPostReceive(this, filterCommands(Result.OK));
@ -652,8 +681,14 @@ private void unlockPack() {
* the formatter failed to write an advertisement. * the formatter failed to write an advertisement.
*/ */
public void sendAdvertisedRefs(final RefAdvertiser adv) throws IOException { public void sendAdvertisedRefs(final RefAdvertiser adv) throws IOException {
if (advertiseError != null) {
adv.writeOne("ERR " + advertiseError);
return;
}
final RevFlag advertised = walk.newFlag("ADVERTISED"); final RevFlag advertised = walk.newFlag("ADVERTISED");
adv.init(walk, advertised); adv.init(walk, advertised);
adv.advertiseCapability(CAPABILITY_SIDE_BAND_64K);
adv.advertiseCapability(CAPABILITY_DELETE_REFS); adv.advertiseCapability(CAPABILITY_DELETE_REFS);
adv.advertiseCapability(CAPABILITY_REPORT_STATUS); adv.advertiseCapability(CAPABILITY_REPORT_STATUS);
if (allowOfsDelta) if (allowOfsDelta)
@ -712,6 +747,16 @@ private void recvCommands() throws IOException {
private void enableCapabilities() { private void enableCapabilities() {
reportStatus = enabledCapablities.contains(CAPABILITY_REPORT_STATUS); reportStatus = enabledCapablities.contains(CAPABILITY_REPORT_STATUS);
sideBand = enabledCapablities.contains(CAPABILITY_SIDE_BAND_64K);
if (sideBand) {
OutputStream out = rawOut;
rawOut = new SideBandOutputStream(CH_DATA, MAX_BUF, out);
pckOut = new PacketLineOut(rawOut);
msgs = new OutputStreamWriter(new SideBandOutputStream(CH_PROGRESS,
MAX_BUF, out), Constants.CHARSET);
}
} }
private boolean needPack() { private boolean needPack() {

View File

@ -44,8 +44,11 @@
package org.eclipse.jgit.transport; package org.eclipse.jgit.transport;
import static org.eclipse.jgit.transport.SideBandOutputStream.HDR_SIZE;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.Writer;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -69,27 +72,31 @@
* Channel 3 results in an exception being thrown, as the remote side has issued * Channel 3 results in an exception being thrown, as the remote side has issued
* an unrecoverable error. * an unrecoverable error.
* *
* @see PacketLineIn#sideband(ProgressMonitor) * @see SideBandOutputStream
*/ */
class SideBandInputStream extends InputStream { class SideBandInputStream extends InputStream {
private static final String PFX_REMOTE = "remote: ";
static final int CH_DATA = 1; static final int CH_DATA = 1;
static final int CH_PROGRESS = 2; static final int CH_PROGRESS = 2;
static final int CH_ERROR = 3; static final int CH_ERROR = 3;
private static Pattern P_UNBOUNDED = Pattern.compile( private static Pattern P_UNBOUNDED = Pattern
"^([\\w ]+): (\\d+)( |, done)?.*", Pattern.DOTALL); .compile("^([\\w ]+): +(\\d+)(?:, done\\.)? *[\r\n]$");
private static Pattern P_BOUNDED = Pattern.compile( private static Pattern P_BOUNDED = Pattern
"^([\\w ]+):.*\\((\\d+)/(\\d+)\\).*", Pattern.DOTALL); .compile("^([\\w ]+): +\\d+% +\\( *(\\d+)/ *(\\d+)\\)(?:, done\\.)? *[\r\n]$");
private final InputStream rawIn;
private final PacketLineIn pckIn; private final PacketLineIn pckIn;
private final InputStream in;
private final ProgressMonitor monitor; private final ProgressMonitor monitor;
private final Writer messages;
private String progressBuffer = ""; private String progressBuffer = "";
private String currentTask; private String currentTask;
@ -102,11 +109,12 @@ class SideBandInputStream extends InputStream {
private int available; private int available;
SideBandInputStream(final PacketLineIn aPckIn, final InputStream aIn, SideBandInputStream(final InputStream in, final ProgressMonitor progress,
final ProgressMonitor aProgress) { final Writer messageStream) {
pckIn = aPckIn; rawIn = in;
in = aIn; pckIn = new PacketLineIn(rawIn);
monitor = aProgress; monitor = progress;
messages = messageStream;
currentTask = ""; currentTask = "";
} }
@ -116,7 +124,7 @@ public int read() throws IOException {
if (eof) if (eof)
return -1; return -1;
available--; available--;
return in.read(); return rawIn.read();
} }
@Override @Override
@ -126,7 +134,7 @@ public int read(final byte[] b, int off, int len) throws IOException {
needDataPacket(); needDataPacket();
if (eof) if (eof)
break; break;
final int n = in.read(b, off, Math.min(len, available)); final int n = rawIn.read(b, off, Math.min(len, available));
if (n < 0) if (n < 0)
break; break;
r += n; r += n;
@ -147,8 +155,8 @@ private void needDataPacket() throws IOException {
return; return;
} }
channel = in.read(); channel = rawIn.read() & 0xff;
available -= 5; // length header plus channel indicator available -= HDR_SIZE; // length header plus channel indicator
if (available == 0) if (available == 0)
continue; continue;
@ -157,18 +165,17 @@ private void needDataPacket() throws IOException {
return; return;
case CH_PROGRESS: case CH_PROGRESS:
progress(readString(available)); progress(readString(available));
continue; continue;
case CH_ERROR: case CH_ERROR:
eof = true; eof = true;
throw new TransportException("remote: " + readString(available)); throw new TransportException(PFX_REMOTE + readString(available));
default: default:
throw new PackProtocolException("Invalid channel " + channel); throw new PackProtocolException("Invalid channel " + channel);
} }
} }
} }
private void progress(String pkt) { private void progress(String pkt) throws IOException {
pkt = progressBuffer + pkt; pkt = progressBuffer + pkt;
for (;;) { for (;;) {
final int lf = pkt.indexOf('\n'); final int lf = pkt.indexOf('\n');
@ -183,16 +190,13 @@ else if (0 <= cr)
else else
break; break;
final String msg = pkt.substring(0, s); doProgressLine(pkt.substring(0, s + 1));
if (doProgressLine(msg)) pkt = pkt.substring(s + 1);
pkt = pkt.substring(s + 1);
else
break;
} }
progressBuffer = pkt; progressBuffer = pkt;
} }
private boolean doProgressLine(final String msg) { private void doProgressLine(final String msg) throws IOException {
Matcher matcher; Matcher matcher;
matcher = P_BOUNDED.matcher(msg); matcher = P_BOUNDED.matcher(msg);
@ -201,13 +205,12 @@ private boolean doProgressLine(final String msg) {
if (!currentTask.equals(taskname)) { if (!currentTask.equals(taskname)) {
currentTask = taskname; currentTask = taskname;
lastCnt = 0; lastCnt = 0;
final int tot = Integer.parseInt(matcher.group(3)); beginTask(Integer.parseInt(matcher.group(3)));
monitor.beginTask(currentTask, tot);
} }
final int cnt = Integer.parseInt(matcher.group(2)); final int cnt = Integer.parseInt(matcher.group(2));
monitor.update(cnt - lastCnt); monitor.update(cnt - lastCnt);
lastCnt = cnt; lastCnt = cnt;
return true; return;
} }
matcher = P_UNBOUNDED.matcher(msg); matcher = P_UNBOUNDED.matcher(msg);
@ -216,20 +219,24 @@ private boolean doProgressLine(final String msg) {
if (!currentTask.equals(taskname)) { if (!currentTask.equals(taskname)) {
currentTask = taskname; currentTask = taskname;
lastCnt = 0; lastCnt = 0;
monitor.beginTask(currentTask, ProgressMonitor.UNKNOWN); beginTask(ProgressMonitor.UNKNOWN);
} }
final int cnt = Integer.parseInt(matcher.group(2)); final int cnt = Integer.parseInt(matcher.group(2));
monitor.update(cnt - lastCnt); monitor.update(cnt - lastCnt);
lastCnt = cnt; lastCnt = cnt;
return true; return;
} }
return false; messages.write(msg);
}
private void beginTask(final int totalWorkUnits) {
monitor.beginTask(PFX_REMOTE + currentTask, totalWorkUnits);
} }
private String readString(final int len) throws IOException { private String readString(final int len) throws IOException {
final byte[] raw = new byte[len]; final byte[] raw = new byte[len];
IO.readFully(in, raw, 0, len); IO.readFully(rawIn, raw, 0, len);
return RawParseUtils.decode(Constants.CHARSET, raw, 0, len); return RawParseUtils.decode(Constants.CHARSET, raw, 0, len);
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008, Google Inc. * Copyright (C) 2008-2010, Google Inc.
* and other copyright owners as documented in the project's IP log. * and other copyright owners as documented in the project's IP log.
* *
* This program and the accompanying materials are made available * This program and the accompanying materials are made available
@ -47,11 +47,10 @@
import java.io.OutputStream; import java.io.OutputStream;
/** /**
* Multiplexes data and progress messages * Multiplexes data and progress messages.
* <p> * <p>
* To correctly use this class you must wrap it in a BufferedOutputStream with a * This stream is buffered at packet sizes, so the caller doesn't need to wrap
* buffer size no larger than either {@link #SMALL_BUF} or {@link #MAX_BUF}, * it in yet another buffered stream.
* minus {@link #HDR_SIZE}.
*/ */
class SideBandOutputStream extends OutputStream { class SideBandOutputStream extends OutputStream {
static final int CH_DATA = SideBandInputStream.CH_DATA; static final int CH_DATA = SideBandInputStream.CH_DATA;
@ -66,34 +65,93 @@ class SideBandOutputStream extends OutputStream {
static final int HDR_SIZE = 5; static final int HDR_SIZE = 5;
private final int channel; private final OutputStream out;
private final PacketLineOut pckOut; private final byte[] buffer;
private byte[] singleByteBuffer; /**
* Number of bytes in {@link #buffer} that are valid data.
* <p>
* Initialized to {@link #HDR_SIZE} if there is no application data in the
* buffer, as the packet header always appears at the start of the buffer.
*/
private int cnt;
SideBandOutputStream(final int chan, final PacketLineOut out) { /**
channel = chan; * Create a new stream to write side band packets.
pckOut = out; *
* @param chan
* channel number to prefix all packets with, so the remote side
* can demultiplex the stream and get back the original data.
* Must be in the range [0, 255].
* @param sz
* maximum size of a data packet within the stream. The remote
* side needs to agree to the packet size to prevent buffer
* overflows. Must be in the range [HDR_SIZE + 1, MAX_BUF).
* @param os
* stream that the packets are written onto. This stream should
* be attached to a SideBandInputStream on the remote side.
*/
SideBandOutputStream(final int chan, final int sz, final OutputStream os) {
if (chan <= 0 || chan > 255)
throw new IllegalArgumentException("channel " + chan
+ " must be in range [0, 255]");
if (sz <= HDR_SIZE)
throw new IllegalArgumentException("packet size " + sz
+ " must be >= " + HDR_SIZE);
else if (MAX_BUF < sz)
throw new IllegalArgumentException("packet size " + sz
+ " must be <= " + MAX_BUF);
out = os;
buffer = new byte[sz];
buffer[4] = (byte) chan;
cnt = HDR_SIZE;
} }
@Override @Override
public void flush() throws IOException { public void flush() throws IOException {
if (channel != CH_DATA) if (HDR_SIZE < cnt)
pckOut.flush(); writeBuffer();
out.flush();
} }
@Override @Override
public void write(final byte[] b, final int off, final int len) public void write(final byte[] b, int off, int len) throws IOException {
throws IOException { while (0 < len) {
pckOut.writeChannelPacket(channel, b, off, len); int capacity = buffer.length - cnt;
if (cnt == HDR_SIZE && capacity < len) {
// Our block to write is bigger than the packet size,
// stream it out as-is to avoid unnecessary copies.
PacketLineOut.formatLength(buffer, buffer.length);
out.write(buffer, 0, HDR_SIZE);
out.write(b, off, capacity);
off += capacity;
len -= capacity;
} else {
if (capacity == 0)
writeBuffer();
int n = Math.min(len, capacity);
System.arraycopy(b, off, buffer, cnt, n);
cnt += n;
off += n;
len -= n;
}
}
} }
@Override @Override
public void write(final int b) throws IOException { public void write(final int b) throws IOException {
if (singleByteBuffer == null) if (cnt == buffer.length)
singleByteBuffer = new byte[1]; writeBuffer();
singleByteBuffer[0] = (byte) b; buffer[cnt++] = (byte) b;
write(singleByteBuffer); }
private void writeBuffer() throws IOException {
PacketLineOut.formatLength(buffer, cnt);
out.write(buffer, 0, cnt);
cnt = HDR_SIZE;
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008, Google Inc. * Copyright (C) 2008-2010, Google Inc.
* and other copyright owners as documented in the project's IP log. * and other copyright owners as documented in the project's IP log.
* *
* This program and the accompanying materials are made available * This program and the accompanying materials are made available
@ -43,7 +43,7 @@
package org.eclipse.jgit.transport; package org.eclipse.jgit.transport;
import java.io.BufferedOutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
@ -66,12 +66,8 @@ class SideBandProgressMonitor implements ProgressMonitor {
private int totalWork; private int totalWork;
SideBandProgressMonitor(final PacketLineOut pckOut) { SideBandProgressMonitor(final OutputStream os) {
final int bufsz = SideBandOutputStream.SMALL_BUF out = new PrintWriter(new OutputStreamWriter(os, Constants.CHARSET));
- SideBandOutputStream.HDR_SIZE;
out = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(
new SideBandOutputStream(SideBandOutputStream.CH_PROGRESS,
pckOut), bufsz), Constants.CHARSET));
} }
public void start(final int totalTasks) { public void start(final int totalTasks) {

View File

@ -45,7 +45,11 @@
package org.eclipse.jgit.transport; package org.eclipse.jgit.transport;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -136,7 +140,13 @@ class TcpFetchConnection extends BasePackFetchConnection {
super(TransportGitAnon.this); super(TransportGitAnon.this);
sock = openConnection(); sock = openConnection();
try { try {
init(sock.getInputStream(), sock.getOutputStream()); InputStream sIn = sock.getInputStream();
OutputStream sOut = sock.getOutputStream();
sIn = new BufferedInputStream(sIn);
sOut = new BufferedOutputStream(sOut);
init(sIn, sOut);
service("git-upload-pack", pckOut); service("git-upload-pack", pckOut);
} catch (IOException err) { } catch (IOException err) {
close(); close();
@ -169,7 +179,13 @@ class TcpPushConnection extends BasePackPushConnection {
super(TransportGitAnon.this); super(TransportGitAnon.this);
sock = openConnection(); sock = openConnection();
try { try {
init(sock.getInputStream(), sock.getOutputStream()); InputStream sIn = sock.getInputStream();
OutputStream sOut = sock.getOutputStream();
sIn = new BufferedInputStream(sIn);
sOut = new BufferedOutputStream(sOut);
init(sIn, sOut);
service("git-receive-pack", pckOut); service("git-receive-pack", pckOut);
} catch (IOException err) { } catch (IOException err) {
close(); close();

View File

@ -48,7 +48,6 @@
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PipedInputStream; import java.io.PipedInputStream;
import java.io.PipedOutputStream; import java.io.PipedOutputStream;
@ -57,6 +56,8 @@
import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.QuotedString; import org.eclipse.jgit.util.QuotedString;
import org.eclipse.jgit.util.io.MessageWriter;
import org.eclipse.jgit.util.io.StreamCopyThread;
import com.jcraft.jsch.ChannelExec; import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSchException; import com.jcraft.jsch.JSchException;
@ -88,8 +89,6 @@ static boolean canHandle(final URIish uri) {
return false; return false;
} }
OutputStream errStream;
TransportGitSsh(final Repository local, final URIish uri) { TransportGitSsh(final Repository local, final URIish uri) {
super(local, uri); super(local, uri);
} }
@ -152,8 +151,6 @@ ChannelExec exec(final String exe) throws TransportException {
try { try {
final ChannelExec channel = (ChannelExec) sock.openChannel("exec"); final ChannelExec channel = (ChannelExec) sock.openChannel("exec");
channel.setCommand(commandFor(exe)); channel.setCommand(commandFor(exe));
errStream = createErrorStream();
channel.setErrStream(errStream, true);
channel.connect(tms); channel.connect(tms);
return channel; return channel;
} catch (JSchException je) { } catch (JSchException je) {
@ -161,9 +158,9 @@ ChannelExec exec(final String exe) throws TransportException {
} }
} }
void checkExecFailure(int status, String exe) throws TransportException { void checkExecFailure(int status, String exe, String why)
throws TransportException {
if (status == 127) { if (status == 127) {
String why = errStream.toString();
IOException cause = null; IOException cause = null;
if (why != null && why.length() > 0) if (why != null && why.length() > 0)
cause = new IOException(why); cause = new IOException(why);
@ -172,41 +169,8 @@ void checkExecFailure(int status, String exe) throws TransportException {
} }
} }
/** NoRemoteRepositoryException cleanNotFound(NoRemoteRepositoryException nf,
* @return the error stream for the channel, the stream is used to detect String why) {
* specific error reasons for exceptions.
*/
private static OutputStream createErrorStream() {
return new OutputStream() {
private StringBuilder all = new StringBuilder();
private StringBuilder sb = new StringBuilder();
public String toString() {
String r = all.toString();
while (r.endsWith("\n"))
r = r.substring(0, r.length() - 1);
return r;
}
@Override
public void write(final int b) throws IOException {
if (b == '\r') {
return;
}
sb.append((char) b);
if (b == '\n') {
all.append(sb);
sb.setLength(0);
}
}
};
}
NoRemoteRepositoryException cleanNotFound(NoRemoteRepositoryException nf) {
String why = errStream.toString();
if (why == null || why.length() == 0) if (why == null || why.length() == 0)
return nf; return nf;
@ -235,7 +199,7 @@ private OutputStream outputStream(ChannelExec channel) throws IOException {
if (getTimeout() <= 0) if (getTimeout() <= 0)
return out; return out;
final PipedInputStream pipeIn = new PipedInputStream(); final PipedInputStream pipeIn = new PipedInputStream();
final CopyThread copyThread = new CopyThread(pipeIn, out); final StreamCopyThread copyThread = new StreamCopyThread(pipeIn, out);
final PipedOutputStream pipeOut = new PipedOutputStream(pipeIn) { final PipedOutputStream pipeOut = new PipedOutputStream(pipeIn) {
@Override @Override
public void flush() throws IOException { public void flush() throws IOException {
@ -257,79 +221,28 @@ public void close() throws IOException {
return pipeOut; return pipeOut;
} }
private static class CopyThread extends Thread {
private final InputStream src;
private final OutputStream dst;
private volatile boolean doFlush;
CopyThread(final InputStream i, final OutputStream o) {
setName(Thread.currentThread().getName() + "-Output");
src = i;
dst = o;
}
void flush() {
if (!doFlush) {
doFlush = true;
interrupt();
}
}
@Override
public void run() {
try {
final byte[] buf = new byte[1024];
for (;;) {
try {
if (doFlush) {
doFlush = false;
dst.flush();
}
final int n;
try {
n = src.read(buf);
} catch (InterruptedIOException wakey) {
continue;
}
if (n < 0)
break;
dst.write(buf, 0, n);
} catch (IOException e) {
break;
}
}
} finally {
try {
src.close();
} catch (IOException e) {
// Ignore IO errors on close
}
try {
dst.close();
} catch (IOException e) {
// Ignore IO errors on close
}
}
}
}
class SshFetchConnection extends BasePackFetchConnection { class SshFetchConnection extends BasePackFetchConnection {
private ChannelExec channel; private ChannelExec channel;
private Thread errorThread;
private int exitStatus; private int exitStatus;
SshFetchConnection() throws TransportException { SshFetchConnection() throws TransportException {
super(TransportGitSsh.this); super(TransportGitSsh.this);
try { try {
channel = exec(getOptionUploadPack()); final MessageWriter msg = new MessageWriter();
setMessageWriter(msg);
if (channel.isConnected()) channel = exec(getOptionUploadPack());
init(channel.getInputStream(), outputStream(channel)); if (!channel.isConnected())
else throw new TransportException(uri, "connection failed");
throw new TransportException(uri, errStream.toString());
final InputStream upErr = channel.getErrStream();
errorThread = new StreamCopyThread(upErr, msg.getRawStream());
errorThread.start();
init(channel.getInputStream(), outputStream(channel));
} catch (TransportException err) { } catch (TransportException err) {
close(); close();
@ -343,14 +256,24 @@ class SshFetchConnection extends BasePackFetchConnection {
try { try {
readAdvertisedRefs(); readAdvertisedRefs();
} catch (NoRemoteRepositoryException notFound) { } catch (NoRemoteRepositoryException notFound) {
close(); final String msgs = getMessages();
checkExecFailure(exitStatus, getOptionUploadPack()); checkExecFailure(exitStatus, getOptionUploadPack(), msgs);
throw cleanNotFound(notFound); throw cleanNotFound(notFound, msgs);
} }
} }
@Override @Override
public void close() { public void close() {
if (errorThread != null) {
try {
errorThread.join();
} catch (InterruptedException e) {
// Stop waiting and return anyway.
} finally {
errorThread = null;
}
}
super.close(); super.close();
if (channel != null) { if (channel != null) {
@ -368,17 +291,25 @@ public void close() {
class SshPushConnection extends BasePackPushConnection { class SshPushConnection extends BasePackPushConnection {
private ChannelExec channel; private ChannelExec channel;
private Thread errorThread;
private int exitStatus; private int exitStatus;
SshPushConnection() throws TransportException { SshPushConnection() throws TransportException {
super(TransportGitSsh.this); super(TransportGitSsh.this);
try { try {
channel = exec(getOptionReceivePack()); final MessageWriter msg = new MessageWriter();
setMessageWriter(msg);
if (channel.isConnected()) channel = exec(getOptionReceivePack());
init(channel.getInputStream(), outputStream(channel)); if (!channel.isConnected())
else throw new TransportException(uri, "connection failed");
throw new TransportException(uri, errStream.toString());
final InputStream rpErr = channel.getErrStream();
errorThread = new StreamCopyThread(rpErr, msg.getRawStream());
errorThread.start();
init(channel.getInputStream(), outputStream(channel));
} catch (TransportException err) { } catch (TransportException err) {
close(); close();
@ -392,14 +323,24 @@ class SshPushConnection extends BasePackPushConnection {
try { try {
readAdvertisedRefs(); readAdvertisedRefs();
} catch (NoRemoteRepositoryException notFound) { } catch (NoRemoteRepositoryException notFound) {
close(); final String msgs = getMessages();
checkExecFailure(exitStatus, getOptionReceivePack()); checkExecFailure(exitStatus, getOptionReceivePack(), msgs);
throw cleanNotFound(notFound); throw cleanNotFound(notFound, msgs);
} }
} }
@Override @Override
public void close() { public void close() {
if (errorThread != null) {
try {
errorThread.join();
} catch (InterruptedException e) {
// Stop waiting and return anyway.
} finally {
errorThread = null;
}
}
super.close(); super.close();
if (channel != null) { if (channel != null) {

View File

@ -632,9 +632,9 @@ class Service {
private final String responseType; private final String responseType;
private final UnionInputStream httpIn; private final HttpExecuteStream execute;
final HttpInputStream in; final UnionInputStream in;
final HttpOutputStream out; final HttpOutputStream out;
@ -645,8 +645,8 @@ class Service {
this.requestType = "application/x-" + serviceName + "-request"; this.requestType = "application/x-" + serviceName + "-request";
this.responseType = "application/x-" + serviceName + "-result"; this.responseType = "application/x-" + serviceName + "-result";
this.httpIn = new UnionInputStream(); this.execute = new HttpExecuteStream();
this.in = new HttpInputStream(httpIn); this.in = new UnionInputStream(execute);
this.out = new HttpOutputStream(); this.out = new HttpOutputStream();
} }
@ -712,7 +712,8 @@ void execute() throws IOException {
throw wrongContentType(responseType, contentType); throw wrongContentType(responseType, contentType);
} }
httpIn.add(openInputStream(conn)); in.add(openInputStream(conn));
in.add(execute);
conn = null; conn = null;
} }
@ -729,43 +730,25 @@ protected OutputStream overflow() throws IOException {
} }
} }
class HttpInputStream extends InputStream { class HttpExecuteStream extends InputStream {
private final UnionInputStream src;
HttpInputStream(UnionInputStream httpIn) {
this.src = httpIn;
}
private InputStream self() throws IOException {
if (src.isEmpty()) {
// If we have no InputStreams available it means we must
// have written data previously to the service, but have
// not yet finished the HTTP request in order to get the
// response from the service. Ensure we get it now.
//
execute();
}
return src;
}
public int available() throws IOException { public int available() throws IOException {
return self().available(); execute();
return 0;
} }
public int read() throws IOException { public int read() throws IOException {
return self().read(); execute();
return -1;
} }
public int read(byte[] b, int off, int len) throws IOException { public int read(byte[] b, int off, int len) throws IOException {
return self().read(b, off, len); execute();
return -1;
} }
public long skip(long n) throws IOException { public long skip(long n) throws IOException {
return self().skip(n); execute();
} return 0;
public void close() throws IOException {
src.close();
} }
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (C) 2007, Dave Watson <dwatson@mimvista.com> * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
* Copyright (C) 2008-2009, Google Inc. * Copyright (C) 2008-2010, Google Inc.
* Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
@ -47,6 +47,8 @@
package org.eclipse.jgit.transport; package org.eclipse.jgit.transport;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -59,6 +61,8 @@
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.io.MessageWriter;
import org.eclipse.jgit.util.io.StreamCopyThread;
/** /**
* Transport to access a local directory as though it were a remote peer. * Transport to access a local directory as though it were a remote peer.
@ -129,11 +133,10 @@ public void close() {
// Resources must be established per-connection. // Resources must be established per-connection.
} }
protected Process startProcessWithErrStream(final String cmd) protected Process spawn(final String cmd)
throws TransportException { throws TransportException {
try { try {
final String[] args; final String[] args;
final Process proc;
if (cmd.startsWith("git-")) { if (cmd.startsWith("git-")) {
args = new String[] { "git", cmd.substring(4), PWD }; args = new String[] { "git", cmd.substring(4), PWD };
@ -148,9 +151,7 @@ protected Process startProcessWithErrStream(final String cmd)
} }
} }
proc = Runtime.getRuntime().exec(args, null, remoteGitDir); return Runtime.getRuntime().exec(args, null, remoteGitDir);
new StreamRewritingThread(cmd, proc.getErrorStream()).start();
return proc;
} catch (IOException err) { } catch (IOException err) {
throw new TransportException(uri, err.getMessage(), err); throw new TransportException(uri, err.getMessage(), err);
} }
@ -246,11 +247,26 @@ public void close() {
class ForkLocalFetchConnection extends BasePackFetchConnection { class ForkLocalFetchConnection extends BasePackFetchConnection {
private Process uploadPack; private Process uploadPack;
private Thread errorReaderThread;
ForkLocalFetchConnection() throws TransportException { ForkLocalFetchConnection() throws TransportException {
super(TransportLocal.this); super(TransportLocal.this);
uploadPack = startProcessWithErrStream(getOptionUploadPack());
final InputStream upIn = uploadPack.getInputStream(); final MessageWriter msg = new MessageWriter();
final OutputStream upOut = uploadPack.getOutputStream(); setMessageWriter(msg);
uploadPack = spawn(getOptionUploadPack());
final InputStream upErr = uploadPack.getErrorStream();
errorReaderThread = new StreamCopyThread(upErr, msg.getRawStream());
errorReaderThread.start();
InputStream upIn = uploadPack.getInputStream();
OutputStream upOut = uploadPack.getOutputStream();
upIn = new BufferedInputStream(upIn);
upOut = new BufferedOutputStream(upOut);
init(upIn, upOut); init(upIn, upOut);
readAdvertisedRefs(); readAdvertisedRefs();
} }
@ -268,6 +284,16 @@ public void close() {
uploadPack = null; uploadPack = null;
} }
} }
if (errorReaderThread != null) {
try {
errorReaderThread.join();
} catch (InterruptedException e) {
// Stop waiting and return anyway.
} finally {
errorReaderThread = null;
}
}
} }
} }
@ -351,11 +377,26 @@ public void close() {
class ForkLocalPushConnection extends BasePackPushConnection { class ForkLocalPushConnection extends BasePackPushConnection {
private Process receivePack; private Process receivePack;
private Thread errorReaderThread;
ForkLocalPushConnection() throws TransportException { ForkLocalPushConnection() throws TransportException {
super(TransportLocal.this); super(TransportLocal.this);
receivePack = startProcessWithErrStream(getOptionReceivePack());
final InputStream rpIn = receivePack.getInputStream(); final MessageWriter msg = new MessageWriter();
final OutputStream rpOut = receivePack.getOutputStream(); setMessageWriter(msg);
receivePack = spawn(getOptionReceivePack());
final InputStream rpErr = receivePack.getErrorStream();
errorReaderThread = new StreamCopyThread(rpErr, msg.getRawStream());
errorReaderThread.start();
InputStream rpIn = receivePack.getInputStream();
OutputStream rpOut = receivePack.getOutputStream();
rpIn = new BufferedInputStream(rpIn);
rpOut = new BufferedOutputStream(rpOut);
init(rpIn, rpOut); init(rpIn, rpOut);
readAdvertisedRefs(); readAdvertisedRefs();
} }
@ -373,34 +414,14 @@ public void close() {
receivePack = null; receivePack = null;
} }
} }
}
}
static class StreamRewritingThread extends Thread { if (errorReaderThread != null) {
private final InputStream in;
StreamRewritingThread(final String cmd, final InputStream in) {
super("JGit " + cmd + " Errors");
this.in = in;
}
public void run() {
final byte[] tmp = new byte[512];
try {
for (;;) {
final int n = in.read(tmp);
if (n < 0)
break;
System.err.write(tmp, 0, n);
System.err.flush();
}
} catch (IOException err) {
// Ignore errors reading errors.
} finally {
try { try {
in.close(); errorReaderThread.join();
} catch (IOException err2) { } catch (InterruptedException e) {
// Ignore errors closing the pipe. // Stop waiting and return anyway.
} finally {
errorReaderThread = null;
} }
} }
} }

View File

@ -43,9 +43,6 @@
package org.eclipse.jgit.transport; package org.eclipse.jgit.transport;
import static org.eclipse.jgit.transport.BasePackFetchConnection.MultiAck;
import java.io.BufferedOutputStream;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -70,6 +67,7 @@
import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.BasePackFetchConnection.MultiAck;
import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
import org.eclipse.jgit.util.io.InterruptTimer; import org.eclipse.jgit.util.io.InterruptTimer;
import org.eclipse.jgit.util.io.TimeoutInputStream; import org.eclipse.jgit.util.io.TimeoutInputStream;
@ -556,13 +554,12 @@ private void sendPack() throws IOException {
int bufsz = SideBandOutputStream.SMALL_BUF; int bufsz = SideBandOutputStream.SMALL_BUF;
if (options.contains(OPTION_SIDE_BAND_64K)) if (options.contains(OPTION_SIDE_BAND_64K))
bufsz = SideBandOutputStream.MAX_BUF; bufsz = SideBandOutputStream.MAX_BUF;
bufsz -= SideBandOutputStream.HDR_SIZE;
packOut = new BufferedOutputStream(new SideBandOutputStream(
SideBandOutputStream.CH_DATA, pckOut), bufsz);
packOut = new SideBandOutputStream(SideBandOutputStream.CH_DATA,
bufsz, rawOut);
if (progress) if (progress)
pm = new SideBandProgressMonitor(pckOut); pm = new SideBandProgressMonitor(new SideBandOutputStream(
SideBandOutputStream.CH_PROGRESS, bufsz, rawOut));
} }
final PackWriter pw; final PackWriter pw;
@ -586,12 +583,9 @@ private void sendPack() throws IOException {
} }
} }
pw.writePack(packOut); pw.writePack(packOut);
packOut.flush();
if (sideband) { if (sideband)
packOut.flush();
pckOut.end(); pckOut.end();
} else {
rawOut.flush();
}
} }
} }

View File

@ -45,6 +45,7 @@
import static org.eclipse.jgit.transport.WalkRemoteObjectDatabase.ROOT_DIR; import static org.eclipse.jgit.transport.WalkRemoteObjectDatabase.ROOT_DIR;
import java.io.BufferedOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
@ -251,6 +252,7 @@ private void sendpack(final List<RemoteRefUpdate> updates,
final String wt = "Put " + base.substring(0, 12); final String wt = "Put " + base.substring(0, 12);
OutputStream os = dest.writeFile(pathPack, monitor, wt + "..pack"); OutputStream os = dest.writeFile(pathPack, monitor, wt + "..pack");
try { try {
os = new BufferedOutputStream(os);
pw.writePack(os); pw.writePack(os);
} finally { } finally {
os.close(); os.close();
@ -258,6 +260,7 @@ private void sendpack(final List<RemoteRefUpdate> updates,
os = dest.writeFile(pathIdx, monitor, wt + "..idx"); os = dest.writeFile(pathIdx, monitor, wt + "..idx");
try { try {
os = new BufferedOutputStream(os);
pw.writeIndex(os); pw.writeIndex(os);
} finally { } finally {
os.close(); os.close();

View File

@ -0,0 +1,115 @@
/*
* Copyright (C) 2009-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.util.io;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.transport.BaseConnection;
import org.eclipse.jgit.util.RawParseUtils;
/**
* Combines messages from an OutputStream (hopefully in UTF-8) and a Writer.
* <p>
* This class is primarily meant for {@link BaseConnection} in contexts where a
* standard error stream from a command execution, as well as messages from a
* side-band channel, need to be combined together into a buffer to represent
* the complete set of messages from a remote repository.
* <p>
* Writes made to the writer are re-encoded as UTF-8 and interleaved into the
* buffer that {@link #getRawStream()} also writes to.
* <p>
* {@link #toString()} returns all written data, after converting it to a String
* under the assumption of UTF-8 encoding.
* <p>
* Internally {@link RawParseUtils#decode(byte[])} is used by {@code toString()}
* tries to work out a reasonably correct character set for the raw data.
*/
public class MessageWriter extends Writer {
private final ByteArrayOutputStream buf;
private final OutputStreamWriter enc;
/** Create an empty writer. */
public MessageWriter() {
buf = new ByteArrayOutputStream();
enc = new OutputStreamWriter(getRawStream(), Constants.CHARSET);
}
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
synchronized (buf) {
enc.write(cbuf, off, len);
enc.flush();
}
}
/**
* @return the underlying byte stream that character writes to this writer
* drop into. Writes to this stream should should be in UTF-8.
*/
public OutputStream getRawStream() {
return buf;
}
@Override
public void close() throws IOException {
// Do nothing, we are buffered with no resources.
}
@Override
public void flush() throws IOException {
// Do nothing, we are buffered with no resources.
}
/** @return string version of all buffered data. */
@Override
public String toString() {
return RawParseUtils.decode(buf.toByteArray());
}
}

View File

@ -0,0 +1,128 @@
/*
* Copyright (C) 2009-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.util.io;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
/** Thread to copy from an input stream to an output stream. */
public class StreamCopyThread extends Thread {
private static final int BUFFER_SIZE = 1024;
private final InputStream src;
private final OutputStream dst;
private volatile boolean doFlush;
/**
* Create a thread to copy data from an input stream to an output stream.
*
* @param i
* stream to copy from. The thread terminates when this stream
* reaches EOF. The thread closes this stream before it exits.
* @param o
* stream to copy into. The destination stream is automatically
* closed when the thread terminates.
*/
public StreamCopyThread(final InputStream i, final OutputStream o) {
setName(Thread.currentThread().getName() + "-StreamCopy");
src = i;
dst = o;
}
/**
* Request the thread to flush the output stream as soon as possible.
* <p>
* This is an asynchronous request to the thread. The actual flush will
* happen at some future point in time, when the thread wakes up to process
* the request.
*/
public void flush() {
if (!doFlush) {
doFlush = true;
interrupt();
}
}
@Override
public void run() {
try {
final byte[] buf = new byte[BUFFER_SIZE];
for (;;) {
try {
if (doFlush) {
doFlush = false;
dst.flush();
}
final int n;
try {
n = src.read(buf);
} catch (InterruptedIOException wakey) {
continue;
}
if (n < 0)
break;
dst.write(buf, 0, n);
} catch (IOException e) {
break;
}
}
} finally {
try {
src.close();
} catch (IOException e) {
// Ignore IO errors on close
}
try {
dst.close();
} catch (IOException e) {
// Ignore IO errors on close
}
}
}
}